diff --git a/resources/images/collapse.png b/resources/images/collapse.png new file mode 100644 index 000000000..b181ccbf9 Binary files /dev/null and b/resources/images/collapse.png differ diff --git a/resources/resources.qrc b/resources/resources.qrc index 511243501..6ffc4de55 100644 --- a/resources/resources.qrc +++ b/resources/resources.qrc @@ -33,6 +33,7 @@ images/VSO_Link_blue_16x.png sounds/ping2.wav images/subscriber.png + images/collapse.png qt.conf diff --git a/src/messages/message.cpp b/src/messages/message.cpp index bdd136a23..1a47eb28a 100644 --- a/src/messages/message.cpp +++ b/src/messages/message.cpp @@ -61,6 +61,16 @@ const QString &Message::getId() const return this->id; } +bool Message::getCollapsedDefault() const +{ + return this->collapsedDefault; +} + +void Message::setCollapsedDefault(bool value) +{ + this->collapsedDefault = value; +} + namespace { void AddCurrentTimestamp(Message *message) diff --git a/src/messages/message.hpp b/src/messages/message.hpp index b6600549d..aa036749b 100644 --- a/src/messages/message.hpp +++ b/src/messages/message.hpp @@ -27,6 +27,8 @@ public: std::vector &getWords(); bool isDisabled() const; const QString &getId() const; + bool getCollapsedDefault() const; + void setCollapsedDefault(bool value); QString loginName; QString displayName; @@ -57,6 +59,9 @@ private: QString timeoutUser = ""; int timeoutCount = 0; bool disabled = false; + + bool collapsedDefault = false; + std::chrono::time_point parseTime; QString content; diff --git a/src/messages/messageref.cpp b/src/messages/messageref.cpp index 115a918e6..5a632a493 100644 --- a/src/messages/messageref.cpp +++ b/src/messages/messageref.cpp @@ -4,10 +4,10 @@ #include -#define MARGIN_LEFT 8 -#define MARGIN_RIGHT 8 -#define MARGIN_TOP 4 -#define MARGIN_BOTTOM 4 +#define MARGIN_LEFT (int)(8 * this->dpiMultiplier) +#define MARGIN_RIGHT (int)(8 * this->dpiMultiplier) +#define MARGIN_TOP (int)(4 * this->dpiMultiplier) +#define MARGIN_BOTTOM (int)(4 * this->dpiMultiplier) using namespace chatterino::messages; @@ -17,6 +17,7 @@ namespace messages { MessageRef::MessageRef(SharedMessage _message) : message(_message) , wordParts() + , collapsed(_message->getCollapsedDefault()) { } @@ -59,9 +60,9 @@ bool MessageRef::layout(int width, float dpiMultiplyer) this->currentWordTypes = SettingsManager::getInstance().getWordTypeMask(); // check if dpi changed - bool dpiChanged = this->dpiMultiplyer != dpiMultiplyer; + bool dpiChanged = this->dpiMultiplier != dpiMultiplyer; layoutRequired |= dpiChanged; - this->dpiMultiplyer = dpiMultiplyer; + this->dpiMultiplier = dpiMultiplyer; imagesChanged |= dpiChanged; textChanged |= dpiChanged; @@ -105,9 +106,13 @@ void MessageRef::actuallyLayout(int width) 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) { @@ -132,7 +137,7 @@ void MessageRef::actuallyLayout(int width) // word wrapping if (word.isText() && word.getWidth() + MARGIN_LEFT > right) { // align and end the current line - alignWordParts(lineStart, lineHeight, width); + alignWordParts(lineStart, lineHeight, width, firstLineHeight); y += lineHeight; int currentPartStart = 0; @@ -187,7 +192,7 @@ void MessageRef::actuallyLayout(int width) // doesn't fit in the line else { // align and end the current line - alignWordParts(lineStart, lineHeight, width); + alignWordParts(lineStart, lineHeight, width, firstLineHeight); y += lineHeight; @@ -206,11 +211,19 @@ void MessageRef::actuallyLayout(int width) } // align and end the current line - alignWordParts(lineStart, lineHeight, width); + alignWordParts(lineStart, lineHeight, width, firstLineHeight); + + this->collapsedHeight = firstLineHeight == -1 ? (int)(24 * dpiMultiplier) + : firstLineHeight + MARGIN_TOP + MARGIN_BOTTOM; // update height int oldHeight = this->height; - this->height = y + lineHeight + MARGIN_BOTTOM; + + if (this->isCollapsed()) { + this->height = this->collapsedHeight; + } else { + this->height = y + lineHeight + MARGIN_BOTTOM; + } // invalidate buffer if height changed if (oldHeight != this->height) { @@ -227,8 +240,8 @@ void MessageRef::updateTextSizes() continue; QFontMetrics &metrics = word.getFontMetrics(); - word.setSize((int)(metrics.width(word.getText()) * this->dpiMultiplyer), - (int)(metrics.height() * this->dpiMultiplyer)); + word.setSize((int)(metrics.width(word.getText()) * this->dpiMultiplier), + (int)(metrics.height() * this->dpiMultiplier)); } } @@ -236,7 +249,7 @@ void MessageRef::updateImageSizes() { const int mediumTextLineHeight = FontManager::getInstance().getFontMetrics(FontManager::Medium).height(); - const qreal emoteScale = SettingsManager::getInstance().emoteScale.get() * this->dpiMultiplyer; + const qreal emoteScale = SettingsManager::getInstance().emoteScale.get() * this->dpiMultiplier; const bool scaleEmotesByLineHeight = SettingsManager::getInstance().scaleEmotesByLineHeight; for (auto &word : this->message->getWords()) { @@ -262,8 +275,12 @@ const std::vector &MessageRef::getWordParts() const return this->wordParts; } -void MessageRef::alignWordParts(int lineStart, int lineHeight, int width) +void MessageRef::alignWordParts(int lineStart, int lineHeight, int width, int &firstLineHeight) { + if (firstLineHeight == -1) { + firstLineHeight = lineHeight; + } + int xOffset = 0; if (this->message->centered && this->wordParts.size() > 0) { @@ -399,5 +416,22 @@ int MessageRef::getSelectionIndex(QPoint position) 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; +} } // namespace messages } // namespace chatterino diff --git a/src/messages/messageref.hpp b/src/messages/messageref.hpp index 76032761e..48c0ea7cd 100644 --- a/src/messages/messageref.hpp +++ b/src/messages/messageref.hpp @@ -22,7 +22,7 @@ public: Message *getMessage(); int getHeight() const; - bool layout(int width, float dpiMultiplyer); + bool layout(int width, float dpiMultiplier); const std::vector &getWordParts() const; @@ -33,6 +33,10 @@ public: int getLastCharacterIndex() const; int getSelectionIndex(QPoint position); + bool isCollapsed() const; + void setCollapsed(bool value); + int getCollapsedHeight() const; + private: // variables SharedMessage message; @@ -43,13 +47,16 @@ private: int currentLayoutWidth = -1; int fontGeneration = -1; int emoteGeneration = -1; - float dpiMultiplyer = -1; + float dpiMultiplier = -1; Word::Type currentWordTypes = Word::None; + bool collapsed; + int collapsedHeight = 32; + // methods void actuallyLayout(int width); - void alignWordParts(int lineStart, int lineHeight, int width); + void alignWordParts(int lineStart, int lineHeight, int width, int &firstLineHeight); void updateTextSizes(); void updateImageSizes(); }; diff --git a/src/messages/word.hpp b/src/messages/word.hpp index 9c8075d0d..caf6878e8 100644 --- a/src/messages/word.hpp +++ b/src/messages/word.hpp @@ -80,6 +80,9 @@ public: 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 diff --git a/src/resources.cpp b/src/resources.cpp index 350df4d3f..2f620390a 100644 --- a/src/resources.cpp +++ b/src/resources.cpp @@ -26,6 +26,7 @@ Resources::Resources() , badgePremium(lli(":/images/twitchprime_bg.png")) , badgeVerified(lli(":/images/verified.png", 0.25)) , badgeSubscriber(lli(":/images/subscriber.png", 0.25)) + , badgeCollapsed(lli(":/images/collapse.png")) , cheerBadge100000(lli(":/images/cheer100000")) , cheerBadge10000(lli(":/images/cheer10000")) , cheerBadge5000(lli(":/images/cheer5000")) diff --git a/src/resources.hpp b/src/resources.hpp index ac918ec8a..6288d5a69 100644 --- a/src/resources.hpp +++ b/src/resources.hpp @@ -22,6 +22,7 @@ public: messages::LazyLoadedImage *badgePremium; messages::LazyLoadedImage *badgeVerified; messages::LazyLoadedImage *badgeSubscriber; + messages::LazyLoadedImage *badgeCollapsed; messages::LazyLoadedImage *cheerBadge100000; messages::LazyLoadedImage *cheerBadge10000; diff --git a/src/twitch/twitchmessagebuilder.cpp b/src/twitch/twitchmessagebuilder.cpp index 1acd3be77..41295b890 100644 --- a/src/twitch/twitchmessagebuilder.cpp +++ b/src/twitch/twitchmessagebuilder.cpp @@ -41,6 +41,10 @@ SharedMessage TwitchMessageBuilder::parse() this->parseUsername(); + // this->message->setCollapsedDefault(true); + // this->appendWord(Word(this->resources.badgeCollapsed, Word::Collapsed, QString(), + // QString())); + // The timestamp is always appended to the builder // Whether or not will be rendered is decided/checked later this->appendTimestamp(); diff --git a/src/widgets/helper/channelview.cpp b/src/widgets/helper/channelview.cpp index 89c13909c..4e7e98cdf 100644 --- a/src/widgets/helper/channelview.cpp +++ b/src/widgets/helper/channelview.cpp @@ -729,11 +729,19 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event) QPoint relativePos; int messageIndex; + // no message under cursor if (!tryGetMessageAt(event->pos(), message, relativePos, messageIndex)) { - setCursor(Qt::ArrowCursor); + this->setCursor(Qt::ArrowCursor); return; } + // message under cursor is collapsed + if (message->isCollapsed()) { + this->setCursor(Qt::PointingHandCursor); + return; + } + + // is selecting if (this->selecting) { int index = message->getSelectionIndex(relativePos); @@ -742,16 +750,18 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event) this->repaint(); } + // check if word underneath cursor const messages::Word *hoverWord; if ((hoverWord = message->tryGetWordPart(relativePos)) == nullptr) { - setCursor(Qt::ArrowCursor); + this->setCursor(Qt::ArrowCursor); return; } + // check if word has a link if (hoverWord->getLink().isValid()) { - setCursor(Qt::PointingHandCursor); + this->setCursor(Qt::PointingHandCursor); } else { - setCursor(Qt::ArrowCursor); + this->setCursor(Qt::ArrowCursor); } } @@ -787,6 +797,11 @@ void ChannelView::mousePressEvent(QMouseEvent *event) return; } + // check if message is collapsed + if (message->isCollapsed()) { + return; + } + int index = message->getSelectionIndex(relativePos); auto selectionItem = SelectionItem(messageIndex, index); @@ -831,6 +846,13 @@ void ChannelView::mouseReleaseEvent(QMouseEvent *event) return; } + // message under cursor is collapsed + if (message->isCollapsed()) { + message->setCollapsed(false); + this->layoutMessages(); + return; + } + const messages::Word *hoverWord; if ((hoverWord = message->tryGetWordPart(relativePos)) == nullptr) {