Formalize zero-width emote implementation (#4314)

Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
Daniel Sage
2023-03-18 12:30:08 -04:00
committed by GitHub
parent db97a14cdc
commit 0acbc0d2c3
18 changed files with 947 additions and 146 deletions
@@ -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();
}
@@ -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(
@@ -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)
@@ -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())
@@ -15,6 +15,14 @@
namespace {
const QChar RTL_EMBED(0x202B);
void alignRectBottomCenter(QRectF &rect, const QRectF &reference)
{
QPointF newCenter(reference.center().x(),
reference.bottom() - (rect.height() / 2.0));
rect.moveCenter(newCenter);
}
} // namespace
namespace chatterino {
@@ -184,6 +192,133 @@ int ImageLayoutElement::getXFromIndex(int index)
}
}
//
// LAYERED IMAGE
//
LayeredImageLayoutElement::LayeredImageLayoutElement(
MessageElement &creator, std::vector<ImagePtr> images,
std::vector<QSize> sizes, QSize largestSize)
: MessageLayoutElement(creator, largestSize)
, images_(std::move(images))
, sizes_(std::move(sizes))
{
assert(this->images_.size() == this->sizes_.size());
this->trailingSpace = creator.hasTrailingSpace();
}
void LayeredImageLayoutElement::addCopyTextToString(QString &str, uint32_t from,
uint32_t to) const
{
const auto *layeredEmoteElement =
dynamic_cast<LayeredEmoteElement *>(&this->getCreator());
if (layeredEmoteElement)
{
// cleaning is taken care in call
str += layeredEmoteElement->getCleanCopyString();
if (this->hasTrailingSpace())
{
str += " ";
}
}
}
int LayeredImageLayoutElement::getSelectionIndexCount() const
{
return this->trailingSpace ? 2 : 1;
}
void LayeredImageLayoutElement::paint(QPainter &painter)
{
auto fullRect = QRectF(this->getRect());
for (size_t i = 0; i < this->images_.size(); ++i)
{
auto &img = this->images_[i];
if (img == nullptr)
{
continue;
}
auto pixmap = img->pixmapOrLoad();
if (img->animated())
{
// As soon as we see an animated emote layer, we can stop rendering
// the static emotes. The paintAnimated function will render any
// static emotes layered on top of the first seen animated emote.
return;
}
if (pixmap)
{
// Matching the web chat behavior, we center the emote within the overall
// binding box. E.g. small overlay emotes like cvMask will sit in the direct
// center of even wide emotes.
auto &size = this->sizes_[i];
QRectF destRect(0, 0, size.width(), size.height());
alignRectBottomCenter(destRect, fullRect);
painter.drawPixmap(destRect, *pixmap, QRectF());
}
}
}
void LayeredImageLayoutElement::paintAnimated(QPainter &painter, int yOffset)
{
auto fullRect = QRectF(this->getRect());
fullRect.moveTop(fullRect.y() + yOffset);
bool animatedFlag = false;
for (size_t i = 0; i < this->images_.size(); ++i)
{
auto &img = this->images_[i];
if (img == nullptr)
{
continue;
}
// If we have a static emote layered on top of an animated emote, we need
// to render the static emote again after animating anything below it.
if (img->animated() || animatedFlag)
{
if (auto pixmap = img->pixmapOrLoad())
{
// Matching the web chat behavior, we center the emote within the overall
// binding box. E.g. small overlay emotes like cvMask will sit in the direct
// center of even wide emotes.
auto &size = this->sizes_[i];
QRectF destRect(0, 0, size.width(), size.height());
alignRectBottomCenter(destRect, fullRect);
painter.drawPixmap(destRect, *pixmap, QRectF());
animatedFlag = true;
}
}
}
}
int LayeredImageLayoutElement::getMouseOverIndex(const QPoint &abs) const
{
return 0;
}
int LayeredImageLayoutElement::getXFromIndex(int index)
{
if (index <= 0)
{
return this->getRect().left();
}
else if (index == 1)
{
// fourtf: remove space width
return this->getRect().right();
}
else
{
return this->getRect().right();
}
}
//
// IMAGE WITH BACKGROUND
//
@@ -83,6 +83,26 @@ protected:
ImagePtr image_;
};
class LayeredImageLayoutElement : public MessageLayoutElement
{
public:
LayeredImageLayoutElement(MessageElement &creator,
std::vector<ImagePtr> images,
std::vector<QSize> sizes, QSize largestSize);
protected:
void addCopyTextToString(QString &str, uint32_t from = 0,
uint32_t to = UINT32_MAX) const override;
int getSelectionIndexCount() const override;
void paint(QPainter &painter) override;
void paintAnimated(QPainter &painter, int yOffset) override;
int getMouseOverIndex(const QPoint &abs) const override;
int getXFromIndex(int index) override;
std::vector<ImagePtr> images_;
std::vector<QSize> sizes_;
};
class ImageWithBackgroundLayoutElement : public ImageLayoutElement
{
public: