Fix spoiler open link after String moving.
This commit is contained in:
parent
d57bf79ce0
commit
0f77143905
9 changed files with 138 additions and 48 deletions
|
|
@ -166,6 +166,7 @@ PRIVATE
|
|||
ui/text/text_parser.h
|
||||
ui/text/text_renderer.cpp
|
||||
ui/text/text_renderer.h
|
||||
ui/text/text_spoiler_data.cpp
|
||||
ui/text/text_spoiler_data.h
|
||||
ui/text/text_utilities.cpp
|
||||
ui/text/text_utilities.h
|
||||
|
|
|
|||
|
|
@ -125,6 +125,30 @@ not_null<SpoilerMessCache*> DefaultSpoilerCache() {
|
|||
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)
|
||||
: _minResizeWidth(minResizeWidth) {
|
||||
}
|
||||
|
|
@ -318,31 +342,36 @@ void String::setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk) {
|
|||
}
|
||||
|
||||
void String::setSpoilerRevealed(bool revealed, anim::type animated) {
|
||||
if (!_spoiler) {
|
||||
const auto data = _spoiler.data.get();
|
||||
if (!data) {
|
||||
return;
|
||||
} else if (_spoiler->revealed == revealed) {
|
||||
} else if (data->revealed == revealed) {
|
||||
if (animated == anim::type::instant
|
||||
&& _spoiler->revealAnimation.animating()) {
|
||||
_spoiler->revealAnimation.stop();
|
||||
_spoiler->animation.repaintCallback()();
|
||||
&& data->revealAnimation.animating()) {
|
||||
data->revealAnimation.stop();
|
||||
data->animation.repaintCallback()();
|
||||
}
|
||||
return;
|
||||
}
|
||||
_spoiler->revealed = revealed;
|
||||
data->revealed = revealed;
|
||||
if (animated == anim::type::instant) {
|
||||
_spoiler->revealAnimation.stop();
|
||||
_spoiler->animation.repaintCallback()();
|
||||
data->revealAnimation.stop();
|
||||
data->animation.repaintCallback()();
|
||||
} else {
|
||||
_spoiler->revealAnimation.start(
|
||||
_spoiler->animation.repaintCallback(),
|
||||
data->revealAnimation.start(
|
||||
data->animation.repaintCallback(),
|
||||
revealed ? 0. : 1.,
|
||||
revealed ? 1. : 0.,
|
||||
st::fadeWrapDuration);
|
||||
}
|
||||
}
|
||||
|
||||
void String::setSpoilerLink(const ClickHandlerPtr &lnk) {
|
||||
_spoiler->link = lnk;
|
||||
void String::setSpoilerLinkFilter(Fn<bool(const ClickContext&)> filter) {
|
||||
Expects(_spoiler.data != nullptr);
|
||||
|
||||
_spoiler.data->link = std::make_shared<SpoilerClickHandler>(
|
||||
this,
|
||||
std::move(filter));
|
||||
}
|
||||
|
||||
bool String::hasLinks() const {
|
||||
|
|
@ -350,7 +379,7 @@ bool String::hasLinks() const {
|
|||
}
|
||||
|
||||
bool String::hasSpoilers() const {
|
||||
return (_spoiler != nullptr);
|
||||
return (_spoiler.data != nullptr);
|
||||
}
|
||||
|
||||
bool String::hasSkipBlock() const {
|
||||
|
|
@ -762,9 +791,9 @@ void String::enumerateText(
|
|||
// Ignore links that are partially copied.
|
||||
const auto handler = (spoilerFrom != rangeFrom
|
||||
|| blockFrom != rangeTo
|
||||
|| !_spoiler)
|
||||
|| !_spoiler.data)
|
||||
? nullptr
|
||||
: _spoiler->link;
|
||||
: _spoiler.data->link;
|
||||
const auto type = EntityType::Spoiler;
|
||||
clickHandlerFinishCallback(r, handler, type);
|
||||
}
|
||||
|
|
@ -805,7 +834,7 @@ void String::enumerateText(
|
|||
}
|
||||
|
||||
bool String::hasPersistentAnimation() const {
|
||||
return _hasCustomEmoji || _spoiler;
|
||||
return _hasCustomEmoji || _spoiler.data;
|
||||
}
|
||||
|
||||
void String::unloadPersistentAnimation() {
|
||||
|
|
@ -1018,7 +1047,7 @@ IsolatedEmoji String::toIsolatedEmoji() const {
|
|||
auto result = IsolatedEmoji();
|
||||
const auto skip = (_blocks.empty()
|
||||
|| _blocks.back()->type() != TextBlockTSkip) ? 0 : 1;
|
||||
if ((_blocks.size() > kIsolatedEmojiLimit + skip) || _spoiler) {
|
||||
if ((_blocks.size() > kIsolatedEmojiLimit + skip) || hasSpoilers()) {
|
||||
return {};
|
||||
}
|
||||
auto index = 0;
|
||||
|
|
@ -1046,7 +1075,7 @@ void String::clear() {
|
|||
void String::clearFields() {
|
||||
_blocks.clear();
|
||||
_links.clear();
|
||||
_spoiler = nullptr;
|
||||
_spoiler.data = nullptr;
|
||||
_maxWidth = _minHeight = 0;
|
||||
_startDir = Qt::LayoutDirectionAuto;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ public:
|
|||
|
||||
[[nodiscard]] bool hasSpoilers() const;
|
||||
void setSpoilerRevealed(bool revealed, anim::type animated);
|
||||
void setSpoilerLink(const ClickHandlerPtr &lnk);
|
||||
void setSpoilerLinkFilter(Fn<bool(const ClickContext&)> filter);
|
||||
|
||||
[[nodiscard]] bool hasSkipBlock() const;
|
||||
bool updateSkipBlock(int width, int height);
|
||||
|
|
@ -237,6 +237,19 @@ private:
|
|||
using TextBlocks = std::vector<Block>;
|
||||
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 countBlockLength(const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const;
|
||||
|
||||
|
|
@ -276,7 +289,7 @@ private:
|
|||
|
||||
Qt::LayoutDirection _startDir = Qt::LayoutDirectionAuto;
|
||||
|
||||
std::unique_ptr<SpoilerData> _spoiler;
|
||||
SpoilerDataWrap _spoiler;
|
||||
|
||||
friend class Parser;
|
||||
friend class Renderer;
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
createBlock();
|
||||
_t->_text.push_back(QChar::LineFeed);
|
||||
|
|
@ -634,8 +626,8 @@ void Parser::finalize(const TextParseOptions &options) {
|
|||
}
|
||||
}
|
||||
if (block->spoilerIndex()) {
|
||||
if (!_t->_spoiler) {
|
||||
_t->_spoiler = std::make_unique<SpoilerData>(
|
||||
if (!_t->_spoiler.data) {
|
||||
_t->_spoiler.data = std::make_unique<SpoilerData>(
|
||||
Integration::Instance().createSpoilerRepaint(_context));
|
||||
}
|
||||
}
|
||||
|
|
@ -696,10 +688,10 @@ void Parser::finalize(const TextParseOptions &options) {
|
|||
}
|
||||
lastHandlerIndex.lnk = realIndex;
|
||||
}
|
||||
if (!_t->_hasCustomEmoji || _t->_spoiler) {
|
||||
if (!_t->_hasCustomEmoji || _t->_spoiler.data) {
|
||||
_t->_isOnlyCustomEmoji = false;
|
||||
}
|
||||
if (_t->_blocks.empty() || _t->_spoiler) {
|
||||
if (_t->_blocks.empty() || _t->_spoiler.data) {
|
||||
_t->_isIsolatedEmoji = false;
|
||||
}
|
||||
_t->_links.squeeze();
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@ private:
|
|||
void trimSourceRange();
|
||||
void blockCreated();
|
||||
void createBlock(int32 skipBack = 0);
|
||||
// void createSkipBlock(int32 w, int32 h);
|
||||
void createNewlineBlock();
|
||||
|
||||
// Returns true if at least one entity was parsed in the current position.
|
||||
|
|
|
|||
|
|
@ -154,7 +154,8 @@ bool Distinct(FixedRange a, FixedRange b) {
|
|||
}
|
||||
|
||||
Renderer::Renderer(const Ui::Text::String &t)
|
||||
: _t(&t) {
|
||||
: _t(&t)
|
||||
, _spoiler(_t->_spoiler.data.get()) {
|
||||
}
|
||||
|
||||
Renderer::~Renderer() {
|
||||
|
|
@ -199,9 +200,9 @@ void Renderer::draw(QPainter &p, const PaintContext &context) {
|
|||
_align = context.align;
|
||||
_cachedNow = context.now;
|
||||
_paused = context.paused;
|
||||
_spoilerOpacity = _t->_spoiler
|
||||
? (1. - _t->_spoiler->revealAnimation.value(
|
||||
_t->_spoiler->revealed ? 1. : 0.))
|
||||
_spoilerOpacity = _spoiler
|
||||
? (1. - _spoiler->revealAnimation.value(
|
||||
_spoiler->revealed ? 1. : 0.))
|
||||
: 0.;
|
||||
enumerate();
|
||||
}
|
||||
|
|
@ -1085,7 +1086,7 @@ void Renderer::pushSpoilerRange(
|
|||
FixedRange range,
|
||||
FixedRange selected,
|
||||
int currentBlockIndex) {
|
||||
if (!_background.spoiler || !_t->_spoiler) {
|
||||
if (!_background.spoiler || !_spoiler) {
|
||||
return;
|
||||
}
|
||||
const auto elided = (_indexOfElidedBlock == currentBlockIndex)
|
||||
|
|
@ -1135,14 +1136,14 @@ void Renderer::fillSpoilerRects(
|
|||
void Renderer::paintSpoilerRects() {
|
||||
Expects(_p != nullptr);
|
||||
|
||||
if (!_t->_spoiler) {
|
||||
if (!_spoiler) {
|
||||
return;
|
||||
}
|
||||
const auto opacity = _p->opacity();
|
||||
if (_spoilerOpacity < 1.) {
|
||||
_p->setOpacity(opacity * _spoilerOpacity);
|
||||
}
|
||||
const auto index = _t->_spoiler->animation.index(now(), _paused);
|
||||
const auto index = _spoiler->animation.index(now(), _paused);
|
||||
paintSpoilerRects(
|
||||
_spoilerRects,
|
||||
_palette->spoilerFg,
|
||||
|
|
@ -1991,12 +1992,12 @@ void Renderer::applyBlockProperties(const AbstractBlock *block) {
|
|||
if (_p) {
|
||||
const auto isMono = IsMono(block->flags());
|
||||
_background = {};
|
||||
if (block->spoilerIndex() && _t->_spoiler) {
|
||||
if (block->spoilerIndex() && _spoiler) {
|
||||
_background.spoiler = true;
|
||||
}
|
||||
if (isMono
|
||||
&& block->lnkIndex()
|
||||
&& (!_background.spoiler || _t->_spoiler->revealed)) {
|
||||
&& (!_background.spoiler || _spoiler->revealed)) {
|
||||
_background.selectActiveBlock = ClickHandler::showAsPressed(
|
||||
_t->_links.at(block->lnkIndex() - 1));
|
||||
}
|
||||
|
|
@ -2016,10 +2017,10 @@ void Renderer::applyBlockProperties(const AbstractBlock *block) {
|
|||
}
|
||||
|
||||
ClickHandlerPtr Renderer::lookupLink(const AbstractBlock *block) const {
|
||||
const auto spoilerLink = (_t->_spoiler
|
||||
&& !_t->_spoiler->revealed
|
||||
const auto spoilerLink = (_spoiler
|
||||
&& !_spoiler->revealed
|
||||
&& block->spoilerIndex())
|
||||
? _t->_spoiler->link
|
||||
? _spoiler->link
|
||||
: ClickHandlerPtr();
|
||||
return (spoilerLink || !block->lnkIndex())
|
||||
? spoilerLink
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ private:
|
|||
const AbstractBlock *block) const;
|
||||
|
||||
const String *_t = nullptr;
|
||||
SpoilerData *_spoiler = nullptr;
|
||||
SpoilerMessCache *_spoilerCache = nullptr;
|
||||
QPainter *_p = nullptr;
|
||||
const style::TextPalette *_palette = nullptr;
|
||||
|
|
|
|||
36
ui/text/text_spoiler_data.cpp
Normal file
36
ui/text/text_spoiler_data.cpp
Normal 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
|
||||
|
||||
|
|
@ -8,18 +8,36 @@
|
|||
|
||||
#include "ui/effects/spoiler_mess.h"
|
||||
#include "ui/effects/animations.h"
|
||||
|
||||
class SpoilerClickHandler;
|
||||
#include "ui/click_handler.h"
|
||||
|
||||
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 {
|
||||
explicit SpoilerData(Fn<void()> repaint)
|
||||
: animation(std::move(repaint)) {
|
||||
}
|
||||
|
||||
SpoilerAnimation animation;
|
||||
ClickHandlerPtr link;
|
||||
std::shared_ptr<SpoilerClickHandler> link;
|
||||
Animations::Simple revealAnimation;
|
||||
bool revealed = false;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue