I BROKE EVERYTHING
refactored the rendering process
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
#include "messages/lazyloadedimage.hpp"
|
||||
#include "messages/image.hpp"
|
||||
#include "asyncexec.hpp"
|
||||
#include "singletons/emotemanager.hpp"
|
||||
#include "singletons/ircmanager.hpp"
|
||||
@@ -19,8 +19,8 @@
|
||||
namespace chatterino {
|
||||
namespace messages {
|
||||
|
||||
LazyLoadedImage::LazyLoadedImage(const QString &url, qreal scale, const QString &name,
|
||||
const QString &tooltip, const QMargins &margin, bool isHat)
|
||||
Image::Image(const QString &url, qreal scale, const QString &name, const QString &tooltip,
|
||||
const QMargins &margin, bool isHat)
|
||||
: currentPixmap(nullptr)
|
||||
, url(url)
|
||||
, name(name)
|
||||
@@ -32,8 +32,8 @@ LazyLoadedImage::LazyLoadedImage(const QString &url, qreal scale, const QString
|
||||
{
|
||||
}
|
||||
|
||||
LazyLoadedImage::LazyLoadedImage(QPixmap *image, qreal scale, const QString &name,
|
||||
const QString &tooltip, const QMargins &margin, bool isHat)
|
||||
Image::Image(QPixmap *image, qreal scale, const QString &name, const QString &tooltip,
|
||||
const QMargins &margin, bool isHat)
|
||||
: currentPixmap(image)
|
||||
, name(name)
|
||||
, tooltip(tooltip)
|
||||
@@ -44,7 +44,7 @@ LazyLoadedImage::LazyLoadedImage(QPixmap *image, qreal scale, const QString &nam
|
||||
{
|
||||
}
|
||||
|
||||
void LazyLoadedImage::loadImage()
|
||||
void Image::loadImage()
|
||||
{
|
||||
util::NetworkRequest req(this->getUrl());
|
||||
req.setCaller(this);
|
||||
@@ -67,7 +67,7 @@ void LazyLoadedImage::loadImage()
|
||||
lli->currentPixmap = pixmap;
|
||||
}
|
||||
|
||||
chatterino::messages::LazyLoadedImage::FrameData data;
|
||||
chatterino::messages::Image::FrameData data;
|
||||
data.duration = std::max(20, reader.nextImageDelay());
|
||||
data.image = pixmap;
|
||||
|
||||
@@ -90,7 +90,7 @@ void LazyLoadedImage::loadImage()
|
||||
// doesn't work, so this is the fix.
|
||||
}
|
||||
|
||||
void LazyLoadedImage::gifUpdateTimout()
|
||||
void Image::gifUpdateTimout()
|
||||
{
|
||||
if (animated) {
|
||||
this->currentFrameOffset += GIF_FRAME_LENGTH;
|
||||
@@ -108,7 +108,7 @@ void LazyLoadedImage::gifUpdateTimout()
|
||||
}
|
||||
}
|
||||
|
||||
const QPixmap *LazyLoadedImage::getPixmap()
|
||||
const QPixmap *Image::getPixmap()
|
||||
{
|
||||
if (!this->isLoading) {
|
||||
this->isLoading = true;
|
||||
@@ -118,42 +118,42 @@ const QPixmap *LazyLoadedImage::getPixmap()
|
||||
return this->currentPixmap;
|
||||
}
|
||||
|
||||
qreal LazyLoadedImage::getScale() const
|
||||
qreal Image::getScale() const
|
||||
{
|
||||
return this->scale;
|
||||
}
|
||||
|
||||
const QString &LazyLoadedImage::getUrl() const
|
||||
const QString &Image::getUrl() const
|
||||
{
|
||||
return this->url;
|
||||
}
|
||||
|
||||
const QString &LazyLoadedImage::getName() const
|
||||
const QString &Image::getName() const
|
||||
{
|
||||
return this->name;
|
||||
}
|
||||
|
||||
const QString &LazyLoadedImage::getTooltip() const
|
||||
const QString &Image::getTooltip() const
|
||||
{
|
||||
return this->tooltip;
|
||||
}
|
||||
|
||||
const QMargins &LazyLoadedImage::getMargin() const
|
||||
const QMargins &Image::getMargin() const
|
||||
{
|
||||
return this->margin;
|
||||
}
|
||||
|
||||
bool LazyLoadedImage::getAnimated() const
|
||||
bool Image::isAnimated() const
|
||||
{
|
||||
return this->animated;
|
||||
}
|
||||
|
||||
bool LazyLoadedImage::isHat() const
|
||||
bool Image::isHat() const
|
||||
{
|
||||
return this->ishat;
|
||||
}
|
||||
|
||||
int LazyLoadedImage::getWidth() const
|
||||
int Image::getWidth() const
|
||||
{
|
||||
if (this->currentPixmap == nullptr) {
|
||||
return 16;
|
||||
@@ -161,12 +161,12 @@ int LazyLoadedImage::getWidth() const
|
||||
return this->currentPixmap->width();
|
||||
}
|
||||
|
||||
int LazyLoadedImage::getScaledWidth() const
|
||||
int Image::getScaledWidth() const
|
||||
{
|
||||
return static_cast<int>(getWidth() * this->scale);
|
||||
return static_cast<int>(this->getWidth() * this->scale);
|
||||
}
|
||||
|
||||
int LazyLoadedImage::getHeight() const
|
||||
int Image::getHeight() const
|
||||
{
|
||||
if (this->currentPixmap == nullptr) {
|
||||
return 16;
|
||||
@@ -174,9 +174,9 @@ int LazyLoadedImage::getHeight() const
|
||||
return this->currentPixmap->height();
|
||||
}
|
||||
|
||||
int LazyLoadedImage::getScaledHeight() const
|
||||
int Image::getScaledHeight() const
|
||||
{
|
||||
return static_cast<int>(getHeight() * this->scale);
|
||||
return static_cast<int>(this->getHeight() * this->scale);
|
||||
}
|
||||
|
||||
} // namespace messages
|
||||
@@ -3,21 +3,23 @@
|
||||
#include <QPixmap>
|
||||
#include <QString>
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
namespace chatterino {
|
||||
namespace messages {
|
||||
|
||||
class LazyLoadedImage : public QObject
|
||||
class Image : public QObject, boost::noncopyable
|
||||
{
|
||||
public:
|
||||
LazyLoadedImage() = delete;
|
||||
Image() = delete;
|
||||
|
||||
explicit LazyLoadedImage(const QString &_url, qreal _scale = 1, const QString &_name = "",
|
||||
const QString &_tooltip = "", const QMargins &_margin = QMargins(),
|
||||
bool isHat = false);
|
||||
explicit Image(const QString &_url, qreal _scale = 1, const QString &_name = "",
|
||||
const QString &_tooltip = "", const QMargins &_margin = QMargins(),
|
||||
bool isHat = false);
|
||||
|
||||
explicit LazyLoadedImage(QPixmap *_currentPixmap, qreal _scale = 1, const QString &_name = "",
|
||||
const QString &_tooltip = "", const QMargins &_margin = QMargins(),
|
||||
bool isHat = false);
|
||||
explicit Image(QPixmap *_currentPixmap, qreal _scale = 1, const QString &_name = "",
|
||||
const QString &_tooltip = "", const QMargins &_margin = QMargins(),
|
||||
bool isHat = false);
|
||||
|
||||
const QPixmap *getPixmap();
|
||||
qreal getScale() const;
|
||||
@@ -25,7 +27,7 @@ public:
|
||||
const QString &getName() const;
|
||||
const QString &getTooltip() const;
|
||||
const QMargins &getMargin() const;
|
||||
bool getAnimated() const;
|
||||
bool isAnimated() const;
|
||||
bool isHat() const;
|
||||
int getWidth() const;
|
||||
int getScaledWidth() const;
|
||||
@@ -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
|
||||
@@ -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 ℑ
|
||||
};
|
||||
|
||||
// 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
|
||||
@@ -171,7 +171,7 @@ public:
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->mutex);
|
||||
|
||||
int x = 0;
|
||||
size_t x = 0;
|
||||
|
||||
for (size_t i = 0; i < this->chunks->size(); i++) {
|
||||
Chunk &chunk = this->chunks->at(i);
|
||||
@@ -191,11 +191,12 @@ public:
|
||||
newChunk->at(j) = replacement;
|
||||
this->chunks->at(i) = newChunk;
|
||||
|
||||
return x;
|
||||
return true;
|
||||
}
|
||||
x++;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// void insertAfter(const std::vector<T> &items, const T &index)
|
||||
|
||||
+55
-127
@@ -1,67 +1,34 @@
|
||||
#include "messages/message.hpp"
|
||||
#include "channel.hpp"
|
||||
#include "emojis.hpp"
|
||||
#include "messages/link.hpp"
|
||||
#include "singletons/emotemanager.hpp"
|
||||
#include "singletons/fontmanager.hpp"
|
||||
#include "singletons/ircmanager.hpp"
|
||||
#include "singletons/resourcemanager.hpp"
|
||||
#include "singletons/thememanager.hpp"
|
||||
#include "messageelement.hpp"
|
||||
#include "util/irchelpers.hpp"
|
||||
|
||||
#include <ctime>
|
||||
#include <list>
|
||||
#include <tuple>
|
||||
|
||||
typedef chatterino::widgets::ScrollbarHighlight SBHighlight;
|
||||
|
||||
namespace chatterino {
|
||||
namespace messages {
|
||||
|
||||
bool Message::containsHighlightedPhrase() const
|
||||
// elements
|
||||
void Message::addElement(MessageElement *element)
|
||||
{
|
||||
return this->highlightTab;
|
||||
this->elements.push_back(std::unique_ptr<MessageElement>(element));
|
||||
}
|
||||
|
||||
void Message::setHighlight(bool value)
|
||||
const std::vector<std::unique_ptr<MessageElement>> &Message::getElements() const
|
||||
{
|
||||
this->highlightTab = value;
|
||||
}
|
||||
|
||||
const QString &Message::getTimeoutUser() const
|
||||
{
|
||||
return this->timeoutUser;
|
||||
}
|
||||
|
||||
int Message::getTimeoutCount() const
|
||||
{
|
||||
return this->timeoutCount;
|
||||
}
|
||||
|
||||
const QString &Message::getContent() const
|
||||
{
|
||||
if (this->content.isNull()) {
|
||||
this->updateContent();
|
||||
}
|
||||
|
||||
return this->content;
|
||||
}
|
||||
|
||||
const std::chrono::time_point<std::chrono::system_clock> &Message::getParseTime() const
|
||||
{
|
||||
return this->parseTime;
|
||||
}
|
||||
|
||||
std::vector<Word> &Message::getWords()
|
||||
{
|
||||
return this->words;
|
||||
return this->elements;
|
||||
}
|
||||
|
||||
// Flags
|
||||
Message::MessageFlags Message::getFlags() const
|
||||
{
|
||||
return this->flags;
|
||||
}
|
||||
|
||||
bool Message::hasFlags(MessageFlags _flags) const
|
||||
{
|
||||
return this->flags & _flags;
|
||||
}
|
||||
|
||||
void Message::setFlags(MessageFlags _flags)
|
||||
{
|
||||
this->flags = flags;
|
||||
@@ -77,113 +44,74 @@ void Message::removeFlags(MessageFlags _flags)
|
||||
this->flags = (MessageFlags)((MessageFlagsType)this->flags & ~((MessageFlagsType)_flags));
|
||||
}
|
||||
|
||||
bool Message::isDisabled() const
|
||||
// Parse Time
|
||||
const QTime &Message::getParseTime() const
|
||||
{
|
||||
return this->disabled;
|
||||
}
|
||||
|
||||
void Message::setDisabled(bool value)
|
||||
{
|
||||
this->disabled = value;
|
||||
return this->parseTime;
|
||||
}
|
||||
|
||||
// Id
|
||||
const QString &Message::getId() const
|
||||
{
|
||||
return this->id;
|
||||
}
|
||||
|
||||
bool Message::getCollapsedDefault() const
|
||||
void Message::setId(const QString &_id)
|
||||
{
|
||||
return this->collapsedDefault;
|
||||
this->id = _id;
|
||||
}
|
||||
|
||||
void Message::setCollapsedDefault(bool value)
|
||||
// Search
|
||||
const QString &Message::getSearchText() const
|
||||
{
|
||||
this->collapsedDefault = value;
|
||||
}
|
||||
|
||||
bool Message::getDisableCompactEmotes() const
|
||||
{
|
||||
return this->disableCompactEmotes;
|
||||
}
|
||||
|
||||
void Message::setDisableCompactEmotes(bool value)
|
||||
{
|
||||
this->disableCompactEmotes = value;
|
||||
}
|
||||
|
||||
void Message::updateContent() const
|
||||
{
|
||||
QString _content("");
|
||||
|
||||
bool first;
|
||||
|
||||
for (const Word &word : this->words) {
|
||||
if (!first) {
|
||||
_content += "";
|
||||
}
|
||||
|
||||
_content += word.getCopyText();
|
||||
first = false;
|
||||
}
|
||||
|
||||
this->content = _content;
|
||||
// fourtf: asdf
|
||||
// if (this->searchText.isNull()) {
|
||||
// QString _content("");
|
||||
|
||||
// bool first;
|
||||
|
||||
// for (const MessageElement &word : this->words) {
|
||||
// if (!first) {
|
||||
// _content += "";
|
||||
// }
|
||||
|
||||
// _content += word.getCopyText();
|
||||
// first = false;
|
||||
// }
|
||||
|
||||
// this->searchText = _content;
|
||||
// }
|
||||
|
||||
// return this->searchText;
|
||||
|
||||
static QString xd;
|
||||
|
||||
return xd;
|
||||
}
|
||||
|
||||
// Highlight
|
||||
SBHighlight Message::getScrollBarHighlight() const
|
||||
{
|
||||
if (this->getFlags() & Message::Highlighted) {
|
||||
if (this->hasFlags(Message::Highlighted)) {
|
||||
return SBHighlight(SBHighlight::Highlight);
|
||||
}
|
||||
return SBHighlight();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void AddCurrentTimestamp(Message *message)
|
||||
// Static
|
||||
MessagePtr Message::createSystemMessage(const QString &text)
|
||||
{
|
||||
std::time_t t;
|
||||
time(&t);
|
||||
char timeStampBuffer[69];
|
||||
MessagePtr message(new Message);
|
||||
|
||||
// Add word for timestamp with no seconds
|
||||
strftime(timeStampBuffer, 69, "%H:%M", localtime(&t));
|
||||
QString timestampNoSeconds(timeStampBuffer);
|
||||
message->getWords().push_back(Word(timestampNoSeconds, Word::TimestampNoSeconds,
|
||||
MessageColor(MessageColor::System),
|
||||
singletons::FontManager::Medium, QString(), QString()));
|
||||
|
||||
// Add word for timestamp with seconds
|
||||
strftime(timeStampBuffer, 69, "%H:%M:%S", localtime(&t));
|
||||
QString timestampWithSeconds(timeStampBuffer);
|
||||
message->getWords().push_back(Word(timestampWithSeconds, Word::TimestampWithSeconds,
|
||||
MessageColor(MessageColor::System),
|
||||
singletons::FontManager::Medium, QString(), QString()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/// Static
|
||||
Message *Message::createSystemMessage(const QString &text)
|
||||
{
|
||||
Message *message = new Message;
|
||||
|
||||
AddCurrentTimestamp(message);
|
||||
|
||||
QStringList words = text.split(' ');
|
||||
|
||||
for (QString word : words) {
|
||||
message->getWords().push_back(Word(word, Word::Flags::Default,
|
||||
MessageColor(MessageColor::Type::System),
|
||||
singletons::FontManager::Medium, word, QString()));
|
||||
}
|
||||
message->addElement(new TimestampElement(QTime::currentTime()));
|
||||
message->addElement(new TextElement(text, MessageElement::Text, MessageColor::System));
|
||||
message->addFlags(Message::System);
|
||||
|
||||
return message;
|
||||
return MessagePtr(message);
|
||||
}
|
||||
|
||||
Message *Message::createTimeoutMessage(const QString &username, const QString &durationInSeconds,
|
||||
const QString &reason, bool multipleTimes)
|
||||
MessagePtr Message::createTimeoutMessage(const QString &username, const QString &durationInSeconds,
|
||||
const QString &reason, bool multipleTimes)
|
||||
{
|
||||
QString text;
|
||||
|
||||
@@ -207,7 +135,7 @@ Message *Message::createTimeoutMessage(const QString &username, const QString &d
|
||||
|
||||
if (reason.length() > 0) {
|
||||
text.append(": \"");
|
||||
text.append(ParseTagString(reason));
|
||||
text.append(util::ParseTagString(reason));
|
||||
text.append("\"");
|
||||
}
|
||||
text.append(".");
|
||||
@@ -216,7 +144,7 @@ Message *Message::createTimeoutMessage(const QString &username, const QString &d
|
||||
text.append(" (multiple times)");
|
||||
}
|
||||
|
||||
Message *message = Message::createSystemMessage(text);
|
||||
MessagePtr message = Message::createSystemMessage(text);
|
||||
message->addFlags(Message::Timeout);
|
||||
message->timeoutUser = username;
|
||||
return message;
|
||||
|
||||
+45
-51
@@ -1,94 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
#include "messages/word.hpp"
|
||||
#include "messages/messageelement.hpp"
|
||||
#include "widgets/helper/scrollbarhighlight.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <cinttypes>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <QTime>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Channel;
|
||||
|
||||
namespace messages {
|
||||
class Message;
|
||||
|
||||
typedef std::shared_ptr<Message> SharedMessage;
|
||||
typedef char MessageFlagsType;
|
||||
typedef std::shared_ptr<Message> MessagePtr;
|
||||
typedef uint8_t MessageFlagsType;
|
||||
|
||||
class Message
|
||||
{
|
||||
public:
|
||||
enum MessageFlags : MessageFlagsType {
|
||||
None = 0,
|
||||
System = (1 << 1),
|
||||
Timeout = (1 << 2),
|
||||
Highlighted = (1 << 3),
|
||||
System = (1 << 0),
|
||||
Timeout = (1 << 1),
|
||||
Highlighted = (1 << 2),
|
||||
DoNotTriggerNotification = (1 << 3), // disable notification sound
|
||||
Centered = (1 << 4),
|
||||
Disabled = (1 << 5),
|
||||
DisableCompactEmotes = (1 << 6),
|
||||
Collapsed = (1 << 7),
|
||||
};
|
||||
|
||||
bool containsHighlightedPhrase() const;
|
||||
void setHighlight(bool value);
|
||||
const QString &getTimeoutUser() const;
|
||||
int getTimeoutCount() const;
|
||||
const QString &getContent() const;
|
||||
const std::chrono::time_point<std::chrono::system_clock> &getParseTime() const;
|
||||
std::vector<Word> &getWords();
|
||||
// Elements
|
||||
// Messages should not be added after the message is done initializing.
|
||||
void addElement(MessageElement *element);
|
||||
const std::vector<std::unique_ptr<MessageElement>> &getElements() const;
|
||||
|
||||
// Message flags
|
||||
MessageFlags getFlags() const;
|
||||
bool hasFlags(MessageFlags flags) const;
|
||||
void setFlags(MessageFlags flags);
|
||||
void addFlags(MessageFlags flags);
|
||||
void removeFlags(MessageFlags flags);
|
||||
bool isDisabled() const;
|
||||
void setDisabled(bool value);
|
||||
|
||||
// Parse Time
|
||||
const QTime &getParseTime() const;
|
||||
|
||||
// Id
|
||||
const QString &getId() const;
|
||||
bool getCollapsedDefault() const;
|
||||
void setCollapsedDefault(bool value);
|
||||
bool getDisableCompactEmotes() const;
|
||||
void setDisableCompactEmotes(bool value);
|
||||
void updateContent() const;
|
||||
void setId(const QString &id);
|
||||
|
||||
// Searching
|
||||
const QString &getSearchText() const;
|
||||
|
||||
// Scrollbar
|
||||
widgets::ScrollbarHighlight getScrollBarHighlight() const;
|
||||
|
||||
// Usernames
|
||||
QString loginName;
|
||||
QString displayName;
|
||||
QString localizedName;
|
||||
QString timeoutUser;
|
||||
|
||||
const QString text;
|
||||
bool centered = false;
|
||||
// Timeouts
|
||||
const QString &getTimeoutUser(const QString &value) const;
|
||||
void setTimeoutUser();
|
||||
|
||||
static Message *createSystemMessage(const QString &text);
|
||||
// Static
|
||||
static MessagePtr createSystemMessage(const QString &text);
|
||||
|
||||
static Message *createTimeoutMessage(const QString &username, const QString &durationInSeconds,
|
||||
const QString &reason, bool multipleTimes);
|
||||
static MessagePtr createTimeoutMessage(const QString &username,
|
||||
const QString &durationInSeconds, const QString &reason,
|
||||
bool multipleTimes);
|
||||
|
||||
private:
|
||||
static LazyLoadedImage *badgeStaff;
|
||||
static LazyLoadedImage *badgeAdmin;
|
||||
static LazyLoadedImage *badgeGlobalmod;
|
||||
static LazyLoadedImage *badgeModerator;
|
||||
static LazyLoadedImage *badgeTurbo;
|
||||
static LazyLoadedImage *badgeBroadcaster;
|
||||
static LazyLoadedImage *badgePremium;
|
||||
|
||||
static QRegularExpression *cheerRegex;
|
||||
|
||||
MessageFlags flags = MessageFlags::None;
|
||||
|
||||
// what is highlightTab?
|
||||
bool highlightTab = false;
|
||||
|
||||
int timeoutCount = 0;
|
||||
bool disabled = false;
|
||||
|
||||
QString timeoutUser;
|
||||
bool collapsedDefault = false;
|
||||
bool disableCompactEmotes = false;
|
||||
|
||||
std::chrono::time_point<std::chrono::system_clock> parseTime;
|
||||
|
||||
mutable QString content;
|
||||
QTime parseTime;
|
||||
mutable QString searchText;
|
||||
QString id = "";
|
||||
|
||||
std::vector<Word> words;
|
||||
std::vector<std::unique_ptr<MessageElement>> elements;
|
||||
};
|
||||
|
||||
} // namespace messages
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "messagebuilder.hpp"
|
||||
#include "singletons/thememanager.hpp"
|
||||
#include "singletons/emotemanager.hpp"
|
||||
#include "singletons/resourcemanager.hpp"
|
||||
#include "singletons/thememanager.hpp"
|
||||
|
||||
#include <QDateTime>
|
||||
|
||||
@@ -11,43 +11,35 @@ namespace messages {
|
||||
MessageBuilder::MessageBuilder()
|
||||
: message(new Message)
|
||||
{
|
||||
_parseTime = std::chrono::system_clock::now();
|
||||
}
|
||||
|
||||
SharedMessage MessageBuilder::getMessage()
|
||||
MessagePtr MessageBuilder::getMessage()
|
||||
{
|
||||
return this->message;
|
||||
}
|
||||
|
||||
void MessageBuilder::appendWord(const Word &&word)
|
||||
void MessageBuilder::appendElement(MessageElement *element)
|
||||
{
|
||||
this->message->getWords().push_back(word);
|
||||
this->message->addElement(element);
|
||||
}
|
||||
|
||||
void MessageBuilder::appendTimestamp()
|
||||
{
|
||||
QDateTime t = QDateTime::currentDateTime();
|
||||
this->appendTimestamp(t);
|
||||
this->appendTimestamp(QTime::currentTime());
|
||||
}
|
||||
|
||||
void MessageBuilder::setHighlight(bool value)
|
||||
{
|
||||
this->message->setHighlight(value);
|
||||
if (value) {
|
||||
this->message->addFlags(Message::Highlighted);
|
||||
} else {
|
||||
this->message->removeFlags(Message::Highlighted);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageBuilder::appendTimestamp(QDateTime &time)
|
||||
void MessageBuilder::appendTimestamp(const QTime &time)
|
||||
{
|
||||
// Add word for timestamp with no seconds
|
||||
QString timestampNoSeconds(time.toString("hh:mm"));
|
||||
this->appendWord(Word(timestampNoSeconds, Word::TimestampNoSeconds,
|
||||
MessageColor(MessageColor::System), singletons::FontManager::Medium, QString(),
|
||||
QString()));
|
||||
|
||||
// Add word for timestamp with seconds
|
||||
QString timestampWithSeconds(time.toString("hh:mm:ss"));
|
||||
this->appendWord(Word(timestampWithSeconds, Word::TimestampWithSeconds,
|
||||
MessageColor(MessageColor::System), singletons::FontManager::Medium, QString(),
|
||||
QString()));
|
||||
this->appendElement(new TimestampElement(time));
|
||||
}
|
||||
|
||||
QString MessageBuilder::matchLink(const QString &string)
|
||||
|
||||
@@ -14,25 +14,32 @@ class MessageBuilder
|
||||
public:
|
||||
MessageBuilder();
|
||||
|
||||
SharedMessage getMessage();
|
||||
MessagePtr getMessage();
|
||||
|
||||
void appendWord(const Word &&word);
|
||||
void appendTimestamp();
|
||||
void appendTimestamp(QDateTime &time);
|
||||
void setHighlight(bool value);
|
||||
void appendElement(MessageElement *element);
|
||||
|
||||
// typename std::enable_if<std::is_base_of<MessageElement, T>::value, T>::type
|
||||
|
||||
template <class T, class... Args>
|
||||
T *append(Args &&... args)
|
||||
{
|
||||
static_assert(std::is_base_of<MessageElement, T>::value, "T must extend MessageElement");
|
||||
|
||||
T *element = new T(std::forward<Args>(args)...);
|
||||
this->appendElement(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
void appendTimestamp();
|
||||
void appendTimestamp(const QTime &time);
|
||||
|
||||
QString matchLink(const QString &string);
|
||||
QRegularExpression linkRegex;
|
||||
|
||||
QString originalMessage;
|
||||
|
||||
protected:
|
||||
std::shared_ptr<messages::Message> message;
|
||||
|
||||
private:
|
||||
std::vector<Word> _words;
|
||||
bool highlight = false;
|
||||
std::chrono::time_point<std::chrono::system_clock> _parseTime;
|
||||
MessagePtr message;
|
||||
};
|
||||
|
||||
} // namespace messages
|
||||
|
||||
@@ -12,8 +12,8 @@ class MessageColor
|
||||
public:
|
||||
enum Type { Custom, Text, Link, System };
|
||||
|
||||
explicit MessageColor(const QColor &color);
|
||||
explicit MessageColor(Type type = Text);
|
||||
MessageColor(const QColor &color);
|
||||
MessageColor(Type type = Text);
|
||||
|
||||
Type getType() const;
|
||||
const QColor &getColor(singletons::ThemeManager &themeManager) const;
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
#include "messages/messageelement.hpp"
|
||||
#include "messages/layouts/messagelayoutcontainer.hpp"
|
||||
#include "messages/layouts/messagelayoutelement.hpp"
|
||||
#include "singletons/settingsmanager.hpp"
|
||||
#include "util/benchmark.hpp"
|
||||
#include "util/emotemap.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
namespace messages {
|
||||
|
||||
MessageElement::MessageElement(Flags _flags)
|
||||
: flags(_flags)
|
||||
{
|
||||
}
|
||||
|
||||
MessageElement *MessageElement::setLink(const Link &_link)
|
||||
{
|
||||
this->link = _link;
|
||||
return this;
|
||||
}
|
||||
|
||||
MessageElement *MessageElement::setTooltip(const QString &_tooltip)
|
||||
{
|
||||
this->tooltip = _tooltip;
|
||||
return this;
|
||||
}
|
||||
|
||||
MessageElement *MessageElement::setTrailingSpace(bool value)
|
||||
{
|
||||
this->trailingSpace = value;
|
||||
}
|
||||
|
||||
const QString &MessageElement::getTooltip() const
|
||||
{
|
||||
return this->tooltip;
|
||||
}
|
||||
|
||||
const Link &MessageElement::getLink() const
|
||||
{
|
||||
return this->link;
|
||||
}
|
||||
|
||||
bool MessageElement::hasTrailingSpace() const
|
||||
{
|
||||
return this->trailingSpace;
|
||||
}
|
||||
|
||||
MessageElement::Flags MessageElement::getFlags() const
|
||||
{
|
||||
return this->flags;
|
||||
}
|
||||
|
||||
// IMAGE
|
||||
ImageElement::ImageElement(Image &_image, MessageElement::Flags flags)
|
||||
: MessageElement(flags)
|
||||
, image(_image)
|
||||
{
|
||||
this->setTooltip(_image.getTooltip());
|
||||
}
|
||||
|
||||
void ImageElement::addToContainer(MessageLayoutContainer &container, MessageElement::Flags _flags)
|
||||
{
|
||||
QSize size(this->image.getWidth() * this->image.getScale() * container.scale,
|
||||
this->image.getHeight() * this->image.getScale() * container.scale);
|
||||
|
||||
container.addElement(new ImageLayoutElement(*this, this->image, size));
|
||||
}
|
||||
|
||||
void ImageElement::update(UpdateFlags _flags)
|
||||
{
|
||||
}
|
||||
|
||||
// EMOTE
|
||||
EmoteElement::EmoteElement(const util::EmoteData &_data, MessageElement::Flags flags)
|
||||
: MessageElement(flags)
|
||||
, data(_data)
|
||||
{
|
||||
if (_data.isValid()) {
|
||||
this->setTooltip(data.image1x->getTooltip());
|
||||
}
|
||||
}
|
||||
|
||||
void EmoteElement::addToContainer(MessageLayoutContainer &container, MessageElement::Flags _flags)
|
||||
{
|
||||
if (!this->data.isValid()) {
|
||||
qDebug() << "EmoteElement::data is invalid xD";
|
||||
return;
|
||||
}
|
||||
|
||||
int quality = singletons::SettingManager::getInstance().preferredEmoteQuality;
|
||||
|
||||
Image *_image;
|
||||
if (quality == 3 && this->data.image3x != nullptr) {
|
||||
_image = this->data.image3x;
|
||||
} else if (quality >= 2 && this->data.image2x != nullptr) {
|
||||
_image = this->data.image2x;
|
||||
} else {
|
||||
_image = this->data.image1x;
|
||||
}
|
||||
|
||||
QSize size((int)(container.scale * _image->getScaledWidth()),
|
||||
(int)(container.scale * _image->getScaledHeight()));
|
||||
|
||||
container.addElement(new ImageLayoutElement(*this, *_image, size));
|
||||
}
|
||||
|
||||
void EmoteElement::update(UpdateFlags _flags)
|
||||
{
|
||||
}
|
||||
|
||||
// TEXT
|
||||
TextElement::TextElement(const QString &text, MessageElement::Flags flags,
|
||||
const MessageColor &_color, FontStyle _style)
|
||||
: MessageElement(flags)
|
||||
, color(_color)
|
||||
, style(_style)
|
||||
{
|
||||
for (QString word : text.split(' ')) {
|
||||
this->words.push_back({word, -1});
|
||||
// fourtf: add logic to store mutliple spaces after message
|
||||
}
|
||||
}
|
||||
|
||||
void TextElement::addToContainer(MessageLayoutContainer &container, MessageElement::Flags _flags)
|
||||
{
|
||||
QFontMetrics &metrics =
|
||||
singletons::FontManager::getInstance().getFontMetrics(this->style, container.scale);
|
||||
singletons::ThemeManager &themeManager = singletons::ThemeManager::ThemeManager::getInstance();
|
||||
|
||||
for (Word &word : this->words) {
|
||||
auto getTextLayoutElement = [&](QString text, int width) {
|
||||
return new TextLayoutElement(*this, text, QSize(width, metrics.height()),
|
||||
this->color.getColor(themeManager), this->style,
|
||||
container.scale);
|
||||
};
|
||||
|
||||
if (word.width == -1) {
|
||||
word.width = metrics.width(word.text);
|
||||
}
|
||||
|
||||
// see if the text fits in the current line
|
||||
if (container.fitsInLine(word.width)) {
|
||||
container.addElementNoLineBreak(getTextLayoutElement(word.text, word.width));
|
||||
continue;
|
||||
}
|
||||
|
||||
// see if the text fits in the next line
|
||||
if (!container.atStartOfLine()) {
|
||||
container.breakLine();
|
||||
|
||||
if (container.fitsInLine(word.width)) {
|
||||
container.addElementNoLineBreak(getTextLayoutElement(word.text, word.width));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// we done goofed, we need to wrap the text
|
||||
QString text = word.text;
|
||||
int textLength = text.length();
|
||||
int wordStart = 0;
|
||||
int width = metrics.width(text[0]);
|
||||
int lastWidth = 0;
|
||||
|
||||
for (int i = 1; i < textLength; i++) {
|
||||
int chatWidth = metrics.width(text[i]);
|
||||
|
||||
if (!container.fitsInLine(width + chatWidth)) {
|
||||
container.addElementNoLineBreak(
|
||||
getTextLayoutElement(text.mid(wordStart, i - wordStart), width - lastWidth));
|
||||
container.breakLine();
|
||||
|
||||
i += 2;
|
||||
wordStart = i;
|
||||
lastWidth = width;
|
||||
width += chatWidth;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
container.addElement(getTextLayoutElement(text.mid(wordStart), word.width - lastWidth));
|
||||
}
|
||||
}
|
||||
|
||||
void TextElement::update(UpdateFlags _flags)
|
||||
{
|
||||
if (_flags & UpdateFlags::Update_Text) {
|
||||
for (Word &word : this->words) {
|
||||
word.width = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TIMESTAMP
|
||||
TimestampElement::TimestampElement()
|
||||
: TimestampElement(QTime::currentTime())
|
||||
{
|
||||
}
|
||||
|
||||
TimestampElement::TimestampElement(QTime _time)
|
||||
: MessageElement(MessageElement::Timestamp)
|
||||
, time(_time)
|
||||
, element(formatTime(_time))
|
||||
{
|
||||
assert(this->element != nullptr);
|
||||
}
|
||||
|
||||
TimestampElement::~TimestampElement()
|
||||
{
|
||||
delete this->element;
|
||||
}
|
||||
|
||||
void TimestampElement::addToContainer(MessageLayoutContainer &container,
|
||||
MessageElement::Flags _flags)
|
||||
{
|
||||
this->element->addToContainer(container, _flags);
|
||||
}
|
||||
|
||||
void TimestampElement::update(UpdateFlags _flags)
|
||||
{
|
||||
if (_flags == UpdateFlags::Update_All) {
|
||||
this->element = TimestampElement::formatTime(this->time);
|
||||
} else {
|
||||
this->element->update(_flags);
|
||||
}
|
||||
}
|
||||
|
||||
TextElement *TimestampElement::formatTime(const QTime &time)
|
||||
{
|
||||
QString text = time.toString(singletons::SettingManager::getInstance().timestampFormat);
|
||||
|
||||
return new TextElement(text, Flags::Timestamp, MessageColor::System, FontStyle::Medium);
|
||||
}
|
||||
|
||||
// TWITCH MODERATION
|
||||
TwitchModerationElement::TwitchModerationElement()
|
||||
: MessageElement(MessageElement::ModeratorTools)
|
||||
{
|
||||
}
|
||||
|
||||
void TwitchModerationElement::addToContainer(MessageLayoutContainer &container,
|
||||
MessageElement::Flags _flags)
|
||||
{
|
||||
}
|
||||
|
||||
void TwitchModerationElement::update(UpdateFlags _flags)
|
||||
{
|
||||
}
|
||||
} // namespace messages
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,224 @@
|
||||
#pragma once
|
||||
|
||||
#include "messages/image.hpp"
|
||||
#include "messages/link.hpp"
|
||||
#include "messages/messagecolor.hpp"
|
||||
#include "singletons/fontmanager.hpp"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <QRect>
|
||||
#include <QString>
|
||||
#include <QTime>
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <util/emotemap.hpp>
|
||||
|
||||
namespace chatterino {
|
||||
class Channel;
|
||||
namespace util {
|
||||
struct EmoteData;
|
||||
}
|
||||
namespace messages {
|
||||
namespace layouts {
|
||||
class MessageLayoutContainer;
|
||||
}
|
||||
|
||||
using namespace chatterino::messages::layouts;
|
||||
|
||||
class MessageElement : boost::noncopyable
|
||||
{
|
||||
public:
|
||||
enum Flags : uint32_t {
|
||||
None = 0,
|
||||
Misc = (1 << 0),
|
||||
Text = (1 << 1),
|
||||
|
||||
Username = (1 << 2),
|
||||
Timestamp = (1 << 3),
|
||||
|
||||
TwitchEmoteImage = (1 << 4),
|
||||
TwitchEmoteText = (1 << 5),
|
||||
TwitchEmote = TwitchEmoteImage | TwitchEmoteText,
|
||||
BttvEmoteImage = (1 << 6),
|
||||
BttvEmoteText = (1 << 7),
|
||||
BttvEmote = BttvEmoteImage | BttvEmoteText,
|
||||
FfzEmoteImage = (1 << 10),
|
||||
FfzEmoteText = (1 << 11),
|
||||
FfzEmote = FfzEmoteImage | FfzEmoteText,
|
||||
EmoteImages = TwitchEmoteImage | BttvEmoteImage | FfzEmoteImage,
|
||||
|
||||
BitsStatic = (1 << 12),
|
||||
BitsAnimated = (1 << 13),
|
||||
|
||||
// Slot 1: Twitch
|
||||
// - Staff badge
|
||||
// - Admin badge
|
||||
// - Global Moderator badge
|
||||
BadgeGlobalAuthority = (1 << 14),
|
||||
|
||||
// Slot 2: Twitch
|
||||
// - Moderator badge
|
||||
// - Broadcaster badge
|
||||
BadgeChannelAuthority = (1 << 15),
|
||||
|
||||
// Slot 3: Twitch
|
||||
// - Subscription badges
|
||||
BadgeSubscription = (1 << 16),
|
||||
|
||||
// Slot 4: Twitch
|
||||
// - Turbo badge
|
||||
// - Prime badge
|
||||
// - Bit badges
|
||||
// - Game badges
|
||||
BadgeVanity = (1 << 17),
|
||||
|
||||
// Slot 5: Chatterino
|
||||
// - Chatterino developer badge
|
||||
// - Chatterino donator badge
|
||||
// - Chatterino top donator badge
|
||||
BadgeChatterino = (1 << 18),
|
||||
|
||||
// Rest of slots: ffz custom badge? bttv custom badge? mywaifu (puke) custom badge?
|
||||
|
||||
Badges = BadgeGlobalAuthority | BadgeChannelAuthority | BadgeSubscription | BadgeVanity |
|
||||
BadgeChatterino,
|
||||
|
||||
ChannelName = (1 << 19),
|
||||
|
||||
BitsAmount = (1 << 20),
|
||||
|
||||
ModeratorTools = (1 << 21),
|
||||
|
||||
EmojiImage = (1 << 23),
|
||||
EmojiText = (1 << 24),
|
||||
EmojiAll = EmojiImage | EmojiText,
|
||||
|
||||
AlwaysShow = (1 << 25),
|
||||
|
||||
// used in the ChannelView class to make the collapse buttons visible if needed
|
||||
Collapsed = (1 << 26),
|
||||
|
||||
Default = Timestamp | Badges | Username | BitsStatic | FfzEmoteImage | BttvEmoteImage |
|
||||
TwitchEmoteImage | BitsAmount | Text | AlwaysShow,
|
||||
};
|
||||
|
||||
enum UpdateFlags : char {
|
||||
Update_Text,
|
||||
Update_Emotes,
|
||||
Update_Images,
|
||||
Update_All = Update_Text | Update_Emotes | Update_Images
|
||||
};
|
||||
|
||||
virtual ~MessageElement()
|
||||
{
|
||||
}
|
||||
|
||||
MessageElement *setLink(const Link &link);
|
||||
MessageElement *setTooltip(const QString &tooltip);
|
||||
MessageElement *setTrailingSpace(bool value);
|
||||
const QString &getTooltip() const;
|
||||
const Link &getLink() const;
|
||||
bool hasTrailingSpace() const;
|
||||
Flags getFlags() const;
|
||||
|
||||
virtual void addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags) = 0;
|
||||
virtual void update(UpdateFlags flags) = 0;
|
||||
|
||||
protected:
|
||||
MessageElement(Flags flags);
|
||||
bool trailingSpace = true;
|
||||
|
||||
private:
|
||||
Link link;
|
||||
QString tooltip;
|
||||
Flags flags;
|
||||
};
|
||||
|
||||
// contains a simple image
|
||||
class ImageElement : public MessageElement
|
||||
{
|
||||
Image ℑ
|
||||
|
||||
public:
|
||||
ImageElement(Image &image, MessageElement::Flags flags);
|
||||
|
||||
virtual void addToContainer(MessageLayoutContainer &container,
|
||||
MessageElement::Flags flags) override;
|
||||
virtual void update(UpdateFlags flags) override;
|
||||
};
|
||||
|
||||
// contains emote data and will pick the emote based on :
|
||||
// a) are images for the emote type enabled
|
||||
// b) which size it wants
|
||||
class EmoteElement : public MessageElement
|
||||
{
|
||||
const util::EmoteData data;
|
||||
|
||||
public:
|
||||
EmoteElement(const util::EmoteData &data, MessageElement::Flags flags);
|
||||
|
||||
virtual void addToContainer(MessageLayoutContainer &container,
|
||||
MessageElement::Flags flags) override;
|
||||
virtual void update(UpdateFlags flags) override;
|
||||
};
|
||||
|
||||
// contains a text, it will split it into words
|
||||
class TextElement : public MessageElement
|
||||
{
|
||||
MessageColor color;
|
||||
FontStyle style;
|
||||
|
||||
struct Word {
|
||||
QString text;
|
||||
int width = -1;
|
||||
};
|
||||
std::vector<Word> words;
|
||||
|
||||
public:
|
||||
TextElement(const QString &text, MessageElement::Flags flags,
|
||||
const MessageColor &color = MessageColor::Text,
|
||||
FontStyle style = FontStyle::Medium);
|
||||
|
||||
virtual void addToContainer(MessageLayoutContainer &container,
|
||||
MessageElement::Flags flags) override;
|
||||
virtual void update(UpdateFlags flags);
|
||||
};
|
||||
|
||||
// contains a text, formated depending on the preferences
|
||||
class TimestampElement : public MessageElement
|
||||
{
|
||||
QTime time;
|
||||
TextElement *element;
|
||||
|
||||
public:
|
||||
TimestampElement();
|
||||
TimestampElement(QTime time);
|
||||
virtual ~TimestampElement();
|
||||
|
||||
virtual void addToContainer(MessageLayoutContainer &container,
|
||||
MessageElement::Flags flags) override;
|
||||
virtual void update(UpdateFlags flags);
|
||||
|
||||
TextElement *formatTime(const QTime &time);
|
||||
};
|
||||
|
||||
// adds all the custom moderation buttons, adds a variable amount of items depending on settings
|
||||
// fourtf: implement
|
||||
class TwitchModerationElement : public MessageElement
|
||||
{
|
||||
public:
|
||||
TwitchModerationElement();
|
||||
|
||||
virtual void addToContainer(MessageLayoutContainer &container,
|
||||
MessageElement::Flags flags) override;
|
||||
virtual void update(UpdateFlags flags);
|
||||
};
|
||||
|
||||
// adds bits as text, static image or animated image
|
||||
// class BitsElement : public MessageElement
|
||||
//{
|
||||
// public:
|
||||
// virtual void addToContainer(LayoutContainer &container) override;
|
||||
//};
|
||||
} // namespace messages
|
||||
} // namespace chatterino
|
||||
@@ -1,450 +0,0 @@
|
||||
#include "messages/messageref.hpp"
|
||||
#include "singletons/emotemanager.hpp"
|
||||
#include "singletons/settingsmanager.hpp"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#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
|
||||
|
||||
using namespace chatterino::messages;
|
||||
|
||||
namespace chatterino {
|
||||
namespace messages {
|
||||
|
||||
MessageRef::MessageRef(SharedMessage _message)
|
||||
: message(_message)
|
||||
, wordParts()
|
||||
, collapsed(_message->getCollapsedDefault())
|
||||
{
|
||||
}
|
||||
|
||||
Message *MessageRef::getMessage()
|
||||
{
|
||||
return this->message.get();
|
||||
}
|
||||
|
||||
int MessageRef::getHeight() const
|
||||
{
|
||||
return this->height;
|
||||
}
|
||||
|
||||
// return true if redraw is required
|
||||
bool MessageRef::layout(int width, float scale)
|
||||
{
|
||||
auto &emoteManager = singletons::EmoteManager::getInstance();
|
||||
|
||||
bool rebuildRequired = false, 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) {
|
||||
this->updateImageSizes();
|
||||
this->buffer = nullptr;
|
||||
}
|
||||
if (textChanged) {
|
||||
this->updateTextSizes();
|
||||
this->buffer = nullptr;
|
||||
}
|
||||
if (widthChanged || wordMaskChanged) {
|
||||
this->buffer = nullptr;
|
||||
}
|
||||
|
||||
// return if no layout is required
|
||||
if (!layoutRequired) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this->actuallyLayout(width);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MessageRef::actuallyLayout(int width)
|
||||
{
|
||||
auto &settings = singletons::SettingManager::getInstance();
|
||||
|
||||
const int spaceWidth = 4;
|
||||
const int right = width - MARGIN_RIGHT;
|
||||
|
||||
bool compactEmotes = !this->getMessage()->getDisableCompactEmotes();
|
||||
|
||||
// clear word parts
|
||||
this->wordParts.clear();
|
||||
|
||||
// layout
|
||||
int x = MARGIN_LEFT;
|
||||
int y = MARGIN_TOP;
|
||||
|
||||
int lineNumber = 0;
|
||||
int lineStart = 0;
|
||||
int lineHeight = 0;
|
||||
int firstLineHeight = -1;
|
||||
bool first = true;
|
||||
|
||||
uint32_t flags = settings.getWordTypeMask();
|
||||
if (this->collapsed) {
|
||||
flags |= Word::Collapsed;
|
||||
}
|
||||
|
||||
// loop throught all the words and add them when a line is full
|
||||
for (auto it = this->message->getWords().begin(); it != this->message->getWords().end(); ++it) {
|
||||
Word &word = *it;
|
||||
|
||||
// Check if given word is supposed to be rendered by comparing it to the current setting
|
||||
if ((word.getFlags() & flags) == Word::None) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int xOffset = 0, yOffset = 0;
|
||||
|
||||
/// if (enableEmoteMargins) {
|
||||
/// if (word.isImage() && word.getImage().isHat()) {
|
||||
/// xOffset = -word.getWidth() + 2;
|
||||
/// } else {
|
||||
xOffset = word.getXOffset();
|
||||
yOffset = word.getYOffset();
|
||||
/// }
|
||||
/// }
|
||||
|
||||
// word wrapping
|
||||
if (word.isText() && word.getWidth(this->scale) + MARGIN_LEFT > right) {
|
||||
// align and end the current line
|
||||
this->_alignWordParts(lineStart, lineHeight, width, firstLineHeight);
|
||||
y += lineHeight;
|
||||
|
||||
int currentPartStart = 0;
|
||||
int currentLineWidth = 0;
|
||||
|
||||
// go through the text, break text when it doesn't fit in the line anymore
|
||||
for (int i = 1; i <= word.getText().length(); i++) {
|
||||
currentLineWidth += word.getCharWidth(i - 1, this->scale);
|
||||
|
||||
if (currentLineWidth + MARGIN_LEFT > right) {
|
||||
// add the current line
|
||||
QString mid = word.getText().mid(currentPartStart, i - currentPartStart - 1);
|
||||
|
||||
this->wordParts.push_back(WordPart(word, MARGIN_LEFT, y, currentLineWidth,
|
||||
word.getHeight(this->scale), lineNumber, mid,
|
||||
mid, false, currentPartStart));
|
||||
|
||||
y += word.getFontMetrics(this->scale).height();
|
||||
|
||||
currentPartStart = i - 1;
|
||||
|
||||
currentLineWidth = 0;
|
||||
lineNumber++;
|
||||
}
|
||||
}
|
||||
|
||||
QString mid(word.getText().mid(currentPartStart));
|
||||
currentLineWidth = word.getFontMetrics(this->scale).width(mid);
|
||||
|
||||
this->wordParts.push_back(WordPart(word, MARGIN_LEFT, y - word.getHeight(this->scale),
|
||||
currentLineWidth, word.getHeight(this->scale),
|
||||
lineNumber, mid, mid, true, currentPartStart));
|
||||
|
||||
x = currentLineWidth + MARGIN_LEFT + spaceWidth;
|
||||
lineHeight = this->_updateLineHeight(0, word, compactEmotes);
|
||||
lineStart = this->wordParts.size() - 1;
|
||||
}
|
||||
// fits in the current line
|
||||
else if (first || x + word.getWidth(this->scale) + xOffset <= right) {
|
||||
this->wordParts.push_back(WordPart(word, x, y - word.getHeight(this->scale), scale,
|
||||
lineNumber, word.getCopyText()));
|
||||
|
||||
x += word.getWidth(this->scale) + xOffset;
|
||||
x += spaceWidth;
|
||||
|
||||
lineHeight = this->_updateLineHeight(lineHeight, word, compactEmotes);
|
||||
}
|
||||
// doesn't fit in the line
|
||||
else {
|
||||
// align and end the current line
|
||||
this->_alignWordParts(lineStart, lineHeight, width, firstLineHeight);
|
||||
|
||||
y += lineHeight;
|
||||
|
||||
lineNumber++;
|
||||
|
||||
this->wordParts.push_back(WordPart(word, MARGIN_LEFT, y - word.getHeight(this->scale),
|
||||
this->scale, lineNumber, word.getCopyText()));
|
||||
|
||||
lineStart = this->wordParts.size() - 1;
|
||||
|
||||
lineHeight = this->_updateLineHeight(0, word, compactEmotes);
|
||||
|
||||
x = word.getWidth(this->scale) + MARGIN_LEFT;
|
||||
x += spaceWidth;
|
||||
}
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
// align and end the current line
|
||||
this->_alignWordParts(lineStart, lineHeight, width, firstLineHeight);
|
||||
|
||||
this->collapsedHeight = firstLineHeight == -1 ? (int)(24 * this->scale)
|
||||
: firstLineHeight + MARGIN_TOP + MARGIN_BOTTOM;
|
||||
|
||||
// update height
|
||||
int oldHeight = this->height;
|
||||
|
||||
if (this->isCollapsed()) {
|
||||
this->height = this->collapsedHeight;
|
||||
} else {
|
||||
this->height = y + lineHeight + MARGIN_BOTTOM;
|
||||
}
|
||||
|
||||
// invalidate buffer if height changed
|
||||
if (oldHeight != this->height) {
|
||||
this->buffer = nullptr;
|
||||
}
|
||||
|
||||
updateBuffer = true;
|
||||
}
|
||||
|
||||
void MessageRef::updateTextSizes()
|
||||
{
|
||||
for (auto &word : this->message->getWords()) {
|
||||
if (!word.isText()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
word.updateSize();
|
||||
}
|
||||
}
|
||||
|
||||
void MessageRef::updateImageSizes()
|
||||
{
|
||||
for (auto &word : this->message->getWords()) {
|
||||
if (!word.isImage())
|
||||
continue;
|
||||
|
||||
word.updateSize();
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<WordPart> &MessageRef::getWordParts() const
|
||||
{
|
||||
return this->wordParts;
|
||||
}
|
||||
|
||||
void MessageRef::_alignWordParts(int lineStart, int lineHeight, int width, int &firstLineHeight)
|
||||
{
|
||||
bool compactEmotes = true;
|
||||
|
||||
if (firstLineHeight == -1) {
|
||||
firstLineHeight = lineHeight;
|
||||
}
|
||||
|
||||
int xOffset = 0;
|
||||
|
||||
if (this->message->centered && this->wordParts.size() > 0) {
|
||||
xOffset = (width - this->wordParts.at(this->wordParts.size() - 1).getRight()) / 2;
|
||||
}
|
||||
|
||||
for (size_t i = lineStart; i < this->wordParts.size(); i++) {
|
||||
WordPart &wordPart = this->wordParts.at(i);
|
||||
|
||||
const bool isCompactEmote = compactEmotes && wordPart.getWord().isImage() &&
|
||||
wordPart.getWord().getFlags() & Word::EmoteImages;
|
||||
|
||||
int yExtra = 0;
|
||||
if (isCompactEmote) {
|
||||
yExtra = (COMPACT_EMOTES_OFFSET / 2) * this->scale;
|
||||
}
|
||||
|
||||
wordPart.setPosition(wordPart.getX() + xOffset, wordPart.getY() + lineHeight + yExtra);
|
||||
}
|
||||
}
|
||||
|
||||
int MessageRef::_updateLineHeight(int currentLineHeight, Word &word, bool compactEmotes)
|
||||
{
|
||||
int newLineHeight = word.getHeight(this->scale);
|
||||
|
||||
// fourtf: doesn't care about the height of a normal line
|
||||
if (compactEmotes && word.isImage() && word.getFlags() & Word::EmoteImages) {
|
||||
newLineHeight -= COMPACT_EMOTES_OFFSET * this->scale;
|
||||
}
|
||||
|
||||
return std::max(currentLineHeight, newLineHeight);
|
||||
}
|
||||
|
||||
const Word *MessageRef::tryGetWordPart(QPoint point)
|
||||
{
|
||||
// go through all words and return the first one that contains the point.
|
||||
for (WordPart &wordPart : this->wordParts) {
|
||||
if (wordPart.getRect().contains(point)) {
|
||||
return &wordPart.getWord();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// XXX(pajlada): This is probably not the optimal way to calculate this
|
||||
int MessageRef::getLastCharacterIndex() const
|
||||
{
|
||||
// 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 WordPart &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 WordPart &part = this->wordParts[i];
|
||||
|
||||
index += part.getWord().isImage() ? 2 : part.getText().length() + 1;
|
||||
}
|
||||
|
||||
for (int i = lineStart; i < lineEnd; i++) {
|
||||
const WordPart &part = this->wordParts[i];
|
||||
|
||||
index += part.getCharacterLength();
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
int MessageRef::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++) {
|
||||
WordPart &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++) {
|
||||
WordPart &part = this->wordParts[i];
|
||||
|
||||
index += part.getWord().isImage() ? 2 : part.getText().length() + 1;
|
||||
}
|
||||
|
||||
for (int i = lineStart; i < lineEnd; i++) {
|
||||
WordPart &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;
|
||||
}
|
||||
|
||||
bool MessageRef::isCollapsed() const
|
||||
{
|
||||
return this->collapsed;
|
||||
}
|
||||
|
||||
void MessageRef::setCollapsed(bool value)
|
||||
{
|
||||
if (this->collapsed != value) {
|
||||
this->currentLayoutWidth = 0;
|
||||
this->collapsed = value;
|
||||
}
|
||||
}
|
||||
|
||||
int MessageRef::getCollapsedHeight() const
|
||||
{
|
||||
return this->collapsedHeight;
|
||||
}
|
||||
|
||||
bool MessageRef::isDisabled() const
|
||||
{
|
||||
return this->message->isDisabled();
|
||||
}
|
||||
} // namespace messages
|
||||
} // namespace chatterino
|
||||
@@ -1,70 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "messages/message.hpp"
|
||||
#include "messages/wordpart.hpp"
|
||||
|
||||
#include <QPixmap>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace chatterino {
|
||||
namespace messages {
|
||||
|
||||
class MessageRef;
|
||||
|
||||
typedef std::shared_ptr<MessageRef> SharedMessageRef;
|
||||
|
||||
class MessageRef
|
||||
{
|
||||
public:
|
||||
MessageRef(SharedMessage message);
|
||||
|
||||
Message *getMessage();
|
||||
int getHeight() const;
|
||||
|
||||
bool layout(int width, float scale);
|
||||
|
||||
const std::vector<WordPart> &getWordParts() const;
|
||||
|
||||
std::shared_ptr<QPixmap> buffer = nullptr;
|
||||
bool updateBuffer = false;
|
||||
|
||||
const Word *tryGetWordPart(QPoint point);
|
||||
int getLastCharacterIndex() const;
|
||||
int getSelectionIndex(QPoint position);
|
||||
|
||||
bool isCollapsed() const;
|
||||
void setCollapsed(bool value);
|
||||
int getCollapsedHeight() const;
|
||||
int getCollapsedLineCount() const;
|
||||
|
||||
bool isDisabled() const;
|
||||
|
||||
private:
|
||||
// variables
|
||||
SharedMessage message;
|
||||
std::vector<WordPart> wordParts;
|
||||
|
||||
int height = 0;
|
||||
|
||||
int currentLayoutWidth = -1;
|
||||
int fontGeneration = -1;
|
||||
int emoteGeneration = -1;
|
||||
float scale = -1;
|
||||
|
||||
Word::Flags currentWordTypes = Word::None;
|
||||
|
||||
bool collapsed;
|
||||
int collapsedHeight = 32;
|
||||
|
||||
// methods
|
||||
void rebuild();
|
||||
void actuallyLayout(int width);
|
||||
void _alignWordParts(int lineStart, int lineHeight, int width, int &firstLineHeight);
|
||||
int _updateLineHeight(int currentLineHeight, Word &word, bool overlapEmotes);
|
||||
void updateTextSizes();
|
||||
void updateImageSizes();
|
||||
};
|
||||
|
||||
} // namespace messages
|
||||
} // namespace chatterino
|
||||
@@ -1,207 +0,0 @@
|
||||
#include "messages/word.hpp"
|
||||
#include "singletons/settingsmanager.hpp"
|
||||
#include "util/benchmark.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
namespace messages {
|
||||
|
||||
// Image word
|
||||
Word::Word(LazyLoadedImage *image, Flags type, const QString ©text, const QString &tooltip,
|
||||
const Link &link)
|
||||
: image(image)
|
||||
, _isImage(true)
|
||||
, type(type)
|
||||
, copyText(copytext)
|
||||
, tooltip(tooltip)
|
||||
, link(link)
|
||||
{
|
||||
}
|
||||
|
||||
// Text word
|
||||
Word::Word(const QString &text, Flags type, const MessageColor &color,
|
||||
singletons::FontManager::Type font, const QString ©text, const QString &tooltip,
|
||||
const Link &link)
|
||||
: image(nullptr)
|
||||
, text(text)
|
||||
, color(color)
|
||||
, _isImage(false)
|
||||
, type(type)
|
||||
, copyText(copytext)
|
||||
, tooltip(tooltip)
|
||||
, font(font)
|
||||
, link(link)
|
||||
{
|
||||
}
|
||||
|
||||
LazyLoadedImage &Word::getImage() const
|
||||
{
|
||||
return *this->image;
|
||||
}
|
||||
|
||||
const QString &Word::getText() const
|
||||
{
|
||||
return this->text;
|
||||
}
|
||||
|
||||
int Word::getWidth(float scale) const
|
||||
{
|
||||
return this->getSize(scale).width();
|
||||
}
|
||||
|
||||
int Word::getHeight(float scale) const
|
||||
{
|
||||
return this->getSize(scale).height();
|
||||
}
|
||||
|
||||
QSize Word::getSize(float scale) const
|
||||
{
|
||||
auto &data = this->getDataByScale(scale);
|
||||
|
||||
if (data.size.isEmpty()) {
|
||||
// no size found
|
||||
if (this->isText()) {
|
||||
QFontMetrics &metrics = this->getFontMetrics(scale);
|
||||
data.size.setWidth((int)(metrics.width(this->getText())));
|
||||
data.size.setHeight((int)(metrics.height()));
|
||||
} else {
|
||||
const int mediumTextLineHeight =
|
||||
singletons::FontManager::getInstance().getFontMetrics(this->font, scale).height();
|
||||
const qreal emoteScale =
|
||||
singletons::SettingManager::getInstance().emoteScale.getValue() * scale;
|
||||
const bool scaleEmotesByLineHeight =
|
||||
singletons::SettingManager::getInstance().scaleEmotesByLineHeight;
|
||||
|
||||
auto &image = this->getImage();
|
||||
|
||||
qreal w = image.getWidth();
|
||||
qreal h = image.getHeight();
|
||||
|
||||
if (scaleEmotesByLineHeight) {
|
||||
data.size.setWidth(w * mediumTextLineHeight / h * emoteScale);
|
||||
data.size.setHeight(mediumTextLineHeight * emoteScale);
|
||||
} else {
|
||||
data.size.setWidth(w * image.getScale() * emoteScale);
|
||||
data.size.setHeight(h * image.getScale() * emoteScale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data.size;
|
||||
}
|
||||
|
||||
void Word::updateSize()
|
||||
{
|
||||
this->dataByScale.clear();
|
||||
}
|
||||
|
||||
bool Word::isImage() const
|
||||
{
|
||||
return this->_isImage;
|
||||
}
|
||||
|
||||
bool Word::isText() const
|
||||
{
|
||||
return !this->_isImage;
|
||||
}
|
||||
|
||||
const QString &Word::getCopyText() const
|
||||
{
|
||||
return this->copyText;
|
||||
}
|
||||
|
||||
bool Word::hasTrailingSpace() const
|
||||
{
|
||||
return this->_hasTrailingSpace;
|
||||
}
|
||||
|
||||
QFont &Word::getFont(float scale) const
|
||||
{
|
||||
return singletons::FontManager::getInstance().getFont(this->font, scale);
|
||||
}
|
||||
|
||||
QFontMetrics &Word::getFontMetrics(float scale) const
|
||||
{
|
||||
return singletons::FontManager::getInstance().getFontMetrics(this->font, scale);
|
||||
}
|
||||
|
||||
Word::Flags Word::getFlags() const
|
||||
{
|
||||
return this->type;
|
||||
}
|
||||
|
||||
const QString &Word::getTooltip() const
|
||||
{
|
||||
return this->tooltip;
|
||||
}
|
||||
|
||||
const MessageColor &Word::getTextColor() const
|
||||
{
|
||||
return this->color;
|
||||
}
|
||||
|
||||
const Link &Word::getLink() const
|
||||
{
|
||||
return this->link;
|
||||
}
|
||||
|
||||
int Word::getXOffset() const
|
||||
{
|
||||
return this->xOffset;
|
||||
}
|
||||
|
||||
int Word::getYOffset() const
|
||||
{
|
||||
return this->yOffset;
|
||||
}
|
||||
|
||||
void Word::setOffset(int xOffset, int yOffset)
|
||||
{
|
||||
this->xOffset = std::max(0, xOffset);
|
||||
this->yOffset = std::max(0, yOffset);
|
||||
}
|
||||
|
||||
int Word::getCharacterLength() const
|
||||
{
|
||||
return this->isImage() ? 2 : this->getText().length() + 1;
|
||||
}
|
||||
|
||||
short Word::getCharWidth(int index, float scale) const
|
||||
{
|
||||
return this->getCharacterWidthCache(scale).at(index);
|
||||
}
|
||||
|
||||
std::vector<short> &Word::getCharacterWidthCache(float scale) const
|
||||
{
|
||||
auto &data = this->getDataByScale(scale);
|
||||
|
||||
// lock not required because there is only one gui thread
|
||||
// std::lock_guard<std::mutex> lock(this->charWidthCacheMutex);
|
||||
|
||||
if (data.charWidthCache.size() == 0 && this->isText()) {
|
||||
for (int i = 0; i < this->getText().length(); i++) {
|
||||
data.charWidthCache.push_back(
|
||||
this->getFontMetrics(scale).charWidth(this->getText(), i));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: on font change
|
||||
return data.charWidthCache;
|
||||
}
|
||||
|
||||
Word::ScaleDependantData &Word::getDataByScale(float scale) const
|
||||
{
|
||||
// try to find and return data for scale
|
||||
for (auto it = this->dataByScale.begin(); it != this->dataByScale.end(); it++) {
|
||||
if (it->scale == scale) {
|
||||
return *it;
|
||||
}
|
||||
}
|
||||
|
||||
// create new data element and return that
|
||||
this->dataByScale.emplace_back(scale);
|
||||
|
||||
return this->dataByScale.back();
|
||||
}
|
||||
|
||||
} // namespace messages
|
||||
} // namespace chatterino
|
||||
@@ -1,163 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "messages/lazyloadedimage.hpp"
|
||||
#include "messages/link.hpp"
|
||||
#include "messages/messagecolor.hpp"
|
||||
#include "singletons/fontmanager.hpp"
|
||||
//#include "wordflags.hpp"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <QRect>
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
namespace messages {
|
||||
|
||||
class Word
|
||||
{
|
||||
public:
|
||||
enum Flags : uint32_t {
|
||||
None = 0,
|
||||
Misc = (1 << 0),
|
||||
Text = (1 << 1),
|
||||
|
||||
TimestampNoSeconds = (1 << 2),
|
||||
TimestampWithSeconds = (1 << 3),
|
||||
|
||||
TwitchEmoteImage = (1 << 4),
|
||||
TwitchEmoteText = (1 << 5),
|
||||
BttvEmoteImage = (1 << 6),
|
||||
BttvEmoteText = (1 << 7),
|
||||
BttvGifEmoteImage = (1 << 8),
|
||||
BttvGifEmoteText = (1 << 9),
|
||||
FfzEmoteImage = (1 << 10),
|
||||
FfzEmoteText = (1 << 11),
|
||||
EmoteImages = TwitchEmoteImage | BttvEmoteImage | BttvGifEmoteImage | FfzEmoteImage,
|
||||
|
||||
BitsStatic = (1 << 12),
|
||||
BitsAnimated = (1 << 13),
|
||||
|
||||
// Slot 1: Twitch
|
||||
// - Staff badge
|
||||
// - Admin badge
|
||||
// - Global Moderator badge
|
||||
BadgeGlobalAuthority = (1 << 14),
|
||||
|
||||
// Slot 2: Twitch
|
||||
// - Moderator badge
|
||||
// - Broadcaster badge
|
||||
BadgeChannelAuthority = (1 << 15),
|
||||
|
||||
// Slot 3: Twitch
|
||||
// - Subscription badges
|
||||
BadgeSubscription = (1 << 16),
|
||||
|
||||
// Slot 4: Twitch
|
||||
// - Turbo badge
|
||||
// - Prime badge
|
||||
// - Bit badges
|
||||
// - Game badges
|
||||
BadgeVanity = (1 << 17),
|
||||
|
||||
// Slot 5: Chatterino
|
||||
// - Chatterino developer badge
|
||||
// - Chatterino donator badge
|
||||
// - Chatterino top donator badge
|
||||
BadgeChatterino = (1 << 18),
|
||||
|
||||
// Rest of slots: ffz custom badge? bttv custom badge? mywaifu (puke) custom badge?
|
||||
|
||||
Badges = BadgeGlobalAuthority | BadgeChannelAuthority | BadgeSubscription | BadgeVanity |
|
||||
BadgeChatterino,
|
||||
|
||||
Username = (1 << 19),
|
||||
BitsAmount = (1 << 20),
|
||||
|
||||
ButtonBan = (1 << 21),
|
||||
ButtonTimeout = (1 << 22),
|
||||
|
||||
EmojiImage = (1 << 23),
|
||||
EmojiText = (1 << 24),
|
||||
|
||||
AlwaysShow = (1 << 25),
|
||||
|
||||
// used in the ChannelView class to make the collapse buttons visible if needed
|
||||
Collapsed = (1 << 26),
|
||||
|
||||
Default = TimestampNoSeconds | Badges | Username | BitsStatic | FfzEmoteImage |
|
||||
BttvEmoteImage | BttvGifEmoteImage | TwitchEmoteImage | BitsAmount | Text |
|
||||
ButtonBan | ButtonTimeout | AlwaysShow
|
||||
};
|
||||
|
||||
Word()
|
||||
{
|
||||
}
|
||||
|
||||
explicit Word(LazyLoadedImage *_image, Flags getFlags, const QString ©text,
|
||||
const QString &tooltip, const Link &getLink = Link());
|
||||
explicit Word(const QString &_text, Flags getFlags, const MessageColor &textColor,
|
||||
singletons::FontManager::Type font, const QString ©text,
|
||||
const QString &tooltip, const Link &getLink = Link());
|
||||
|
||||
bool isImage() const;
|
||||
bool isText() const;
|
||||
|
||||
LazyLoadedImage &getImage() const;
|
||||
const QString &getText() const;
|
||||
const QString &getCopyText() const;
|
||||
bool hasTrailingSpace() const;
|
||||
Flags getFlags() const;
|
||||
const QString &getTooltip() const;
|
||||
const MessageColor &getTextColor() const;
|
||||
const Link &getLink() const;
|
||||
int getXOffset() const;
|
||||
int getYOffset() const;
|
||||
void setOffset(int _xOffset, int _yOffset);
|
||||
int getCharacterLength() const;
|
||||
|
||||
void updateSize();
|
||||
|
||||
QFont &getFont(float scale) const;
|
||||
QFontMetrics &getFontMetrics(float scale) const;
|
||||
int getWidth(float scale) const;
|
||||
int getHeight(float scale) const;
|
||||
QSize getSize(float scale) const;
|
||||
short getCharWidth(int index, float scale) const;
|
||||
|
||||
private:
|
||||
LazyLoadedImage *image;
|
||||
QString text;
|
||||
MessageColor color;
|
||||
bool _isImage;
|
||||
|
||||
Flags type;
|
||||
QString copyText;
|
||||
QString tooltip;
|
||||
|
||||
int xOffset = 0;
|
||||
int yOffset = 0;
|
||||
|
||||
bool _hasTrailingSpace = true;
|
||||
singletons::FontManager::Type font = singletons::FontManager::Medium;
|
||||
Link link;
|
||||
|
||||
struct ScaleDependantData {
|
||||
float scale;
|
||||
QSize size;
|
||||
mutable std::vector<short> charWidthCache;
|
||||
|
||||
ScaleDependantData(float _scale)
|
||||
: scale(_scale)
|
||||
, size()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
mutable std::list<ScaleDependantData> dataByScale;
|
||||
|
||||
inline ScaleDependantData &getDataByScale(float scale) const;
|
||||
std::vector<short> &getCharacterWidthCache(float scale) const;
|
||||
};
|
||||
|
||||
} // namespace messages
|
||||
} // namespace chatterino
|
||||
@@ -1,123 +0,0 @@
|
||||
#include "messages/wordpart.hpp"
|
||||
#include "messages/word.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
namespace messages {
|
||||
|
||||
WordPart::WordPart(Word &_word, int _x, int _y, float scale, int _lineNumber,
|
||||
const QString &_copyText, bool _allowTrailingSpace)
|
||||
: word(_word)
|
||||
, copyText(_copyText)
|
||||
, text(_word.isText() ? _word.getText() : QString())
|
||||
, x(_x)
|
||||
, y(_y)
|
||||
, width(_word.getWidth(scale))
|
||||
, height(_word.getHeight(scale))
|
||||
, lineNumber(_lineNumber)
|
||||
, _trailingSpace(!_word.getCopyText().isEmpty() &&
|
||||
_word.hasTrailingSpace() & _allowTrailingSpace)
|
||||
, wordCharOffset(0)
|
||||
{
|
||||
}
|
||||
|
||||
WordPart::WordPart(Word &_word, int _x, int _y, int _width, int _height, int _lineNumber,
|
||||
const QString &_copyText, const QString &_customText, bool _allowTrailingSpace,
|
||||
int _wordCharOffset)
|
||||
: word(_word)
|
||||
, copyText(_copyText)
|
||||
, text(_customText)
|
||||
, x(_x)
|
||||
, y(_y)
|
||||
, width(_width)
|
||||
, height(_height)
|
||||
, lineNumber(_lineNumber)
|
||||
, _trailingSpace(!_word.getCopyText().isEmpty() &&
|
||||
_word.hasTrailingSpace() & _allowTrailingSpace)
|
||||
, wordCharOffset(_wordCharOffset)
|
||||
{
|
||||
}
|
||||
|
||||
const Word &WordPart::getWord() const
|
||||
{
|
||||
return this->word;
|
||||
}
|
||||
|
||||
int WordPart::getWidth() const
|
||||
{
|
||||
return this->width;
|
||||
}
|
||||
|
||||
int WordPart::getHeight() const
|
||||
{
|
||||
return this->height;
|
||||
}
|
||||
|
||||
int WordPart::getX() const
|
||||
{
|
||||
return this->x;
|
||||
}
|
||||
|
||||
int WordPart::getY() const
|
||||
{
|
||||
return this->y;
|
||||
}
|
||||
|
||||
void WordPart::setPosition(int x, int y)
|
||||
{
|
||||
this->x = x;
|
||||
this->y = y;
|
||||
}
|
||||
|
||||
void WordPart::setY(int y)
|
||||
{
|
||||
this->y = y;
|
||||
}
|
||||
|
||||
int WordPart::getRight() const
|
||||
{
|
||||
return this->x + this->width;
|
||||
}
|
||||
|
||||
int WordPart::getBottom() const
|
||||
{
|
||||
return this->y + this->height;
|
||||
}
|
||||
|
||||
QRect WordPart::getRect() const
|
||||
{
|
||||
return QRect(this->x, this->y, this->width, this->height - 1);
|
||||
}
|
||||
|
||||
const QString WordPart::getCopyText() const
|
||||
{
|
||||
return this->copyText;
|
||||
}
|
||||
|
||||
int WordPart::hasTrailingSpace() const
|
||||
{
|
||||
return this->_trailingSpace;
|
||||
}
|
||||
|
||||
const QString &WordPart::getText() const
|
||||
{
|
||||
return this->text;
|
||||
}
|
||||
|
||||
int WordPart::getLineNumber() const
|
||||
{
|
||||
return this->lineNumber;
|
||||
}
|
||||
|
||||
int WordPart::getCharacterLength() const
|
||||
{
|
||||
// return (this->getWord().isImage() ? 1 : this->getText().length()) + (_trailingSpace ? 1 :
|
||||
// 0);
|
||||
return this->getWord().isImage() ? 2 : this->getText().length() + 1;
|
||||
}
|
||||
|
||||
short WordPart::getCharWidth(int index, float scale) const
|
||||
{
|
||||
return this->getWord().getCharWidth(index + this->wordCharOffset, scale);
|
||||
}
|
||||
} // namespace messages
|
||||
} // namespace chatterino
|
||||
@@ -1,56 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QRect>
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
namespace messages {
|
||||
|
||||
class Word;
|
||||
|
||||
class WordPart
|
||||
{
|
||||
public:
|
||||
WordPart(Word &getWord, int getX, int getY, float scale, int _lineNumber,
|
||||
const QString &getCopyText, bool allowTrailingSpace = true);
|
||||
|
||||
WordPart(Word &getWord, int getX, int getY, int getWidth, int getHeight, int _lineNumber,
|
||||
const QString &getCopyText, const QString &customText, bool allowTrailingSpace = true,
|
||||
int wordCharOffset = 0);
|
||||
|
||||
const Word &getWord() const;
|
||||
int getWidth() const;
|
||||
int getHeight() const;
|
||||
int getX() const;
|
||||
int getY() const;
|
||||
void setPosition(int _x, int _y);
|
||||
void setY(int _y);
|
||||
int getRight() const;
|
||||
int getBottom() const;
|
||||
QRect getRect() const;
|
||||
const QString getCopyText() const;
|
||||
int hasTrailingSpace() const;
|
||||
const QString &getText() const;
|
||||
int getLineNumber() const;
|
||||
int getCharacterLength() const;
|
||||
short getCharWidth(int index, float scale) const;
|
||||
|
||||
private:
|
||||
Word &word;
|
||||
|
||||
QString copyText;
|
||||
QString text;
|
||||
|
||||
int x;
|
||||
int y;
|
||||
int width;
|
||||
int height;
|
||||
|
||||
int lineNumber;
|
||||
|
||||
bool _trailingSpace;
|
||||
int wordCharOffset;
|
||||
};
|
||||
|
||||
} // namespace messages
|
||||
} // namespace chatterino
|
||||
Reference in New Issue
Block a user