Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Formalize zero-width emote implementation #4314

Merged
merged 38 commits into from
Mar 18, 2023
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
350c894
wip 1: mostly working
dnsge Jan 18, 2023
8783b5d
wip 2: center and scale layered emotes properly
dnsge Jan 18, 2023
8b48dba
wip 3: skip static once encountering animated layer
dnsge Jan 18, 2023
3e3db97
wip 4: almost 100% working tooltips
dnsge Jan 18, 2023
8ce5eb2
wip 5: cleanup and try to get horizontal tooltip centering
dnsge Jan 18, 2023
d6a124e
wip 6: remove todo
dnsge Jan 18, 2023
c5b1cc9
wip 7: change builder method visibility
dnsge Jan 18, 2023
9d303f1
wip 8: remove ZeroWidthEmote flag
dnsge Jan 18, 2023
a533ff2
wip 9: rename some methods and structs
dnsge Jan 18, 2023
97ae476
wip 10: vertical and grid layout for multiple zero-width emotes
dnsge Jan 19, 2023
475f214
wip 11: give each emote layer a context menu entry
dnsge Jan 19, 2023
160f267
wip 12: add 'enable zero-width emotes' setting
dnsge Jan 19, 2023
09bfc16
Update CHANGELOG.md
dnsge Jan 19, 2023
a6bba8e
Clean up visibility from copy/paste
dnsge Jan 19, 2023
62edb0c
Fix tooltip content margin
dnsge Jan 19, 2023
9de74dd
Adjust tooltip entry alignment and spacing
dnsge Jan 19, 2023
7fca079
Merge branch 'master' into feat/better-layered-emotes
dnsge Jan 19, 2023
a275d67
Properly set and consider attemptRefresh
dnsge Jan 20, 2023
daffad4
Align emote layers to bottom center instead of true center
dnsge Jan 20, 2023
21dccc0
Add settings description and tweak changelog
dnsge Jan 23, 2023
a95c057
Merge branch 'master' into feat/better-layered-emotes
dnsge Jan 23, 2023
37b04f6
Merge branch 'master' into feat/better-layered-emotes
dnsge Jan 31, 2023
7d688d3
Merge branch 'master' into feat/better-layered-emotes
dnsge Feb 13, 2023
bda4591
Merge branch 'master' into feat/better-layered-emotes
dnsge Mar 2, 2023
4ffed6c
Fix changelog entry position
dnsge Mar 2, 2023
3b2f5ef
Rename empty() to isEmpty()
dnsge Mar 2, 2023
427666a
Clarify getBindingSize -> getBoundingBoxSize
dnsge Mar 2, 2023
42c77a6
Extract unique emote code
dnsge Mar 2, 2023
b3b751c
Replace instances of "record" with "entry"
dnsge Mar 2, 2023
a718f77
Add some comments
dnsge Mar 2, 2023
51ff1c3
Remove unused constructor and add nullptr defaults
dnsge Mar 2, 2023
b01f83e
Move `alignRectBottomCenter` to outside of chatterino's namespace
pajlada Mar 18, 2023
52e0f13
Merge remote-tracking branch 'origin/master' into feat/better-layered…
pajlada Mar 18, 2023
6cf76fb
Switch zeroWidth & zeroWidthSetting checks around
pajlada Mar 18, 2023
d593066
Inverse 'setVisibleEntries' logic
pajlada Mar 18, 2023
2e02e45
continue if entry is a nullptr
pajlada Mar 18, 2023
017c473
Add parent to emote submenus
pajlada Mar 18, 2023
a5e34cb
Merge remote-tracking branch 'origin/master' into feat/better-layered…
pajlada Mar 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
- Bugfix: Fixed scrollbar highlight colors when changing message history limit. (#4288)
- Bugfix: Fixed the split "Search" menu action not opening the correct search window. (#4305)
- Bugfix: Fixed an issue on Windows when opening links in incognito mode that contained forward slashes in hash (#4307)
- Bugfix: Fixed an issue where animated emotes would render on top of zero-width emotes. (#4314)
- Bugfix: Fixed an issue where it was difficult to hover a zero-width emote. (#4314)
dnsge marked this conversation as resolved.
Show resolved Hide resolved
- Dev: Remove protocol from QApplication's Organization Domain (so changed from `https://www.chatterino.com` to `chatterino.com`). (#4256)
- Dev: Ignore `WM_SHOWWINDOW` hide events, causing fewer attempted rescales. (#4198)
- Dev: Migrated to C++ 20 (#4252, #4257)
Expand Down
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,8 @@ set(SOURCE_FILES
widgets/Scrollbar.hpp
widgets/StreamView.cpp
widgets/StreamView.hpp
widgets/TooltipEntryWidget.cpp
widgets/TooltipEntryWidget.hpp
widgets/TooltipWidget.cpp
widgets/TooltipWidget.hpp
widgets/Window.cpp
Expand Down
19 changes: 19 additions & 0 deletions src/messages/MessageBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,25 @@ void MessageBuilder::append(std::unique_ptr<MessageElement> element)
this->message().elements.push_back(std::move(element));
}

bool MessageBuilder::empty()
{
return this->message().elements.empty();
}

MessageElement &MessageBuilder::back()
{
return *this->message().elements.back();
dnsge marked this conversation as resolved.
Show resolved Hide resolved
}

std::unique_ptr<MessageElement> MessageBuilder::releaseBack()
{
assert(!this->empty());

auto ptr = std::move(this->message().elements.back());
this->message().elements.pop_back();
return ptr;
}

QString MessageBuilder::matchLink(const QString &string)
{
LinkParser linkParser(string);
Expand Down
4 changes: 4 additions & 0 deletions src/messages/MessageBuilder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ class MessageBuilder
virtual void addTextOrEmoji(EmotePtr emote);
virtual void addTextOrEmoji(const QString &value);

bool empty();
dnsge marked this conversation as resolved.
Show resolved Hide resolved
MessageElement &back();
std::unique_ptr<MessageElement> releaseBack();

MessageColor textColor_ = MessageColor::Text;

private:
Expand Down
156 changes: 156 additions & 0 deletions src/messages/MessageElement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,22 @@

namespace chatterino {

namespace {

QSize getBindingSize(std::vector<ImagePtr> &images)
dnsge marked this conversation as resolved.
Show resolved Hide resolved
{
int width = 0, height = 0;
dnsge marked this conversation as resolved.
Show resolved Hide resolved
for (auto &img : images)
dnsge marked this conversation as resolved.
Show resolved Hide resolved
{
width = std::max(width, img->width());
height = std::max(height, img->height());
}

return QSize(width, height);
}

} // namespace

MessageElement::MessageElement(MessageElementFlags flags)
: flags_(flags)
{
Expand Down Expand Up @@ -216,6 +232,146 @@ MessageLayoutElement *EmoteElement::makeImageLayoutElement(
return new ImageLayoutElement(*this, image, size);
}

LayeredEmoteElement::LayeredEmoteElement(std::vector<EmotePtr> &&emotes,
dnsge marked this conversation as resolved.
Show resolved Hide resolved
pajlada marked this conversation as resolved.
Show resolved Hide resolved
MessageElementFlags flags,
dnsge marked this conversation as resolved.
Show resolved Hide resolved
const MessageColor &textElementColor)
: MessageElement(flags)
, emotes_(std::move(emotes))
, textElementColor_(textElementColor)
{
this->updateTooltips();
}

void LayeredEmoteElement::addEmoteLayer(const EmotePtr &emote)
{
this->emotes_.push_back(emote);
this->updateTooltips();
}

void LayeredEmoteElement::addToContainer(MessageLayoutContainer &container,
MessageElementFlags flags)
dnsge marked this conversation as resolved.
Show resolved Hide resolved
{
if (flags.hasAny(this->getFlags()))
{
if (flags.has(MessageElementFlag::EmoteImages))
{
auto images = this->getLoadedImages(container.getScale());
if (images.empty())
{
return;
}

auto emoteScale = getSettings()->emoteScale.getValue();
float overallScale = emoteScale * container.getScale();
dnsge marked this conversation as resolved.
Show resolved Hide resolved

auto largestSize = getBindingSize(images) * overallScale;
std::vector<QSize> individualSizes;
individualSizes.reserve(this->emotes_.size());
for (auto img : images)
{
individualSizes.push_back(QSize(img->width(), img->height()) *
overallScale);
}

container.addElement(this->makeImageLayoutElement(
images, individualSizes, largestSize)
->setLink(this->getLink()));
}
else
{
if (this->textElement_)
{
this->textElement_->addToContainer(container,
MessageElementFlag::Misc);
}
}
}
}

std::vector<ImagePtr> LayeredEmoteElement::getLoadedImages(float scale)
{
std::vector<ImagePtr> res;
res.reserve(this->emotes_.size());

for (auto emote : this->emotes_)
{
auto image = emote->images.getImageOrLoaded(scale);
if (image->isEmpty())
{
continue;
}
res.push_back(image);
}
return res;
}

MessageLayoutElement *LayeredEmoteElement::makeImageLayoutElement(
const std::vector<ImagePtr> &images, const std::vector<QSize> &sizes,
QSize largestSize)
{
return new LayeredImageLayoutElement(*this, images, sizes, largestSize);
}

void LayeredEmoteElement::updateTooltips()
{
if (!this->emotes_.empty())
{
QString copyStr = this->getCopyString();
dnsge marked this conversation as resolved.
Show resolved Hide resolved
this->textElement_.reset(new TextElement(
copyStr, MessageElementFlag::Misc, this->textElementColor_));
this->setTooltip(copyStr);
}

std::vector<QString> result;
result.reserve(this->emotes_.size());

for (auto &emote : this->emotes_)
{
result.push_back(emote->tooltip.string);
}

this->emoteTooltips_ = std::move(result);
}

const std::vector<QString> &LayeredEmoteElement::getEmoteTooltips() const
{
return this->emoteTooltips_;
}

QString LayeredEmoteElement::getCleanCopyString() const
{
QString result;
dnsge marked this conversation as resolved.
Show resolved Hide resolved
for (size_t i = 0; i < this->emotes_.size(); ++i)
{
if (i != 0)
{
result += " ";
}
result +=
TwitchEmotes::cleanUpEmoteCode(this->emotes_[i]->getCopyString());
}
return result;
}

QString LayeredEmoteElement::getCopyString() const
{
QString result;
dnsge marked this conversation as resolved.
Show resolved Hide resolved
for (size_t i = 0; i < this->emotes_.size(); ++i)
{
if (i != 0)
{
result += " ";
}
result += this->emotes_[i]->getCopyString();
}
return result;
}

const std::vector<EmotePtr> &LayeredEmoteElement::getEmotes() const
{
return this->emotes_;
}

// BADGE
BadgeElement::BadgeElement(const EmotePtr &emote, MessageElementFlags flags)
: MessageElement(flags)
Expand Down
36 changes: 33 additions & 3 deletions src/messages/MessageElement.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,7 @@ enum class MessageElementFlag : int64_t {
LowercaseLink = (1LL << 29),
OriginalLink = (1LL << 30),

// ZeroWidthEmotes are emotes that are supposed to overlay over any pre-existing emotes
// e.g. BTTV's SoSnowy during christmas season or 7TV's RainTime
ZeroWidthEmote = (1LL << 31),
// Unused: (1LL << 31)
dnsge marked this conversation as resolved.
Show resolved Hide resolved

// for elements of the message reply
RepliedMessage = (1LL << 32),
Expand Down Expand Up @@ -321,6 +319,38 @@ class EmoteElement : public MessageElement
EmotePtr emote_;
};

class LayeredEmoteElement : public MessageElement
dnsge marked this conversation as resolved.
Show resolved Hide resolved
{
public:
LayeredEmoteElement(
std::vector<EmotePtr> &&emotes, MessageElementFlags flags,
const MessageColor &textElementColor = MessageColor::Text);

void addEmoteLayer(const EmotePtr &emote);

void addToContainer(MessageLayoutContainer &container,
MessageElementFlags flags) override;

QString getCleanCopyString() const;
dnsge marked this conversation as resolved.
Show resolved Hide resolved
const std::vector<EmotePtr> &getEmotes() const;
const std::vector<QString> &getEmoteTooltips() const;

private:
MessageLayoutElement *makeImageLayoutElement(
const std::vector<ImagePtr> &image, const std::vector<QSize> &sizes,
QSize largestSize);

QString getCopyString() const;
void updateTooltips();
std::vector<ImagePtr> getLoadedImages(float scale);

std::vector<EmotePtr> emotes_;
std::vector<QString> emoteTooltips_;

std::unique_ptr<TextElement> textElement_;
MessageColor textElementColor_;
};

class BadgeElement : public MessageElement
{
public:
Expand Down
30 changes: 8 additions & 22 deletions src/messages/layouts/MessageLayoutContainer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,7 @@ void MessageLayoutContainer::clear()

void MessageLayoutContainer::addElement(MessageLayoutElement *element)
{
bool isZeroWidth =
element->getFlags().has(MessageElementFlag::ZeroWidthEmote);

if (!isZeroWidth && !this->fitsInLine(element->getRect().width()))
if (!this->fitsInLine(element->getRect().width()))
{
this->breakLine();
}
Expand Down Expand Up @@ -175,14 +172,6 @@ void MessageLayoutContainer::_addElement(MessageLayoutElement *element,
this->lineHeight_ = std::max(this->lineHeight_, elementLineHeight);

auto xOffset = 0;
bool isZeroWidthEmote = element->getCreator().getFlags().has(
MessageElementFlag::ZeroWidthEmote);

if (isZeroWidthEmote && !isRTLMode)
{
xOffset -= element->getRect().width() + this->spaceWidth_;
}

auto yOffset = 0;

if (element->getCreator().getFlags().has(
Expand All @@ -195,7 +184,7 @@ void MessageLayoutContainer::_addElement(MessageLayoutElement *element,

if (getSettings()->removeSpacesBetweenEmotes &&
element->getFlags().hasAny({MessageElementFlag::EmoteImages}) &&
!isZeroWidthEmote && shouldRemoveSpaceBetweenEmotes())
shouldRemoveSpaceBetweenEmotes())
{
// Move cursor one 'space width' to the left (right in case of RTL) to combine hug the previous emote
if (isRTLMode)
Expand Down Expand Up @@ -230,16 +219,13 @@ void MessageLayoutContainer::_addElement(MessageLayoutElement *element,
}

// set current x
if (!isZeroWidthEmote)
if (isRTLMode)
{
if (isRTLMode)
{
this->currentX_ -= element->getRect().width();
}
else
{
this->currentX_ += element->getRect().width();
}
this->currentX_ -= element->getRect().width();
}
else
{
this->currentX_ += element->getRect().width();
}

if (element->hasTrailingSpace())
Expand Down
Loading