Optimize spoiler revealing.
This commit is contained in:
parent
a60fe582ad
commit
bc76e4f601
11 changed files with 93 additions and 178 deletions
|
|
@ -271,8 +271,6 @@ PRIVATE
|
|||
ui/rect_part.h
|
||||
ui/round_rect.cpp
|
||||
ui/round_rect.h
|
||||
ui/spoiler_click_handler.cpp
|
||||
ui/spoiler_click_handler.h
|
||||
ui/rp_widget.cpp
|
||||
ui/rp_widget.h
|
||||
ui/ui_utility.cpp
|
||||
|
|
|
|||
|
|
@ -775,6 +775,10 @@ int SpoilerAnimation::index(crl::time now, bool paused) {
|
|||
return absolute % kDefaultFramesCount;
|
||||
}
|
||||
|
||||
Fn<void()> SpoilerAnimation::repaintCallback() const {
|
||||
return _repaint;
|
||||
}
|
||||
|
||||
bool SpoilerAnimation::repaint(crl::time now) {
|
||||
if (!_scheduled) {
|
||||
_scheduled = true;
|
||||
|
|
|
|||
|
|
@ -96,7 +96,9 @@ public:
|
|||
explicit SpoilerAnimation(Fn<void()> repaint);
|
||||
~SpoilerAnimation();
|
||||
|
||||
int index(crl::time now, bool paused);
|
||||
[[nodiscard]] int index(crl::time now, bool paused);
|
||||
|
||||
[[nodiscard]] Fn<void()> repaintCallback() const;
|
||||
|
||||
private:
|
||||
friend class SpoilerAnimationManager;
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
// 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/spoiler_click_handler.h"
|
||||
|
||||
#include "ui/effects/animation_value.h"
|
||||
#include "ui/text/text_entity.h"
|
||||
|
||||
ClickHandler::TextEntity SpoilerClickHandler::getTextEntity() const {
|
||||
return { EntityType::Spoiler };
|
||||
}
|
||||
|
||||
void SpoilerClickHandler::onClick(ClickContext context) const {
|
||||
if (!_shown) {
|
||||
const auto nonconst = const_cast<SpoilerClickHandler*>(this);
|
||||
nonconst->_shown = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpoilerClickHandler::shown() const {
|
||||
return _shown;
|
||||
}
|
||||
|
||||
crl::time SpoilerClickHandler::startMs() const {
|
||||
return _startMs;
|
||||
}
|
||||
|
||||
void SpoilerClickHandler::setStartMs(crl::time value) {
|
||||
if (anim::Disabled()) {
|
||||
return;
|
||||
}
|
||||
_startMs = value;
|
||||
}
|
||||
|
||||
void SpoilerClickHandler::setShown(bool value) {
|
||||
_shown = value;
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
// 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
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "ui/click_handler.h"
|
||||
|
||||
enum class EntityType : uchar;
|
||||
|
||||
class SpoilerClickHandler : public ClickHandler {
|
||||
public:
|
||||
SpoilerClickHandler() = default;
|
||||
|
||||
TextEntity getTextEntity() const override;
|
||||
|
||||
void onClick(ClickContext context) const override;
|
||||
|
||||
[[nodiscard]] bool shown() const;
|
||||
void setShown(bool value);
|
||||
[[nodiscard]] crl::time startMs() const;
|
||||
void setStartMs(crl::time value);
|
||||
|
||||
private:
|
||||
bool _shown = false;
|
||||
crl::time _startMs = 0;
|
||||
|
||||
};
|
||||
|
|
@ -12,7 +12,6 @@
|
|||
#include "ui/text/text_spoiler_data.h"
|
||||
#include "ui/basic_click_handlers.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/spoiler_click_handler.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "styles/style_basic.h"
|
||||
|
||||
|
|
@ -318,31 +317,40 @@ void String::setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk) {
|
|||
_links[lnkIndex - 1] = lnk;
|
||||
}
|
||||
|
||||
void String::setSpoiler(
|
||||
uint16 lnkIndex,
|
||||
const std::shared_ptr<SpoilerClickHandler> &lnk) {
|
||||
if (!lnkIndex || !_spoiler || lnkIndex > _spoiler->links.size()) {
|
||||
void String::setSpoilerRevealed(bool revealed, anim::type animated) {
|
||||
if (!_spoiler) {
|
||||
return;
|
||||
} else if (_spoiler->revealed == revealed) {
|
||||
if (animated == anim::type::instant
|
||||
&& _spoiler->revealAnimation.animating()) {
|
||||
_spoiler->revealAnimation.stop();
|
||||
_spoiler->animation.repaintCallback()();
|
||||
}
|
||||
return;
|
||||
}
|
||||
_spoiler->links[lnkIndex - 1] = lnk;
|
||||
_spoiler->revealed = revealed;
|
||||
if (animated == anim::type::instant) {
|
||||
_spoiler->revealAnimation.stop();
|
||||
_spoiler->animation.repaintCallback()();
|
||||
} else {
|
||||
_spoiler->revealAnimation.start(
|
||||
_spoiler->animation.repaintCallback(),
|
||||
revealed ? 0. : 1.,
|
||||
revealed ? 1. : 0.,
|
||||
st::fadeWrapDuration);
|
||||
}
|
||||
}
|
||||
|
||||
void String::setSpoilerShown(uint16 lnkIndex, bool shown) {
|
||||
if (!lnkIndex
|
||||
|| !_spoiler
|
||||
|| (lnkIndex > _spoiler->links.size())
|
||||
|| !_spoiler->links[lnkIndex - 1]) {
|
||||
return;
|
||||
}
|
||||
_spoiler->links[lnkIndex - 1]->setShown(shown);
|
||||
void String::setSpoilerLink(const ClickHandlerPtr &lnk) {
|
||||
_spoiler->link = lnk;
|
||||
}
|
||||
|
||||
bool String::hasLinks() const {
|
||||
return !_links.isEmpty();
|
||||
}
|
||||
|
||||
int String::spoilersCount() const {
|
||||
return !_spoiler ? 0 : int(_spoiler->links.size());
|
||||
bool String::hasSpoilers() const {
|
||||
return (_spoiler != nullptr);
|
||||
}
|
||||
|
||||
bool String::hasSkipBlock() const {
|
||||
|
|
@ -752,9 +760,11 @@ void String::enumerateText(
|
|||
if (rangeTo > rangeFrom) { // handle click handler
|
||||
const auto r = base::StringViewMid(_text, rangeFrom, rangeTo - rangeFrom);
|
||||
// Ignore links that are partially copied.
|
||||
const auto handler = (spoilerFrom != rangeFrom || blockFrom != rangeTo || !_spoiler)
|
||||
const auto handler = (spoilerFrom != rangeFrom
|
||||
|| blockFrom != rangeTo
|
||||
|| !_spoiler)
|
||||
? nullptr
|
||||
: _spoiler->links.at(spoilerIndex - 1);
|
||||
: _spoiler->link;
|
||||
const auto type = EntityType::Spoiler;
|
||||
clickHandlerFinishCallback(r, handler, type);
|
||||
}
|
||||
|
|
@ -1040,14 +1050,6 @@ void String::clearFields() {
|
|||
_startDir = Qt::LayoutDirectionAuto;
|
||||
}
|
||||
|
||||
ClickHandlerPtr String::spoilerLink(uint16 spoilerIndex) const {
|
||||
if (spoilerIndex && _spoiler) {
|
||||
const auto &handler = _spoiler->links.at(spoilerIndex - 1);
|
||||
return (handler && !handler->shown()) ? handler : nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool IsBad(QChar ch) {
|
||||
return (ch == 0)
|
||||
|| (ch >= 8232 && ch < 8237)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@
|
|||
#include <any>
|
||||
|
||||
class Painter;
|
||||
class SpoilerClickHandler;
|
||||
|
||||
namespace anim {
|
||||
enum class type : uchar;
|
||||
} // namespace anim
|
||||
|
||||
namespace style {
|
||||
struct TextPalette;
|
||||
|
|
@ -162,13 +165,12 @@ public:
|
|||
void setText(const style::TextStyle &st, const QString &text, const TextParseOptions &options = kDefaultTextOptions);
|
||||
void setMarkedText(const style::TextStyle &st, const TextWithEntities &textWithEntities, const TextParseOptions &options = kMarkupTextOptions, const std::any &context = {});
|
||||
|
||||
void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk);
|
||||
[[nodiscard]] bool hasLinks() const;
|
||||
void setSpoiler(
|
||||
uint16 lnkIndex,
|
||||
const std::shared_ptr<SpoilerClickHandler> &lnk);
|
||||
void setSpoilerShown(uint16 lnkIndex, bool shown);
|
||||
[[nodiscard]] int spoilersCount() const;
|
||||
void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk);
|
||||
|
||||
[[nodiscard]] bool hasSpoilers() const;
|
||||
void setSpoilerRevealed(bool revealed, anim::type animated);
|
||||
void setSpoilerLink(const ClickHandlerPtr &lnk);
|
||||
|
||||
[[nodiscard]] bool hasSkipBlock() const;
|
||||
bool updateSkipBlock(int width, int height);
|
||||
|
|
@ -254,8 +256,6 @@ private:
|
|||
// it is also called from move constructor / assignment operator
|
||||
void clearFields();
|
||||
|
||||
[[nodiscard]] ClickHandlerPtr spoilerLink(uint16 spoilerIndex) const;
|
||||
|
||||
TextForMimeData toText(
|
||||
TextSelection selection,
|
||||
bool composeExpanded,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@
|
|||
#include "ui/integration.h"
|
||||
#include "ui/text/text_isolated_emoji.h"
|
||||
#include "ui/text/text_spoiler_data.h"
|
||||
#include "ui/spoiler_click_handler.h"
|
||||
#include "styles/style_basic.h"
|
||||
|
||||
#include <QtCore/QUrl>
|
||||
|
|
@ -634,19 +633,11 @@ void Parser::finalize(const TextParseOptions &options) {
|
|||
_t->_isIsolatedEmoji = false;
|
||||
}
|
||||
}
|
||||
const auto spoilerIndex = block->spoilerIndex();
|
||||
if (spoilerIndex) {
|
||||
if (block->spoilerIndex()) {
|
||||
if (!_t->_spoiler) {
|
||||
_t->_spoiler = std::make_unique<SpoilerData>(
|
||||
Integration::Instance().createSpoilerRepaint(_context));
|
||||
}
|
||||
if (_t->_spoiler->links.size() < spoilerIndex) {
|
||||
_t->_spoiler->links.resize(spoilerIndex);
|
||||
const auto handler = (options.flags & TextParseLinks)
|
||||
? std::make_shared<SpoilerClickHandler>()
|
||||
: nullptr;
|
||||
_t->setSpoiler(spoilerIndex, std::move(handler));
|
||||
}
|
||||
}
|
||||
const auto shiftedIndex = block->lnkIndex();
|
||||
auto useCustomIndex = false;
|
||||
|
|
@ -712,9 +703,6 @@ void Parser::finalize(const TextParseOptions &options) {
|
|||
_t->_isIsolatedEmoji = false;
|
||||
}
|
||||
_t->_links.squeeze();
|
||||
if (_t->_spoiler) {
|
||||
_t->_spoiler->links.squeeze();
|
||||
}
|
||||
_t->_blocks.shrink_to_fit();
|
||||
_t->_text.squeeze();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
#include "ui/text/text_renderer.h"
|
||||
|
||||
#include "ui/text/text_spoiler_data.h"
|
||||
#include "ui/spoiler_click_handler.h"
|
||||
#include "styles/style_basic.h"
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
|
|
@ -200,6 +199,10 @@ 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.))
|
||||
: 0.;
|
||||
enumerate();
|
||||
}
|
||||
|
||||
|
|
@ -720,12 +723,8 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
|||
if (!_p && _lookupX >= x && _lookupX < x + si.width) { // _lookupRequest
|
||||
if (_lookupLink) {
|
||||
if (_lookupY >= _y + _yDelta && _lookupY < _y + _yDelta + _fontHeight) {
|
||||
const auto spoilerLink = _t->spoilerLink(currentBlock->spoilerIndex());
|
||||
const auto resultLink = (spoilerLink || !currentBlock->lnkIndex())
|
||||
? spoilerLink
|
||||
: _t->_links.at(currentBlock->lnkIndex() - 1);
|
||||
if (resultLink) {
|
||||
_lookupResult.link = resultLink;
|
||||
if (const auto link = lookupLink(currentBlock)) {
|
||||
_lookupResult.link = link;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -802,19 +801,16 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
|||
}
|
||||
}
|
||||
}
|
||||
const auto hasSpoiler = _background.inFront
|
||||
|| _background.startMs;
|
||||
const auto hasSpoiler = _background.spoiler
|
||||
&& (_spoilerOpacity > 0.);
|
||||
if (hasSpoiler) {
|
||||
fillSpoiler = { x, x + si.width };
|
||||
}
|
||||
const auto spoilerOpacity = hasSpoiler
|
||||
? fillSpoilerOpacity()
|
||||
: 0.;
|
||||
fillSelectRange(fillSelect);
|
||||
const auto opacity = _p->opacity();
|
||||
if (spoilerOpacity < 1.) {
|
||||
if (!hasSpoiler || _spoilerOpacity < 1.) {
|
||||
if (hasSpoiler) {
|
||||
_p->setOpacity(opacity * (1. - spoilerOpacity));
|
||||
_p->setOpacity(opacity * (1. - _spoilerOpacity));
|
||||
}
|
||||
const auto x = (glyphX + st::emojiPadding).toInt();
|
||||
const auto y = _y + _yDelta + emojiY;
|
||||
|
|
@ -872,12 +868,8 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
|||
if (!_p && _lookupX >= x && _lookupX < x + itemWidth) { // _lookupRequest
|
||||
if (_lookupLink) {
|
||||
if (_lookupY >= _y + _yDelta && _lookupY < _y + _yDelta + _fontHeight) {
|
||||
const auto spoilerLink = _t->spoilerLink(currentBlock->spoilerIndex());
|
||||
const auto resultLink = (spoilerLink || !currentBlock->lnkIndex())
|
||||
? spoilerLink
|
||||
: _t->_links.at(currentBlock->lnkIndex() - 1);
|
||||
if (resultLink) {
|
||||
_lookupResult.link = resultLink;
|
||||
if (const auto link = lookupLink(currentBlock)) {
|
||||
_lookupResult.link = link;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -987,17 +979,15 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
|||
fillSelect = { selX, selX + selWidth };
|
||||
fillSelectRange(fillSelect);
|
||||
}
|
||||
const auto hasSpoiler = (_background.inFront || _background.startMs);
|
||||
const auto spoilerOpacity = hasSpoiler
|
||||
? fillSpoilerOpacity()
|
||||
: 0.;
|
||||
const auto hasSpoiler = _background.spoiler
|
||||
&& (_spoilerOpacity > 0.);
|
||||
const auto opacity = _p->opacity();
|
||||
const auto isElidedBlock = !rtl
|
||||
&& (_indexOfElidedBlock == blockIndex);
|
||||
const auto complexClipping = hasSpoiler
|
||||
&& isElidedBlock
|
||||
&& spoilerOpacity == 1.;
|
||||
if ((spoilerOpacity < 1.) || isElidedBlock) {
|
||||
&& (_spoilerOpacity == 1.);
|
||||
if (!hasSpoiler || (_spoilerOpacity < 1.) || isElidedBlock) {
|
||||
const auto complexClippingEnabled = complexClipping
|
||||
&& _p->hasClipping();
|
||||
const auto complexClippingRegion = complexClipping
|
||||
|
|
@ -1015,7 +1005,7 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
|||
_y + 2 * _lineHeight),
|
||||
Qt::IntersectClip);
|
||||
} else if (hasSpoiler && !isElidedBlock) {
|
||||
_p->setOpacity(opacity * (1. - spoilerOpacity));
|
||||
_p->setOpacity(opacity * (1. - _spoilerOpacity));
|
||||
}
|
||||
if (Q_UNLIKELY(hasSelected)) {
|
||||
if (Q_UNLIKELY(hasNotSelected)) {
|
||||
|
|
@ -1091,22 +1081,6 @@ void Renderer::fillSelectRange(FixedRange range) {
|
|||
_p->fillRect(left, _y + _yDelta, width, _fontHeight, _palette->selectBg);
|
||||
}
|
||||
|
||||
float64 Renderer::fillSpoilerOpacity() {
|
||||
if (!_background.startMs) {
|
||||
return 1.;
|
||||
}
|
||||
const auto progress = float64(now() - _background.startMs)
|
||||
/ st::fadeWrapDuration;
|
||||
if ((progress > 1.) && _background.spoilerIndex && _t->_spoiler) {
|
||||
const auto link = _t->_spoiler->links.at(
|
||||
_background.spoilerIndex - 1);
|
||||
if (link) {
|
||||
link->setStartMs(0);
|
||||
}
|
||||
}
|
||||
return (1. - std::min(progress, 1.));
|
||||
}
|
||||
|
||||
void Renderer::pushSpoilerRange(
|
||||
FixedRange range,
|
||||
FixedRange selected,
|
||||
|
|
@ -1159,9 +1133,15 @@ void Renderer::fillSpoilerRects(
|
|||
}
|
||||
|
||||
void Renderer::paintSpoilerRects() {
|
||||
Expects(_p != nullptr);
|
||||
|
||||
if (!_t->_spoiler) {
|
||||
return;
|
||||
}
|
||||
const auto opacity = _p->opacity();
|
||||
if (_spoilerOpacity < 1.) {
|
||||
_p->setOpacity(opacity * _spoilerOpacity);
|
||||
}
|
||||
const auto index = _t->_spoiler->animation.index(now(), _paused);
|
||||
paintSpoilerRects(
|
||||
_spoilerRects,
|
||||
|
|
@ -1171,6 +1151,9 @@ void Renderer::paintSpoilerRects() {
|
|||
_spoilerSelectedRects,
|
||||
_palette->selectSpoilerFg,
|
||||
index);
|
||||
if (_spoilerOpacity < 1.) {
|
||||
_p->setOpacity(opacity);
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::paintSpoilerRects(
|
||||
|
|
@ -2009,15 +1992,11 @@ void Renderer::applyBlockProperties(const AbstractBlock *block) {
|
|||
const auto isMono = IsMono(block->flags());
|
||||
_background = {};
|
||||
if (block->spoilerIndex() && _t->_spoiler) {
|
||||
const auto handler
|
||||
= _t->_spoiler->links.at(block->spoilerIndex() - 1);
|
||||
const auto inBack = (handler && handler->shown());
|
||||
_background.inFront = !inBack;
|
||||
_background.spoiler = true;
|
||||
_background.startMs = handler ? handler->startMs() : 0;
|
||||
_background.spoilerIndex = block->spoilerIndex();
|
||||
}
|
||||
if (isMono && block->lnkIndex() && !_background.inFront) {
|
||||
if (isMono
|
||||
&& block->lnkIndex()
|
||||
&& (!_background.spoiler || _t->_spoiler->revealed)) {
|
||||
_background.selectActiveBlock = ClickHandler::showAsPressed(
|
||||
_t->_links.at(block->lnkIndex() - 1));
|
||||
}
|
||||
|
|
@ -2036,4 +2015,15 @@ void Renderer::applyBlockProperties(const AbstractBlock *block) {
|
|||
}
|
||||
}
|
||||
|
||||
ClickHandlerPtr Renderer::lookupLink(const AbstractBlock *block) const {
|
||||
const auto spoilerLink = (_t->_spoiler
|
||||
&& !_t->_spoiler->revealed
|
||||
&& block->spoilerIndex())
|
||||
? _t->_spoiler->link
|
||||
: ClickHandlerPtr();
|
||||
return (spoilerLink || !block->lnkIndex())
|
||||
? spoilerLink
|
||||
: _t->_links.at(block->lnkIndex() - 1);
|
||||
}
|
||||
|
||||
} // namespace Ui::Text
|
||||
|
|
|
|||
|
|
@ -59,7 +59,6 @@ private:
|
|||
const String::TextBlocks::const_iterator &_endBlockIter,
|
||||
const String::TextBlocks::const_iterator &_end);
|
||||
void fillSelectRange(FixedRange range);
|
||||
[[nodiscard]] float64 fillSpoilerOpacity();
|
||||
void pushSpoilerRange(
|
||||
FixedRange range,
|
||||
FixedRange selected,
|
||||
|
|
@ -108,6 +107,8 @@ private:
|
|||
bool eBidiItemize(QScriptAnalysis *analysis, BidiControl &control);
|
||||
|
||||
void applyBlockProperties(const AbstractBlock *block);
|
||||
[[nodiscard]] ClickHandlerPtr lookupLink(
|
||||
const AbstractBlock *block) const;
|
||||
|
||||
const String *_t = nullptr;
|
||||
SpoilerMessCache *_spoilerCache = nullptr;
|
||||
|
|
@ -123,11 +124,7 @@ private:
|
|||
const QPen *_currentPen = nullptr;
|
||||
const QPen *_currentPenSelected = nullptr;
|
||||
struct {
|
||||
bool inFront = false;
|
||||
bool spoiler = true;
|
||||
crl::time startMs = 0;
|
||||
uint16 spoilerIndex = 0;
|
||||
|
||||
bool spoiler = false;
|
||||
bool selectActiveBlock = false; // For monospace.
|
||||
} _background;
|
||||
int _yFrom = 0;
|
||||
|
|
@ -137,6 +134,7 @@ private:
|
|||
bool _fullWidthSelection = true;
|
||||
const QChar *_str = nullptr;
|
||||
mutable crl::time _cachedNow = 0;
|
||||
float64 _spoilerOpacity = 0.;
|
||||
QVarLengthArray<FixedRange> _spoilerRanges;
|
||||
QVarLengthArray<FixedRange> _spoilerSelectedRanges;
|
||||
QVarLengthArray<QRect, kSpoilersRectsSize> _spoilerRects;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "ui/effects/spoiler_mess.h"
|
||||
#include "ui/effects/animations.h"
|
||||
|
||||
class SpoilerClickHandler;
|
||||
|
||||
|
|
@ -18,7 +19,9 @@ struct SpoilerData {
|
|||
}
|
||||
|
||||
SpoilerAnimation animation;
|
||||
QVector<std::shared_ptr<SpoilerClickHandler>> links;
|
||||
ClickHandlerPtr link;
|
||||
Animations::Simple revealAnimation;
|
||||
bool revealed = false;
|
||||
};
|
||||
|
||||
} // namespace Ui::Text
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue