refactored message drawing and layouting
This commit is contained in:
+2
-1
@@ -164,7 +164,8 @@ HEADERS += \
|
|||||||
src/messages/messagecolor.hpp \
|
src/messages/messagecolor.hpp \
|
||||||
src/util/nativeeventhelper.hpp \
|
src/util/nativeeventhelper.hpp \
|
||||||
src/debug/log.hpp \
|
src/debug/log.hpp \
|
||||||
src/messages/imageloadermanager.hpp
|
src/messages/imageloadermanager.hpp \
|
||||||
|
src/util/benchmark.hpp
|
||||||
|
|
||||||
PRECOMPILED_HEADER =
|
PRECOMPILED_HEADER =
|
||||||
|
|
||||||
|
|||||||
@@ -20,10 +20,14 @@ using namespace chatterino::messages;
|
|||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
|
EmoteManager *EmoteManager::instance = nullptr;
|
||||||
|
|
||||||
EmoteManager::EmoteManager(WindowManager &_windowManager)
|
EmoteManager::EmoteManager(WindowManager &_windowManager)
|
||||||
: windowManager(_windowManager)
|
: windowManager(_windowManager)
|
||||||
, findShortCodesRegex(":([-+\\w]+):")
|
, findShortCodesRegex(":([-+\\w]+):")
|
||||||
{
|
{
|
||||||
|
this->instance = this;
|
||||||
|
|
||||||
pajlada::Settings::Setting<std::string> roomID(
|
pajlada::Settings::Setting<std::string> roomID(
|
||||||
"/accounts/current/roomID", "", pajlada::Settings::SettingOption::DoNotWriteToJSON);
|
"/accounts/current/roomID", "", pajlada::Settings::SettingOption::DoNotWriteToJSON);
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ class EmoteManager
|
|||||||
public:
|
public:
|
||||||
explicit EmoteManager(WindowManager &_windowManager);
|
explicit EmoteManager(WindowManager &_windowManager);
|
||||||
|
|
||||||
|
static EmoteManager *instance;
|
||||||
|
|
||||||
void loadGlobalEmotes();
|
void loadGlobalEmotes();
|
||||||
|
|
||||||
void reloadBTTVChannelEmotes(const QString &channelName,
|
void reloadBTTVChannelEmotes(const QString &channelName,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "messages/lazyloadedimage.hpp"
|
#include "messages/lazyloadedimage.hpp"
|
||||||
#include "windowmanager.hpp"
|
#include "windowmanager.hpp"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
#include <QBuffer>
|
#include <QBuffer>
|
||||||
#include <QImageReader>
|
#include <QImageReader>
|
||||||
#include <QNetworkAccessManager>
|
#include <QNetworkAccessManager>
|
||||||
@@ -81,7 +82,7 @@ void ImageLoaderWorker::handleLoad(chatterino::messages::LazyLoadedImage *lli, Q
|
|||||||
lli->emoteManager.incGeneration();
|
lli->emoteManager.incGeneration();
|
||||||
lli->windowManager.layoutVisibleChatWidgets();
|
lli->windowManager.layoutVisibleChatWidgets();
|
||||||
|
|
||||||
delete reply;
|
reply->deleteLater();
|
||||||
delete this;
|
delete this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,97 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "messages/limitedqueuesnapshot.hpp"
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
namespace messages {
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
class LimitedQueue
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
LimitedQueue(int _limit = 1000, int _buffer = 250)
|
|
||||||
: offset(0)
|
|
||||||
, limit(_limit)
|
|
||||||
, buffer(_buffer)
|
|
||||||
{
|
|
||||||
this->vector = std::make_shared<std::vector<T>>();
|
|
||||||
this->vector->reserve(this->limit + this->buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void clear()
|
|
||||||
{
|
|
||||||
std::lockthis->guard<std::mutex> lock(this->mutex);
|
|
||||||
|
|
||||||
this->vector = std::make_shared<std::vector<T>>();
|
|
||||||
this->vector->reserve(this->limit + this->buffer);
|
|
||||||
|
|
||||||
this->offset = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// return true if an item was deleted
|
|
||||||
// deleted will be set if the item was deleted
|
|
||||||
bool appendItem(const T &item, T &deleted)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(this->mutex);
|
|
||||||
|
|
||||||
if (this->vector->size() >= this->limit) {
|
|
||||||
// vector is full
|
|
||||||
if (this->offset == this->buffer) {
|
|
||||||
deleted = this->vector->at(this->offset);
|
|
||||||
|
|
||||||
// create new vector
|
|
||||||
auto newVector = std::make_shared<std::vector<T>>();
|
|
||||||
newVector->reserve(this->limit + this->buffer);
|
|
||||||
|
|
||||||
for (unsigned int i = 0; i < this->limit; ++i) {
|
|
||||||
newVector->pushthis->back(this->vector->at(i + this->offset));
|
|
||||||
}
|
|
||||||
newVector->push_back(item);
|
|
||||||
|
|
||||||
this->offset = 0;
|
|
||||||
this->vector = newVector;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
deleted = this->vector->at(this->offset);
|
|
||||||
|
|
||||||
// append item and increment offset("deleting" first element)
|
|
||||||
this->vector->push_back(item);
|
|
||||||
this->offset++;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// append item
|
|
||||||
this->vector->pushthis->back(item);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
messages::LimitedQueueSnapshot<T> getSnapshot()
|
|
||||||
{
|
|
||||||
std::lockthis->guard<std::mutex> lock(this->mutex);
|
|
||||||
|
|
||||||
if (this->vector->size() < this->limit) {
|
|
||||||
return LimitedQueueSnapshot<T>(this->vector, this->offset, this->vector->size());
|
|
||||||
} else {
|
|
||||||
return LimitedQueueSnapshot<T>(this->vector, this->offset, this->limit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::shared_ptr<std::vector<T>> vector;
|
|
||||||
std::mutex mutex;
|
|
||||||
|
|
||||||
unsigned int offset;
|
|
||||||
unsigned int limit;
|
|
||||||
unsigned int buffer;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace messages
|
|
||||||
} // namespace chatterino
|
|
||||||
+121
-101
@@ -30,84 +30,72 @@ int MessageRef::getHeight() const
|
|||||||
return this->height;
|
return this->height;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MessageRef::layout(int width, bool enableEmoteMargins)
|
// return true if redraw is required
|
||||||
|
bool MessageRef::layout(int width)
|
||||||
{
|
{
|
||||||
auto &settings = SettingsManager::getInstance();
|
bool layoutRequired = false;
|
||||||
|
|
||||||
bool sizeChanged = width != this->currentLayoutWidth;
|
// check if width changed
|
||||||
bool redraw = width != this->currentLayoutWidth;
|
const bool widthChanged = width != this->currentLayoutWidth;
|
||||||
int spaceWidth = 4;
|
layoutRequired |= widthChanged;
|
||||||
|
this->currentLayoutWidth = width;
|
||||||
|
// check if emotes changed
|
||||||
|
const bool imagesChanged = this->emoteGeneration != EmoteManager::instance->getGeneration();
|
||||||
|
layoutRequired |= imagesChanged;
|
||||||
|
this->emoteGeneration = EmoteManager::instance->getGeneration();
|
||||||
|
// check if text changed
|
||||||
|
const bool textChanged = this->fontGeneration != FontManager::getInstance().getGeneration();
|
||||||
|
layoutRequired |= textChanged;
|
||||||
|
this->fontGeneration = FontManager::getInstance().getGeneration();
|
||||||
|
// check if work mask changed
|
||||||
|
const bool wordMaskChanged =
|
||||||
|
this->currentWordTypes != SettingsManager::getInstance().getWordTypeMask();
|
||||||
|
layoutRequired |= wordMaskChanged;
|
||||||
|
this->currentWordTypes = SettingsManager::getInstance().getWordTypeMask();
|
||||||
|
|
||||||
int mediumTextLineHeight =
|
// update word sizes if needed
|
||||||
FontManager::getInstance().getFontMetrics(FontManager::Medium).height();
|
if (imagesChanged) {
|
||||||
|
this->updateImageSizes();
|
||||||
|
}
|
||||||
|
if (textChanged) {
|
||||||
|
this->updateTextSizes();
|
||||||
|
}
|
||||||
|
if (widthChanged) {
|
||||||
|
this->buffer = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
/* TODO(pajlada): Re-implement
|
// return if no layout is required
|
||||||
bool recalculateImages = this->emoteGeneration != EmoteManager::getInstance().getGeneration();
|
if (!layoutRequired) {
|
||||||
*/
|
|
||||||
bool recalculateImages = true;
|
|
||||||
|
|
||||||
bool recalculateText = this->fontGeneration != FontManager::getInstance().getGeneration();
|
|
||||||
bool newWordTypes = this->currentWordTypes != SettingsManager::getInstance().getWordTypeMask();
|
|
||||||
|
|
||||||
qreal emoteScale = settings.emoteScale.get();
|
|
||||||
bool scaleEmotesByLineHeight = settings.scaleEmotesByLineHeight.get();
|
|
||||||
|
|
||||||
// calculate word sizes
|
|
||||||
if (!redraw && !recalculateImages && !recalculateText && !newWordTypes) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this->emoteGeneration = EmoteManager::getInstance().getGeneration();
|
this->actuallyLayout(width);
|
||||||
this->fontGeneration = FontManager::getInstance().getGeneration();
|
|
||||||
|
|
||||||
for (auto &word : this->message->getWords()) {
|
return true;
|
||||||
if (word.isImage()) {
|
}
|
||||||
if (!recalculateImages) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto &image = word.getImage();
|
void MessageRef::actuallyLayout(int width)
|
||||||
|
{
|
||||||
|
auto &settings = SettingsManager::getInstance();
|
||||||
|
|
||||||
qreal w = image.getWidth();
|
const int spaceWidth = 4;
|
||||||
qreal h = image.getHeight();
|
const int right = width - MARGIN_RIGHT;
|
||||||
|
|
||||||
if (scaleEmotesByLineHeight) {
|
// clear word parts
|
||||||
word.setSize(w * mediumTextLineHeight / h * emoteScale,
|
this->wordParts.clear();
|
||||||
mediumTextLineHeight * emoteScale);
|
|
||||||
} else {
|
|
||||||
word.setSize(w * image.getScale() * emoteScale, h * image.getScale() * emoteScale);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!recalculateText) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
QFontMetrics &metrics = word.getFontMetrics();
|
|
||||||
word.setSize(metrics.width(word.getText()), metrics.height());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newWordTypes) {
|
|
||||||
this->currentWordTypes = settings.getWordTypeMask();
|
|
||||||
}
|
|
||||||
|
|
||||||
// layout
|
// layout
|
||||||
this->currentLayoutWidth = width;
|
|
||||||
|
|
||||||
int x = MARGIN_LEFT;
|
int x = MARGIN_LEFT;
|
||||||
int y = MARGIN_TOP;
|
int y = MARGIN_TOP;
|
||||||
|
|
||||||
int right = width - MARGIN_RIGHT;
|
|
||||||
|
|
||||||
int lineNumber = 0;
|
int lineNumber = 0;
|
||||||
int lineStart = 0;
|
int lineStart = 0;
|
||||||
int lineHeight = 0;
|
int lineHeight = 0;
|
||||||
bool first = true;
|
bool first = true;
|
||||||
|
|
||||||
this->wordParts.clear();
|
|
||||||
|
|
||||||
uint32_t flags = settings.getWordTypeMask();
|
uint32_t flags = settings.getWordTypeMask();
|
||||||
|
|
||||||
|
// 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) {
|
for (auto it = this->message->getWords().begin(); it != this->message->getWords().end(); ++it) {
|
||||||
Word &word = *it;
|
Word &word = *it;
|
||||||
|
|
||||||
@@ -118,64 +106,60 @@ bool MessageRef::layout(int width, bool enableEmoteMargins)
|
|||||||
|
|
||||||
int xOffset = 0, yOffset = 0;
|
int xOffset = 0, yOffset = 0;
|
||||||
|
|
||||||
if (enableEmoteMargins) {
|
/// if (enableEmoteMargins) {
|
||||||
if (word.isImage() && word.getImage().isHat()) {
|
/// if (word.isImage() && word.getImage().isHat()) {
|
||||||
xOffset = -word.getWidth() + 2;
|
/// xOffset = -word.getWidth() + 2;
|
||||||
} else {
|
/// } else {
|
||||||
xOffset = word.getXOffset();
|
xOffset = word.getXOffset();
|
||||||
yOffset = word.getYOffset();
|
yOffset = word.getYOffset();
|
||||||
}
|
/// }
|
||||||
}
|
/// }
|
||||||
|
|
||||||
// word wrapping
|
// word wrapping
|
||||||
if (word.isText() && word.getWidth() + MARGIN_LEFT > right) {
|
if (word.isText() && word.getWidth() + MARGIN_LEFT > right) {
|
||||||
|
// align and end the current line
|
||||||
alignWordParts(lineStart, lineHeight, width);
|
alignWordParts(lineStart, lineHeight, width);
|
||||||
|
|
||||||
y += lineHeight;
|
y += lineHeight;
|
||||||
|
|
||||||
const QString &text = word.getText();
|
int currentPartStart = 0;
|
||||||
|
int currentLineWidth = 0;
|
||||||
|
|
||||||
int start = 0;
|
// go through the text, break text when it doesn't fit in the line anymore
|
||||||
QFontMetrics &metrics = word.getFontMetrics();
|
for (int i = 1; i <= word.getText().length(); i++) {
|
||||||
|
currentLineWidth += word.getCharWidth(i - 1);
|
||||||
|
|
||||||
int width = 0;
|
if (currentLineWidth + MARGIN_LEFT > right) {
|
||||||
|
// add the current line
|
||||||
|
QString mid = word.getText().mid(currentPartStart, i - currentPartStart - 1);
|
||||||
|
|
||||||
std::vector<short> &charWidths = word.getCharacterWidthCache();
|
this->wordParts.push_back(WordPart(word, MARGIN_LEFT, y, currentLineWidth,
|
||||||
int charOffset = 0;
|
|
||||||
|
|
||||||
for (int i = 2; i <= text.length(); i++) {
|
|
||||||
if ((width = width + charWidths[i - 1]) + MARGIN_LEFT > right) {
|
|
||||||
QString mid = text.mid(start, i - start - 1);
|
|
||||||
|
|
||||||
this->wordParts.push_back(WordPart(word, MARGIN_LEFT, y, width,
|
|
||||||
word.getHeight(), lineNumber, mid, mid,
|
word.getHeight(), lineNumber, mid, mid,
|
||||||
false, charOffset));
|
false, currentPartStart));
|
||||||
|
|
||||||
charOffset = i;
|
y += word.getFontMetrics().height();
|
||||||
|
|
||||||
y += metrics.height();
|
currentPartStart = i - 1;
|
||||||
|
|
||||||
start = i - 1;
|
currentLineWidth = 0;
|
||||||
|
|
||||||
width = 0;
|
|
||||||
lineNumber++;
|
lineNumber++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString mid(text.mid(start));
|
QString mid(word.getText().mid(currentPartStart));
|
||||||
width = metrics.width(mid);
|
currentLineWidth = word.getFontMetrics().width(mid);
|
||||||
|
|
||||||
this->wordParts.push_back(WordPart(word, MARGIN_LEFT, y - word.getHeight(), width,
|
this->wordParts.push_back(WordPart(word, MARGIN_LEFT, y - word.getHeight(),
|
||||||
word.getHeight(), lineNumber, mid, mid, charOffset));
|
currentLineWidth, word.getHeight(), lineNumber, mid,
|
||||||
x = width + MARGIN_LEFT + spaceWidth;
|
mid, true, currentPartStart));
|
||||||
|
|
||||||
|
x = currentLineWidth + MARGIN_LEFT + spaceWidth;
|
||||||
lineHeight = word.getHeight();
|
lineHeight = word.getHeight();
|
||||||
|
|
||||||
lineStart = this->wordParts.size() - 1;
|
lineStart = this->wordParts.size() - 1;
|
||||||
|
|
||||||
first = false;
|
first = false;
|
||||||
} else if (first || x + word.getWidth() + xOffset <= right) {
|
}
|
||||||
// fits in the line
|
// fits in the current line
|
||||||
|
else if (first || x + word.getWidth() + xOffset <= right) {
|
||||||
this->wordParts.push_back(
|
this->wordParts.push_back(
|
||||||
WordPart(word, x, y - word.getHeight(), lineNumber, word.getCopyText()));
|
WordPart(word, x, y - word.getHeight(), lineNumber, word.getCopyText()));
|
||||||
|
|
||||||
@@ -185,8 +169,10 @@ bool MessageRef::layout(int width, bool enableEmoteMargins)
|
|||||||
lineHeight = std::max(word.getHeight(), lineHeight);
|
lineHeight = std::max(word.getHeight(), lineHeight);
|
||||||
|
|
||||||
first = false;
|
first = false;
|
||||||
} else {
|
}
|
||||||
// doesn't fit in the line
|
// doesn't fit in the line
|
||||||
|
else {
|
||||||
|
// align and end the current line
|
||||||
alignWordParts(lineStart, lineHeight, width);
|
alignWordParts(lineStart, lineHeight, width);
|
||||||
|
|
||||||
y += lineHeight;
|
y += lineHeight;
|
||||||
@@ -205,22 +191,56 @@ bool MessageRef::layout(int width, bool enableEmoteMargins)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// align and end the current line
|
||||||
alignWordParts(lineStart, lineHeight, width);
|
alignWordParts(lineStart, lineHeight, width);
|
||||||
|
|
||||||
if (this->height != y + lineHeight) {
|
// update height
|
||||||
sizeChanged = true;
|
int oldHeight = this->height;
|
||||||
this->height = y + lineHeight;
|
this->height = y + lineHeight + MARGIN_BOTTOM;
|
||||||
}
|
|
||||||
|
|
||||||
this->height += MARGIN_BOTTOM;
|
// invalidate buffer if height changed
|
||||||
|
if (oldHeight != this->height) {
|
||||||
if (sizeChanged) {
|
this->buffer = nullptr;
|
||||||
buffer = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateBuffer = true;
|
updateBuffer = true;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
void MessageRef::updateTextSizes()
|
||||||
|
{
|
||||||
|
for (auto &word : this->message->getWords()) {
|
||||||
|
if (!word.isText())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
QFontMetrics &metrics = word.getFontMetrics();
|
||||||
|
word.setSize(metrics.width(word.getText()), metrics.height());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageRef::updateImageSizes()
|
||||||
|
{
|
||||||
|
const int mediumTextLineHeight =
|
||||||
|
FontManager::getInstance().getFontMetrics(FontManager::Medium).height();
|
||||||
|
const qreal emoteScale = SettingsManager::getInstance().emoteScale.get();
|
||||||
|
const bool scaleEmotesByLineHeight =
|
||||||
|
SettingsManager::getInstance().scaleEmotesByLineHeight.get();
|
||||||
|
|
||||||
|
for (auto &word : this->message->getWords()) {
|
||||||
|
if (!word.isImage())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto &image = word.getImage();
|
||||||
|
|
||||||
|
qreal w = image.getWidth();
|
||||||
|
qreal h = image.getHeight();
|
||||||
|
|
||||||
|
if (scaleEmotesByLineHeight) {
|
||||||
|
word.setSize(w * mediumTextLineHeight / h * emoteScale,
|
||||||
|
mediumTextLineHeight * emoteScale);
|
||||||
|
} else {
|
||||||
|
word.setSize(w * image.getScale() * emoteScale, h * image.getScale() * emoteScale);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<WordPart> &MessageRef::getWordParts() const
|
const std::vector<WordPart> &MessageRef::getWordParts() const
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public:
|
|||||||
Message *getMessage();
|
Message *getMessage();
|
||||||
int getHeight() const;
|
int getHeight() const;
|
||||||
|
|
||||||
bool layout(int width, bool enableEmoteMargins = true);
|
bool layout(int width);
|
||||||
|
|
||||||
const std::vector<WordPart> &getWordParts() const;
|
const std::vector<WordPart> &getWordParts() const;
|
||||||
|
|
||||||
@@ -30,7 +30,6 @@ public:
|
|||||||
bool updateBuffer = false;
|
bool updateBuffer = false;
|
||||||
|
|
||||||
const Word *tryGetWordPart(QPoint point);
|
const Word *tryGetWordPart(QPoint point);
|
||||||
|
|
||||||
int getSelectionIndex(QPoint position);
|
int getSelectionIndex(QPoint position);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -42,13 +41,15 @@ private:
|
|||||||
|
|
||||||
int currentLayoutWidth = -1;
|
int currentLayoutWidth = -1;
|
||||||
int fontGeneration = -1;
|
int fontGeneration = -1;
|
||||||
/* TODO(pajlada): Re-implement
|
|
||||||
int emoteGeneration = -1;
|
int emoteGeneration = -1;
|
||||||
*/
|
|
||||||
Word::Type currentWordTypes = Word::None;
|
Word::Type currentWordTypes = Word::None;
|
||||||
|
|
||||||
// methods
|
// methods
|
||||||
|
void actuallyLayout(int width);
|
||||||
void alignWordParts(int lineStart, int lineHeight, int width);
|
void alignWordParts(int lineStart, int lineHeight, int width);
|
||||||
|
void updateTextSizes();
|
||||||
|
void updateImageSizes();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace messages
|
} // namespace messages
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "messages/word.hpp"
|
#include "messages/word.hpp"
|
||||||
|
#include "util/benchmark.hpp"
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
namespace messages {
|
namespace messages {
|
||||||
@@ -127,6 +128,11 @@ int Word::getCharacterLength() const
|
|||||||
return this->isImage() ? 2 : this->getText().length() + 1;
|
return this->isImage() ? 2 : this->getText().length() + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
short Word::getCharWidth(int index) const
|
||||||
|
{
|
||||||
|
return this->getCharacterWidthCache().at(index);
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<short> &Word::getCharacterWidthCache() const
|
std::vector<short> &Word::getCharacterWidthCache() const
|
||||||
{
|
{
|
||||||
// lock not required because there is only one gui thread
|
// lock not required because there is only one gui thread
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ public:
|
|||||||
void setOffset(int _xOffset, int _yOffset);
|
void setOffset(int _xOffset, int _yOffset);
|
||||||
int getCharacterLength() const;
|
int getCharacterLength() const;
|
||||||
|
|
||||||
std::vector<short> &getCharacterWidthCache() const;
|
short getCharWidth(int index) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LazyLoadedImage *image;
|
LazyLoadedImage *image;
|
||||||
@@ -136,6 +136,7 @@ private:
|
|||||||
FontManager::Type font = FontManager::Medium;
|
FontManager::Type font = FontManager::Medium;
|
||||||
Link link;
|
Link link;
|
||||||
|
|
||||||
|
std::vector<short> &getCharacterWidthCache() const;
|
||||||
mutable std::vector<short> charWidthCache;
|
mutable std::vector<short> charWidthCache;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -115,9 +115,9 @@ int WordPart::getCharacterLength() const
|
|||||||
return this->getWord().isImage() ? 2 : this->getText().length() + 1;
|
return this->getWord().isImage() ? 2 : this->getText().length() + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
short WordPart::getCharacterWidth(int index) const
|
short WordPart::getCharWidth(int index) const
|
||||||
{
|
{
|
||||||
return this->getWord().getCharacterWidthCache().at(index + this->wordCharOffset);
|
return this->getWord().getCharWidth(index + this->wordCharOffset);
|
||||||
}
|
}
|
||||||
} // namespace messages
|
} // namespace messages
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ public:
|
|||||||
const QString &getText() const;
|
const QString &getText() const;
|
||||||
int getLineNumber() const;
|
int getLineNumber() const;
|
||||||
int getCharacterLength() const;
|
int getCharacterLength() const;
|
||||||
short getCharacterWidth(int index) const;
|
short getCharWidth(int index) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Word &word;
|
Word &word;
|
||||||
|
|||||||
@@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QElapsedTimer>
|
#include <QElapsedTimer>
|
||||||
|
#include <boost/current_function.hpp>
|
||||||
|
|
||||||
#define BENCH(x) \
|
#define BENCH(x) \
|
||||||
QElapsedTimer x; \
|
QElapsedTimer x; \
|
||||||
x.start();
|
x.start();
|
||||||
|
|
||||||
#define MARK(x) \
|
#define MARK(x) \
|
||||||
qDebug() << __FILE__ << __LINE__ << static_cast<float>(x.nsecsElapsed()) / 100000.0 << "ms";
|
qDebug() << BOOST_CURRENT_FUNCTION << __LINE__ \
|
||||||
|
<< static_cast<float>(x.nsecsElapsed()) / 100000.0 << "ms";
|
||||||
|
|||||||
+66
-31
@@ -6,6 +6,7 @@
|
|||||||
#include "messages/messageref.hpp"
|
#include "messages/messageref.hpp"
|
||||||
#include "settingsmanager.hpp"
|
#include "settingsmanager.hpp"
|
||||||
#include "ui_accountpopupform.h"
|
#include "ui_accountpopupform.h"
|
||||||
|
#include "util/benchmark.hpp"
|
||||||
#include "util/distancebetweenpoints.hpp"
|
#include "util/distancebetweenpoints.hpp"
|
||||||
#include "widgets/chatwidget.hpp"
|
#include "widgets/chatwidget.hpp"
|
||||||
#include "windowmanager.hpp"
|
#include "windowmanager.hpp"
|
||||||
@@ -46,12 +47,12 @@ ChannelView::ChannelView(WindowManager &windowManager, BaseWidget *parent)
|
|||||||
|
|
||||||
this->goToBottom->setVisible(this->scrollBar.isVisible() && !this->scrollBar.isAtBottom());
|
this->goToBottom->setVisible(this->scrollBar.isVisible() && !this->scrollBar.isAtBottom());
|
||||||
|
|
||||||
this->update();
|
this->queueUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
this->repaintGifsConnection =
|
this->repaintGifsConnection =
|
||||||
windowManager.repaintGifs.connect([&] { this->updateGifEmotes(); });
|
windowManager.repaintGifs.connect([&] { this->updateGifEmotes(); });
|
||||||
this->layoutConnection = windowManager.repaintGifs.connect([&] { this->layout(); });
|
this->layoutConnection = windowManager.layout.connect([&] { this->layoutMessages(); });
|
||||||
|
|
||||||
this->goToBottom = new RippleEffectLabel(this, 0);
|
this->goToBottom = new RippleEffectLabel(this, 0);
|
||||||
this->goToBottom->setStyleSheet("background-color: rgba(0,0,0,0.5); color: #FFF;");
|
this->goToBottom->setStyleSheet("background-color: rgba(0,0,0,0.5); color: #FFF;");
|
||||||
@@ -60,6 +61,16 @@ ChannelView::ChannelView(WindowManager &windowManager, BaseWidget *parent)
|
|||||||
|
|
||||||
connect(goToBottom, &RippleEffectLabel::clicked, this,
|
connect(goToBottom, &RippleEffectLabel::clicked, this,
|
||||||
[this] { QTimer::singleShot(180, [this] { this->scrollBar.scrollToBottom(); }); });
|
[this] { QTimer::singleShot(180, [this] { this->scrollBar.scrollToBottom(); }); });
|
||||||
|
|
||||||
|
this->updateTimer.setInterval(1000 / 60);
|
||||||
|
this->updateTimer.setSingleShot(true);
|
||||||
|
connect(&this->updateTimer, &QTimer::timeout, this, [this] {
|
||||||
|
if (this->updateQueued) {
|
||||||
|
this->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
this->updateTimer.start();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ChannelView::~ChannelView()
|
ChannelView::~ChannelView()
|
||||||
@@ -68,17 +79,35 @@ ChannelView::~ChannelView()
|
|||||||
this, &ChannelView::wordTypeMaskChanged);
|
this, &ChannelView::wordTypeMaskChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ChannelView::layoutMessages()
|
void ChannelView::queueUpdate()
|
||||||
{
|
{
|
||||||
|
if (this->updateTimer.isActive()) {
|
||||||
|
this->updateQueued = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
update();
|
||||||
|
|
||||||
|
this->updateTimer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChannelView::layoutMessages()
|
||||||
|
{
|
||||||
|
this->actuallyLayoutMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChannelView::actuallyLayoutMessages()
|
||||||
|
{
|
||||||
|
BENCH(timer)
|
||||||
auto messages = this->getMessagesSnapshot();
|
auto messages = this->getMessagesSnapshot();
|
||||||
|
|
||||||
if (messages.getLength() == 0) {
|
if (messages.getLength() == 0) {
|
||||||
this->scrollBar.setVisible(false);
|
this->scrollBar.setVisible(false);
|
||||||
return false;
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool redrawRequired = false;
|
||||||
bool showScrollbar = false;
|
bool showScrollbar = false;
|
||||||
bool redraw = false;
|
|
||||||
|
|
||||||
// Bool indicating whether or not we were showing all messages
|
// Bool indicating whether or not we were showing all messages
|
||||||
// True if one of the following statements are true:
|
// True if one of the following statements are true:
|
||||||
@@ -97,7 +126,7 @@ bool ChannelView::layoutMessages()
|
|||||||
for (size_t i = start; i < messages.getLength(); ++i) {
|
for (size_t i = start; i < messages.getLength(); ++i) {
|
||||||
auto message = messages[i];
|
auto message = messages[i];
|
||||||
|
|
||||||
redraw |= message->layout(layoutWidth, true);
|
redrawRequired |= message->layout(layoutWidth);
|
||||||
|
|
||||||
y += message->getHeight();
|
y += message->getHeight();
|
||||||
|
|
||||||
@@ -113,7 +142,7 @@ bool ChannelView::layoutMessages()
|
|||||||
for (std::size_t i = messages.getLength() - 1; i > 0; i--) {
|
for (std::size_t i = messages.getLength() - 1; i > 0; i--) {
|
||||||
auto *message = messages[i].get();
|
auto *message = messages[i].get();
|
||||||
|
|
||||||
message->layout(layoutWidth, true);
|
message->layout(layoutWidth);
|
||||||
|
|
||||||
h -= message->getHeight();
|
h -= message->getHeight();
|
||||||
|
|
||||||
@@ -144,7 +173,11 @@ bool ChannelView::layoutMessages()
|
|||||||
this->scrollBar.scrollToBottom();
|
this->scrollBar.scrollToBottom();
|
||||||
}
|
}
|
||||||
|
|
||||||
return redraw;
|
MARK(timer);
|
||||||
|
|
||||||
|
if (redrawRequired) {
|
||||||
|
this->queueUpdate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChannelView::clearMessages()
|
void ChannelView::clearMessages()
|
||||||
@@ -154,13 +187,15 @@ void ChannelView::clearMessages()
|
|||||||
|
|
||||||
// Layout chat widget messages, and force an update regardless if there are no messages
|
// Layout chat widget messages, and force an update regardless if there are no messages
|
||||||
this->layoutMessages();
|
this->layoutMessages();
|
||||||
this->update();
|
this->queueUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChannelView::updateGifEmotes()
|
void ChannelView::updateGifEmotes()
|
||||||
{
|
{
|
||||||
this->onlyUpdateEmotes = true;
|
if (!this->gifEmotes.empty()) {
|
||||||
this->update();
|
this->onlyUpdateEmotes = true;
|
||||||
|
this->queueUpdate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollBar &ChannelView::getScrollBar()
|
ScrollBar &ChannelView::getScrollBar()
|
||||||
@@ -278,7 +313,6 @@ void ChannelView::clearSelection()
|
|||||||
{
|
{
|
||||||
this->selection = Selection();
|
this->selection = Selection();
|
||||||
layoutMessages();
|
layoutMessages();
|
||||||
update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
messages::LimitedQueueSnapshot<SharedMessageRef> ChannelView::getMessagesSnapshot()
|
messages::LimitedQueueSnapshot<SharedMessageRef> ChannelView::getMessagesSnapshot()
|
||||||
@@ -318,8 +352,7 @@ void ChannelView::setChannel(std::shared_ptr<Channel> channel)
|
|||||||
this->selection.start.messageIndex--;
|
this->selection.start.messageIndex--;
|
||||||
this->selection.end.messageIndex--;
|
this->selection.end.messageIndex--;
|
||||||
|
|
||||||
layoutMessages();
|
this->layoutMessages();
|
||||||
update();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
auto snapshot = channel->getMessageSnapshot();
|
auto snapshot = channel->getMessageSnapshot();
|
||||||
@@ -373,9 +406,10 @@ void ChannelView::setSelection(const SelectionItem &start, const SelectionItem &
|
|||||||
|
|
||||||
void ChannelView::paintEvent(QPaintEvent * /*event*/)
|
void ChannelView::paintEvent(QPaintEvent * /*event*/)
|
||||||
{
|
{
|
||||||
|
// BENCH(timer);
|
||||||
QPainter painter(this);
|
QPainter painter(this);
|
||||||
|
|
||||||
painter.setRenderHint(QPainter::SmoothPixmapTransform);
|
// painter.setRenderHint(QPainter::SmoothPixmapTransform);
|
||||||
|
|
||||||
// only update gif emotes
|
// only update gif emotes
|
||||||
#ifndef Q_OS_MAC
|
#ifndef Q_OS_MAC
|
||||||
@@ -402,10 +436,11 @@ void ChannelView::paintEvent(QPaintEvent * /*event*/)
|
|||||||
|
|
||||||
// draw gif emotes
|
// draw gif emotes
|
||||||
for (GifEmoteData &item : this->gifEmotes) {
|
for (GifEmoteData &item : this->gifEmotes) {
|
||||||
painter.fillRect(item.rect, this->colorScheme.ChatBackground);
|
// painter.fillRect(item.rect, this->colorScheme.ChatBackground);
|
||||||
|
|
||||||
painter.drawPixmap(item.rect, *item.image->getPixmap());
|
painter.drawPixmap(item.rect, *item.image->getPixmap());
|
||||||
}
|
}
|
||||||
|
// MARK(timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChannelView::drawMessages(QPainter &painter)
|
void ChannelView::drawMessages(QPainter &painter)
|
||||||
@@ -423,14 +458,13 @@ void ChannelView::drawMessages(QPainter &painter)
|
|||||||
for (size_t i = start; i < messages.getLength(); ++i) {
|
for (size_t i = start; i < messages.getLength(); ++i) {
|
||||||
messages::MessageRef *messageRef = messages[i].get();
|
messages::MessageRef *messageRef = messages[i].get();
|
||||||
|
|
||||||
std::shared_ptr<QPixmap> bufferPtr = messageRef->buffer;
|
std::shared_ptr<QPixmap> buffer = messageRef->buffer;
|
||||||
QPixmap *buffer = bufferPtr.get();
|
|
||||||
|
|
||||||
bool updateBuffer = messageRef->updateBuffer;
|
// bool updateBuffer = messageRef->updateBuffer;
|
||||||
|
bool updateBuffer = false;
|
||||||
|
|
||||||
if (buffer == nullptr) {
|
if (!buffer) {
|
||||||
buffer = new QPixmap(width(), messageRef->getHeight());
|
buffer = std::shared_ptr<QPixmap>(new QPixmap(width(), messageRef->getHeight()));
|
||||||
bufferPtr = std::shared_ptr<QPixmap>(buffer);
|
|
||||||
updateBuffer = true;
|
updateBuffer = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -438,7 +472,8 @@ void ChannelView::drawMessages(QPainter &painter)
|
|||||||
|
|
||||||
// update messages that have been changed
|
// update messages that have been changed
|
||||||
if (updateBuffer) {
|
if (updateBuffer) {
|
||||||
this->updateMessageBuffer(messageRef, buffer, i);
|
this->updateMessageBuffer(messageRef, buffer.get(), i);
|
||||||
|
qDebug() << "updating buffer xD";
|
||||||
}
|
}
|
||||||
|
|
||||||
// get gif emotes
|
// get gif emotes
|
||||||
@@ -459,11 +494,11 @@ void ChannelView::drawMessages(QPainter &painter)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
messageRef->buffer = bufferPtr;
|
messageRef->buffer = buffer;
|
||||||
|
|
||||||
// if (buffer != nullptr) {
|
if (buffer) {
|
||||||
painter.drawPixmap(0, y, *buffer);
|
painter.drawPixmap(0, y, *buffer.get());
|
||||||
// }
|
}
|
||||||
|
|
||||||
y += messageRef->getHeight();
|
y += messageRef->getHeight();
|
||||||
|
|
||||||
@@ -502,7 +537,7 @@ void ChannelView::updateMessageBuffer(messages::MessageRef *messageRef, QPixmap
|
|||||||
|
|
||||||
const QPixmap *image = lli.getPixmap();
|
const QPixmap *image = lli.getPixmap();
|
||||||
|
|
||||||
if (image != nullptr) {
|
if (image != nullptr && !lli.getAnimated()) {
|
||||||
painter.drawPixmap(QRect(wordPart.getX(), wordPart.getY(), wordPart.getWidth(),
|
painter.drawPixmap(QRect(wordPart.getX(), wordPart.getY(), wordPart.getWidth(),
|
||||||
wordPart.getHeight()),
|
wordPart.getHeight()),
|
||||||
*image);
|
*image);
|
||||||
@@ -582,7 +617,7 @@ void ChannelView::drawMessageSelection(QPainter &painter, messages::MessageRef *
|
|||||||
int offset = this->selection.min.charIndex - charIndex;
|
int offset = this->selection.min.charIndex - charIndex;
|
||||||
|
|
||||||
for (int j = 0; j < offset; j++) {
|
for (int j = 0; j < offset; j++) {
|
||||||
rect.setLeft(rect.left() + part.getCharacterWidth(j));
|
rect.setLeft(rect.left() + part.getCharWidth(j));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSingleWord) {
|
if (isSingleWord) {
|
||||||
@@ -591,7 +626,7 @@ void ChannelView::drawMessageSelection(QPainter &painter, messages::MessageRef *
|
|||||||
rect.setRight(part.getX());
|
rect.setRight(part.getX());
|
||||||
|
|
||||||
for (int j = 0; j < offset + length; j++) {
|
for (int j = 0; j < offset + length; j++) {
|
||||||
rect.setRight(rect.right() + part.getCharacterWidth(j));
|
rect.setRight(rect.right() + part.getCharWidth(j));
|
||||||
}
|
}
|
||||||
|
|
||||||
painter.fillRect(rect, selectionColor);
|
painter.fillRect(rect, selectionColor);
|
||||||
@@ -648,7 +683,7 @@ void ChannelView::drawMessageSelection(QPainter &painter, messages::MessageRef *
|
|||||||
rect.setRight(part.getX());
|
rect.setRight(part.getX());
|
||||||
|
|
||||||
for (int j = 0; j < offset + length; j++) {
|
for (int j = 0; j < offset + length; j++) {
|
||||||
rect.setRight(rect.right() + part.getCharacterWidth(j));
|
rect.setRight(rect.right() + part.getCharWidth(j));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (this->selection.max.charIndex == charIndex) {
|
if (this->selection.max.charIndex == charIndex) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
#include <QPaintEvent>
|
#include <QPaintEvent>
|
||||||
#include <QScroller>
|
#include <QScroller>
|
||||||
|
#include <QTimer>
|
||||||
#include <QWheelEvent>
|
#include <QWheelEvent>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
@@ -88,6 +89,7 @@ public:
|
|||||||
~ChannelView();
|
~ChannelView();
|
||||||
|
|
||||||
void updateGifEmotes();
|
void updateGifEmotes();
|
||||||
|
void queueUpdate();
|
||||||
ScrollBar &getScrollBar();
|
ScrollBar &getScrollBar();
|
||||||
QString getSelectedText();
|
QString getSelectedText();
|
||||||
bool hasSelection();
|
bool hasSelection();
|
||||||
@@ -95,7 +97,7 @@ public:
|
|||||||
|
|
||||||
void setChannel(std::shared_ptr<Channel> channel);
|
void setChannel(std::shared_ptr<Channel> channel);
|
||||||
messages::LimitedQueueSnapshot<messages::SharedMessageRef> getMessagesSnapshot();
|
messages::LimitedQueueSnapshot<messages::SharedMessageRef> getMessagesSnapshot();
|
||||||
bool layoutMessages();
|
void layoutMessages();
|
||||||
|
|
||||||
void clearMessages();
|
void clearMessages();
|
||||||
|
|
||||||
@@ -122,8 +124,11 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
WindowManager &windowManager;
|
WindowManager &windowManager;
|
||||||
|
QTimer updateTimer;
|
||||||
|
bool updateQueued = false;
|
||||||
|
|
||||||
void detachChannel();
|
void detachChannel();
|
||||||
|
void actuallyLayoutMessages();
|
||||||
|
|
||||||
void drawMessages(QPainter &painter);
|
void drawMessages(QPainter &painter);
|
||||||
void updateMessageBuffer(messages::MessageRef *messageRef, QPixmap *buffer, int messageIndex);
|
void updateMessageBuffer(messages::MessageRef *messageRef, QPixmap *buffer, int messageIndex);
|
||||||
|
|||||||
@@ -167,14 +167,9 @@ bool ChatWidget::showChangeChannelPopup(const char *dialogTitle, bool empty)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatWidget::layoutMessages(bool forceUpdate)
|
void ChatWidget::layoutMessages()
|
||||||
{
|
{
|
||||||
this->view.layoutMessages();
|
this->view.layoutMessages();
|
||||||
this->view.update();
|
|
||||||
|
|
||||||
// if (this->view.layoutMessages() || forceUpdate) {
|
|
||||||
// this->view.update();
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatWidget::updateGifEmotes()
|
void ChatWidget::updateGifEmotes()
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public:
|
|||||||
void giveFocus(Qt::FocusReason reason);
|
void giveFocus(Qt::FocusReason reason);
|
||||||
bool hasFocus() const;
|
bool hasFocus() const;
|
||||||
|
|
||||||
void layoutMessages(bool forceUpdate = false);
|
void layoutMessages();
|
||||||
void updateGifEmotes();
|
void updateGifEmotes();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
Reference in New Issue
Block a user