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_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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
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/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;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue