Fix spoiler open link after String moving.

This commit is contained in:
John Preston 2022-09-23 11:58:37 +04:00
parent d57bf79ce0
commit 0f77143905
9 changed files with 138 additions and 48 deletions

View file

@ -166,6 +166,7 @@ PRIVATE
ui/text/text_parser.h ui/text/text_parser.h
ui/text/text_renderer.cpp ui/text/text_renderer.cpp
ui/text/text_renderer.h ui/text/text_renderer.h
ui/text/text_spoiler_data.cpp
ui/text/text_spoiler_data.h ui/text/text_spoiler_data.h
ui/text/text_utilities.cpp ui/text/text_utilities.cpp
ui/text/text_utilities.h ui/text/text_utilities.h

View file

@ -125,6 +125,30 @@ not_null<SpoilerMessCache*> DefaultSpoilerCache() {
return &data.cache; return &data.cache;
} }
String::SpoilerDataWrap::SpoilerDataWrap() noexcept = default;
String::SpoilerDataWrap::SpoilerDataWrap(SpoilerDataWrap &&other) noexcept
: data(std::move(other.data)) {
adjustFrom(&other);
}
String::SpoilerDataWrap &String::SpoilerDataWrap::operator=(
SpoilerDataWrap &&other) noexcept {
data = std::move(other.data);
adjustFrom(&other);
return *this;
}
void String::SpoilerDataWrap::adjustFrom(const SpoilerDataWrap *other) {
if (data && data->link) {
const auto raw = [](auto pointer) {
return reinterpret_cast<quintptr>(pointer);
};
data->link->setText(reinterpret_cast<String*>(
raw(data->link->text().get()) + raw(this) - raw(other)));
}
}
String::String(int32 minResizeWidth) String::String(int32 minResizeWidth)
: _minResizeWidth(minResizeWidth) { : _minResizeWidth(minResizeWidth) {
} }
@ -318,31 +342,36 @@ void String::setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk) {
} }
void String::setSpoilerRevealed(bool revealed, anim::type animated) { void String::setSpoilerRevealed(bool revealed, anim::type animated) {
if (!_spoiler) { const auto data = _spoiler.data.get();
if (!data) {
return; return;
} else if (_spoiler->revealed == revealed) { } else if (data->revealed == revealed) {
if (animated == anim::type::instant if (animated == anim::type::instant
&& _spoiler->revealAnimation.animating()) { && data->revealAnimation.animating()) {
_spoiler->revealAnimation.stop(); data->revealAnimation.stop();
_spoiler->animation.repaintCallback()(); data->animation.repaintCallback()();
} }
return; return;
} }
_spoiler->revealed = revealed; data->revealed = revealed;
if (animated == anim::type::instant) { if (animated == anim::type::instant) {
_spoiler->revealAnimation.stop(); data->revealAnimation.stop();
_spoiler->animation.repaintCallback()(); data->animation.repaintCallback()();
} else { } else {
_spoiler->revealAnimation.start( data->revealAnimation.start(
_spoiler->animation.repaintCallback(), data->animation.repaintCallback(),
revealed ? 0. : 1., revealed ? 0. : 1.,
revealed ? 1. : 0., revealed ? 1. : 0.,
st::fadeWrapDuration); st::fadeWrapDuration);
} }
} }
void String::setSpoilerLink(const ClickHandlerPtr &lnk) { void String::setSpoilerLinkFilter(Fn<bool(const ClickContext&)> filter) {
_spoiler->link = lnk; Expects(_spoiler.data != nullptr);
_spoiler.data->link = std::make_shared<SpoilerClickHandler>(
this,
std::move(filter));
} }
bool String::hasLinks() const { bool String::hasLinks() const {
@ -350,7 +379,7 @@ bool String::hasLinks() const {
} }
bool String::hasSpoilers() const { bool String::hasSpoilers() const {
return (_spoiler != nullptr); return (_spoiler.data != nullptr);
} }
bool String::hasSkipBlock() const { bool String::hasSkipBlock() const {
@ -762,9 +791,9 @@ void String::enumerateText(
// Ignore links that are partially copied. // Ignore links that are partially copied.
const auto handler = (spoilerFrom != rangeFrom const auto handler = (spoilerFrom != rangeFrom
|| blockFrom != rangeTo || blockFrom != rangeTo
|| !_spoiler) || !_spoiler.data)
? nullptr ? nullptr
: _spoiler->link; : _spoiler.data->link;
const auto type = EntityType::Spoiler; const auto type = EntityType::Spoiler;
clickHandlerFinishCallback(r, handler, type); clickHandlerFinishCallback(r, handler, type);
} }
@ -805,7 +834,7 @@ void String::enumerateText(
} }
bool String::hasPersistentAnimation() const { bool String::hasPersistentAnimation() const {
return _hasCustomEmoji || _spoiler; return _hasCustomEmoji || _spoiler.data;
} }
void String::unloadPersistentAnimation() { void String::unloadPersistentAnimation() {
@ -1018,7 +1047,7 @@ IsolatedEmoji String::toIsolatedEmoji() const {
auto result = IsolatedEmoji(); auto result = IsolatedEmoji();
const auto skip = (_blocks.empty() const auto skip = (_blocks.empty()
|| _blocks.back()->type() != TextBlockTSkip) ? 0 : 1; || _blocks.back()->type() != TextBlockTSkip) ? 0 : 1;
if ((_blocks.size() > kIsolatedEmojiLimit + skip) || _spoiler) { if ((_blocks.size() > kIsolatedEmojiLimit + skip) || hasSpoilers()) {
return {}; return {};
} }
auto index = 0; auto index = 0;
@ -1046,7 +1075,7 @@ void String::clear() {
void String::clearFields() { void String::clearFields() {
_blocks.clear(); _blocks.clear();
_links.clear(); _links.clear();
_spoiler = nullptr; _spoiler.data = nullptr;
_maxWidth = _minHeight = 0; _maxWidth = _minHeight = 0;
_startDir = Qt::LayoutDirectionAuto; _startDir = Qt::LayoutDirectionAuto;
} }

View file

@ -170,7 +170,7 @@ public:
[[nodiscard]] bool hasSpoilers() const; [[nodiscard]] bool hasSpoilers() const;
void setSpoilerRevealed(bool revealed, anim::type animated); void setSpoilerRevealed(bool revealed, anim::type animated);
void setSpoilerLink(const ClickHandlerPtr &lnk); void setSpoilerLinkFilter(Fn<bool(const ClickContext&)> filter);
[[nodiscard]] bool hasSkipBlock() const; [[nodiscard]] bool hasSkipBlock() const;
bool updateSkipBlock(int width, int height); bool updateSkipBlock(int width, int height);
@ -237,6 +237,19 @@ private:
using TextBlocks = std::vector<Block>; using TextBlocks = std::vector<Block>;
using TextLinks = QVector<ClickHandlerPtr>; using TextLinks = QVector<ClickHandlerPtr>;
class SpoilerDataWrap {
public:
SpoilerDataWrap() noexcept;
SpoilerDataWrap(SpoilerDataWrap &&other) noexcept;
SpoilerDataWrap &operator=(SpoilerDataWrap &&other) noexcept;
std::unique_ptr<SpoilerData> data;
private:
void adjustFrom(const SpoilerDataWrap *other);
};
uint16 countBlockEnd(const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const; uint16 countBlockEnd(const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const;
uint16 countBlockLength(const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const; uint16 countBlockLength(const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const;
@ -276,7 +289,7 @@ private:
Qt::LayoutDirection _startDir = Qt::LayoutDirectionAuto; Qt::LayoutDirection _startDir = Qt::LayoutDirectionAuto;
std::unique_ptr<SpoilerData> _spoiler; SpoilerDataWrap _spoiler;
friend class Parser; friend class Parser;
friend class Renderer; friend class Renderer;

View file

@ -207,14 +207,6 @@ void Parser::createBlock(int32 skipBack) {
} }
} }
// Unused.
// void Parser::createSkipBlock(int32 w, int32 h) {
// createBlock();
// _t->_text.push_back('_');
// _t->_blocks.push_back(Block::Skip(_t->_st->font, _t->_text, _blockStart++, w, h, _monoIndex ? _monoIndex : _lnkIndex, _spoilerIndex));
// blockCreated();
// }
void Parser::createNewlineBlock() { void Parser::createNewlineBlock() {
createBlock(); createBlock();
_t->_text.push_back(QChar::LineFeed); _t->_text.push_back(QChar::LineFeed);
@ -634,8 +626,8 @@ void Parser::finalize(const TextParseOptions &options) {
} }
} }
if (block->spoilerIndex()) { if (block->spoilerIndex()) {
if (!_t->_spoiler) { if (!_t->_spoiler.data) {
_t->_spoiler = std::make_unique<SpoilerData>( _t->_spoiler.data = std::make_unique<SpoilerData>(
Integration::Instance().createSpoilerRepaint(_context)); Integration::Instance().createSpoilerRepaint(_context));
} }
} }
@ -696,10 +688,10 @@ void Parser::finalize(const TextParseOptions &options) {
} }
lastHandlerIndex.lnk = realIndex; lastHandlerIndex.lnk = realIndex;
} }
if (!_t->_hasCustomEmoji || _t->_spoiler) { if (!_t->_hasCustomEmoji || _t->_spoiler.data) {
_t->_isOnlyCustomEmoji = false; _t->_isOnlyCustomEmoji = false;
} }
if (_t->_blocks.empty() || _t->_spoiler) { if (_t->_blocks.empty() || _t->_spoiler.data) {
_t->_isIsolatedEmoji = false; _t->_isIsolatedEmoji = false;
} }
_t->_links.squeeze(); _t->_links.squeeze();

View file

@ -56,7 +56,6 @@ private:
void trimSourceRange(); void trimSourceRange();
void blockCreated(); void blockCreated();
void createBlock(int32 skipBack = 0); void createBlock(int32 skipBack = 0);
// void createSkipBlock(int32 w, int32 h);
void createNewlineBlock(); void createNewlineBlock();
// Returns true if at least one entity was parsed in the current position. // Returns true if at least one entity was parsed in the current position.

View file

@ -154,7 +154,8 @@ bool Distinct(FixedRange a, FixedRange b) {
} }
Renderer::Renderer(const Ui::Text::String &t) Renderer::Renderer(const Ui::Text::String &t)
: _t(&t) { : _t(&t)
, _spoiler(_t->_spoiler.data.get()) {
} }
Renderer::~Renderer() { Renderer::~Renderer() {
@ -199,9 +200,9 @@ void Renderer::draw(QPainter &p, const PaintContext &context) {
_align = context.align; _align = context.align;
_cachedNow = context.now; _cachedNow = context.now;
_paused = context.paused; _paused = context.paused;
_spoilerOpacity = _t->_spoiler _spoilerOpacity = _spoiler
? (1. - _t->_spoiler->revealAnimation.value( ? (1. - _spoiler->revealAnimation.value(
_t->_spoiler->revealed ? 1. : 0.)) _spoiler->revealed ? 1. : 0.))
: 0.; : 0.;
enumerate(); enumerate();
} }
@ -1085,7 +1086,7 @@ void Renderer::pushSpoilerRange(
FixedRange range, FixedRange range,
FixedRange selected, FixedRange selected,
int currentBlockIndex) { int currentBlockIndex) {
if (!_background.spoiler || !_t->_spoiler) { if (!_background.spoiler || !_spoiler) {
return; return;
} }
const auto elided = (_indexOfElidedBlock == currentBlockIndex) const auto elided = (_indexOfElidedBlock == currentBlockIndex)
@ -1135,14 +1136,14 @@ void Renderer::fillSpoilerRects(
void Renderer::paintSpoilerRects() { void Renderer::paintSpoilerRects() {
Expects(_p != nullptr); Expects(_p != nullptr);
if (!_t->_spoiler) { if (!_spoiler) {
return; return;
} }
const auto opacity = _p->opacity(); const auto opacity = _p->opacity();
if (_spoilerOpacity < 1.) { if (_spoilerOpacity < 1.) {
_p->setOpacity(opacity * _spoilerOpacity); _p->setOpacity(opacity * _spoilerOpacity);
} }
const auto index = _t->_spoiler->animation.index(now(), _paused); const auto index = _spoiler->animation.index(now(), _paused);
paintSpoilerRects( paintSpoilerRects(
_spoilerRects, _spoilerRects,
_palette->spoilerFg, _palette->spoilerFg,
@ -1991,12 +1992,12 @@ void Renderer::applyBlockProperties(const AbstractBlock *block) {
if (_p) { if (_p) {
const auto isMono = IsMono(block->flags()); const auto isMono = IsMono(block->flags());
_background = {}; _background = {};
if (block->spoilerIndex() && _t->_spoiler) { if (block->spoilerIndex() && _spoiler) {
_background.spoiler = true; _background.spoiler = true;
} }
if (isMono if (isMono
&& block->lnkIndex() && block->lnkIndex()
&& (!_background.spoiler || _t->_spoiler->revealed)) { && (!_background.spoiler || _spoiler->revealed)) {
_background.selectActiveBlock = ClickHandler::showAsPressed( _background.selectActiveBlock = ClickHandler::showAsPressed(
_t->_links.at(block->lnkIndex() - 1)); _t->_links.at(block->lnkIndex() - 1));
} }
@ -2016,10 +2017,10 @@ void Renderer::applyBlockProperties(const AbstractBlock *block) {
} }
ClickHandlerPtr Renderer::lookupLink(const AbstractBlock *block) const { ClickHandlerPtr Renderer::lookupLink(const AbstractBlock *block) const {
const auto spoilerLink = (_t->_spoiler const auto spoilerLink = (_spoiler
&& !_t->_spoiler->revealed && !_spoiler->revealed
&& block->spoilerIndex()) && block->spoilerIndex())
? _t->_spoiler->link ? _spoiler->link
: ClickHandlerPtr(); : ClickHandlerPtr();
return (spoilerLink || !block->lnkIndex()) return (spoilerLink || !block->lnkIndex())
? spoilerLink ? spoilerLink

View file

@ -111,6 +111,7 @@ private:
const AbstractBlock *block) const; const AbstractBlock *block) const;
const String *_t = nullptr; const String *_t = nullptr;
SpoilerData *_spoiler = nullptr;
SpoilerMessCache *_spoilerCache = nullptr; SpoilerMessCache *_spoilerCache = nullptr;
QPainter *_p = nullptr; QPainter *_p = nullptr;
const style::TextPalette *_palette = nullptr; const style::TextPalette *_palette = nullptr;

View file

@ -0,0 +1,36 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "ui/text/text_spoiler_data.h"
#include "ui/text/text.h"
namespace Ui::Text {
SpoilerClickHandler::SpoilerClickHandler(
not_null<String*> text,
Fn<bool(const ClickContext&)> filter)
: _text(text)
, _filter(std::move(filter)) {
}
not_null<String*> SpoilerClickHandler::text() const {
return _text;
}
void SpoilerClickHandler::setText(not_null<String*> text) {
_text = text;
}
void SpoilerClickHandler::onClick(ClickContext context) const {
if (_filter && !_filter(context)) {
return;
}
_text->setSpoilerRevealed(true, anim::type::normal);
}
} // namespace Ui::Text

View file

@ -8,18 +8,36 @@
#include "ui/effects/spoiler_mess.h" #include "ui/effects/spoiler_mess.h"
#include "ui/effects/animations.h" #include "ui/effects/animations.h"
#include "ui/click_handler.h"
class SpoilerClickHandler;
namespace Ui::Text { namespace Ui::Text {
class String;
class SpoilerClickHandler final : public ClickHandler {
public:
SpoilerClickHandler(
not_null<String*> text,
Fn<bool(const ClickContext&)> filter);
[[nodiscard]] not_null<String*> text() const;
void setText(not_null<String*> text);
void onClick(ClickContext context) const override;
private:
not_null<String*> _text;
const Fn<bool(const ClickContext &)> _filter;
};
struct SpoilerData { struct SpoilerData {
explicit SpoilerData(Fn<void()> repaint) explicit SpoilerData(Fn<void()> repaint)
: animation(std::move(repaint)) { : animation(std::move(repaint)) {
} }
SpoilerAnimation animation; SpoilerAnimation animation;
ClickHandlerPtr link; std::shared_ptr<SpoilerClickHandler> link;
Animations::Simple revealAnimation; Animations::Simple revealAnimation;
bool revealed = false; bool revealed = false;
}; };