Implement effects paywalls.
This commit is contained in:
parent
d102d256a9
commit
732b67ca04
13 changed files with 241 additions and 47 deletions
|
@ -564,6 +564,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_effect_add_title" = "Add an animated effect";
|
"lng_effect_add_title" = "Add an animated effect";
|
||||||
"lng_effect_stickers_title" = "Effects from stickers";
|
"lng_effect_stickers_title" = "Effects from stickers";
|
||||||
"lng_effect_send" = "Send with Effect";
|
"lng_effect_send" = "Send with Effect";
|
||||||
|
"lng_effect_none" = "No effects found.";
|
||||||
|
"lng_effect_premium" = "Subscribe to {link} to add this animated effect.";
|
||||||
|
"lng_effect_premium_link" = "Telegram Premium";
|
||||||
|
|
||||||
"lng_languages" = "Languages";
|
"lng_languages" = "Languages";
|
||||||
"lng_languages_none" = "No languages found.";
|
"lng_languages_none" = "No languages found.";
|
||||||
|
|
|
@ -73,7 +73,9 @@ using Data::StickersSet;
|
||||||
using Data::StickersPack;
|
using Data::StickersPack;
|
||||||
using SetFlag = Data::StickersSetFlag;
|
using SetFlag = Data::StickersSetFlag;
|
||||||
|
|
||||||
[[nodiscard]] std::optional<QColor> ComputeImageColor(const QImage &frame) {
|
[[nodiscard]] std::optional<QColor> ComputeImageColor(
|
||||||
|
const style::icon &lockIcon,
|
||||||
|
const QImage &frame) {
|
||||||
if (frame.isNull()
|
if (frame.isNull()
|
||||||
|| frame.format() != QImage::Format_ARGB32_Premultiplied) {
|
|| frame.format() != QImage::Format_ARGB32_Premultiplied) {
|
||||||
return {};
|
return {};
|
||||||
|
@ -83,7 +85,7 @@ using SetFlag = Data::StickersSetFlag;
|
||||||
auto sb = int64();
|
auto sb = int64();
|
||||||
auto sa = int64();
|
auto sa = int64();
|
||||||
const auto factor = frame.devicePixelRatio();
|
const auto factor = frame.devicePixelRatio();
|
||||||
const auto size = st::stickersPremiumLock.size() * factor;
|
const auto size = lockIcon.size() * factor;
|
||||||
const auto width = std::min(frame.width(), size.width());
|
const auto width = std::min(frame.width(), size.width());
|
||||||
const auto height = std::min(frame.height(), size.height());
|
const auto height = std::min(frame.height(), size.height());
|
||||||
const auto skipx = (frame.width() - width) / 2;
|
const auto skipx = (frame.width() - width) / 2;
|
||||||
|
@ -110,22 +112,30 @@ using SetFlag = Data::StickersSetFlag;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] QColor ComputeLockColor(const QImage &frame) {
|
[[nodiscard]] QColor ComputeLockColor(
|
||||||
return ComputeImageColor(frame).value_or(st::windowSubTextFg->c);
|
const style::icon &lockIcon,
|
||||||
|
const QImage &frame) {
|
||||||
|
return ComputeImageColor(
|
||||||
|
lockIcon,
|
||||||
|
frame
|
||||||
|
).value_or(st::windowSubTextFg->c);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ValidatePremiumLockBg(QImage &image, const QImage &frame) {
|
void ValidatePremiumLockBg(
|
||||||
|
const style::icon &lockIcon,
|
||||||
|
QImage &image,
|
||||||
|
const QImage &frame) {
|
||||||
if (!image.isNull()) {
|
if (!image.isNull()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto factor = style::DevicePixelRatio();
|
const auto factor = style::DevicePixelRatio();
|
||||||
const auto size = st::stickersPremiumLock.size();
|
const auto size = lockIcon.size();
|
||||||
image = QImage(
|
image = QImage(
|
||||||
size * factor,
|
size * factor,
|
||||||
QImage::Format_ARGB32_Premultiplied);
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
image.setDevicePixelRatio(factor);
|
image.setDevicePixelRatio(factor);
|
||||||
auto p = QPainter(&image);
|
auto p = QPainter(&image);
|
||||||
const auto color = ComputeLockColor(frame);
|
const auto color = ComputeLockColor(lockIcon, frame);
|
||||||
p.fillRect(
|
p.fillRect(
|
||||||
QRect(QPoint(), size),
|
QRect(QPoint(), size),
|
||||||
anim::color(color, st::windowSubTextFg, kGrayLockOpacity));
|
anim::color(color, st::windowSubTextFg, kGrayLockOpacity));
|
||||||
|
@ -134,12 +144,12 @@ void ValidatePremiumLockBg(QImage &image, const QImage &frame) {
|
||||||
image = Images::Circle(std::move(image));
|
image = Images::Circle(std::move(image));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ValidatePremiumStarFg(QImage &image) {
|
void ValidatePremiumStarFg(const style::icon &lockIcon, QImage &image) {
|
||||||
if (!image.isNull()) {
|
if (!image.isNull()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto factor = style::DevicePixelRatio();
|
const auto factor = style::DevicePixelRatio();
|
||||||
const auto size = st::stickersPremiumLock.size();
|
const auto size = lockIcon.size();
|
||||||
image = QImage(
|
image = QImage(
|
||||||
size * factor,
|
size * factor,
|
||||||
QImage::Format_ARGB32_Premultiplied);
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
|
@ -176,7 +186,10 @@ void ValidatePremiumStarFg(QImage &image) {
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
StickerPremiumMark::StickerPremiumMark(not_null<Main::Session*> session) {
|
StickerPremiumMark::StickerPremiumMark(
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
const style::icon &lockIcon)
|
||||||
|
: _lockIcon(lockIcon) {
|
||||||
style::PaletteChanged(
|
style::PaletteChanged(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
_lockGray = QImage();
|
_lockGray = QImage();
|
||||||
|
@ -202,16 +215,14 @@ void StickerPremiumMark::paint(
|
||||||
const auto factor = style::DevicePixelRatio();
|
const auto factor = style::DevicePixelRatio();
|
||||||
const auto radius = st::roundRadiusSmall;
|
const auto radius = st::roundRadiusSmall;
|
||||||
const auto point = position + QPoint(
|
const auto point = position + QPoint(
|
||||||
(_premium
|
(singleSize.width() - (bg.width() / factor) - radius),
|
||||||
? (singleSize.width() - (bg.width() / factor) - radius)
|
|
||||||
: (singleSize.width() - (bg.width() / factor)) / 2),
|
|
||||||
singleSize.height() - (bg.height() / factor) - radius);
|
singleSize.height() - (bg.height() / factor) - radius);
|
||||||
p.drawImage(point, bg);
|
p.drawImage(point, bg);
|
||||||
if (_premium) {
|
if (_premium) {
|
||||||
validateStar();
|
validateStar();
|
||||||
p.drawImage(point, _star);
|
p.drawImage(point, _star);
|
||||||
} else {
|
} else {
|
||||||
st::stickersPremiumLock.paint(p, point, outerWidth);
|
_lockIcon.paint(p, point, outerWidth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,11 +230,11 @@ void StickerPremiumMark::validateLock(
|
||||||
const QImage &frame,
|
const QImage &frame,
|
||||||
QImage &backCache) {
|
QImage &backCache) {
|
||||||
auto &image = frame.isNull() ? _lockGray : backCache;
|
auto &image = frame.isNull() ? _lockGray : backCache;
|
||||||
ValidatePremiumLockBg(image, frame);
|
ValidatePremiumLockBg(_lockIcon, image, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
void StickerPremiumMark::validateStar() {
|
void StickerPremiumMark::validateStar() {
|
||||||
ValidatePremiumStarFg(_star);
|
ValidatePremiumStarFg(_lockIcon, _star);
|
||||||
}
|
}
|
||||||
|
|
||||||
class StickerSetBox::Inner final : public Ui::RpWidget {
|
class StickerSetBox::Inner final : public Ui::RpWidget {
|
||||||
|
@ -664,7 +675,7 @@ StickerSetBox::Inner::Inner(
|
||||||
st::windowBgRipple,
|
st::windowBgRipple,
|
||||||
st::windowBgOver,
|
st::windowBgOver,
|
||||||
[=] { repaintItems(); }))
|
[=] { repaintItems(); }))
|
||||||
, _premiumMark(_session)
|
, _premiumMark(_session, st::stickersPremiumLock)
|
||||||
, _updateItemsTimer([=] { updateItems(); })
|
, _updateItemsTimer([=] { updateItems(); })
|
||||||
, _input(set)
|
, _input(set)
|
||||||
, _padding((type == Data::StickersType::Emoji)
|
, _padding((type == Data::StickersType::Emoji)
|
||||||
|
|
|
@ -30,7 +30,9 @@ class Show;
|
||||||
|
|
||||||
class StickerPremiumMark final {
|
class StickerPremiumMark final {
|
||||||
public:
|
public:
|
||||||
explicit StickerPremiumMark(not_null<Main::Session*> session);
|
StickerPremiumMark(
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
const style::icon &lockIcon);
|
||||||
|
|
||||||
void paint(
|
void paint(
|
||||||
QPainter &p,
|
QPainter &p,
|
||||||
|
@ -44,6 +46,7 @@ private:
|
||||||
void validateLock(const QImage &frame, QImage &backCache);
|
void validateLock(const QImage &frame, QImage &backCache);
|
||||||
void validateStar();
|
void validateStar();
|
||||||
|
|
||||||
|
const style::icon &_lockIcon;
|
||||||
QImage _lockGray;
|
QImage _lockGray;
|
||||||
QImage _star;
|
QImage _star;
|
||||||
bool _premium = false;
|
bool _premium = false;
|
||||||
|
|
|
@ -754,6 +754,7 @@ inlineResultsMinWidth: 48px;
|
||||||
inlineDurationMargin: 3px;
|
inlineDurationMargin: 3px;
|
||||||
|
|
||||||
stickersPremiumLock: icon{{ "emoji/premium_lock", premiumButtonFg }};
|
stickersPremiumLock: icon{{ "emoji/premium_lock", premiumButtonFg }};
|
||||||
|
emojiPremiumLock: icon{{ "chat/mini_lock", premiumButtonFg }};
|
||||||
|
|
||||||
reactStripExtend: margins(21px, 49px, 39px, 0px);
|
reactStripExtend: margins(21px, 49px, 39px, 0px);
|
||||||
reactStripHeight: 40px;
|
reactStripHeight: 40px;
|
||||||
|
|
|
@ -136,6 +136,7 @@ struct EmojiListWidget::CustomEmojiInstance {
|
||||||
struct EmojiListWidget::RecentOne {
|
struct EmojiListWidget::RecentOne {
|
||||||
Ui::Text::CustomEmoji *custom = nullptr;
|
Ui::Text::CustomEmoji *custom = nullptr;
|
||||||
RecentEmojiId id;
|
RecentEmojiId id;
|
||||||
|
mutable QImage premiumLock;
|
||||||
};
|
};
|
||||||
|
|
||||||
EmojiColorPicker::EmojiColorPicker(
|
EmojiColorPicker::EmojiColorPicker(
|
||||||
|
@ -478,8 +479,12 @@ EmojiListWidget::EmojiListWidget(
|
||||||
, _localSetsManager(
|
, _localSetsManager(
|
||||||
std::make_unique<LocalStickersManager>(&session()))
|
std::make_unique<LocalStickersManager>(&session()))
|
||||||
, _customRecentFactory(std::move(descriptor.customRecentFactory))
|
, _customRecentFactory(std::move(descriptor.customRecentFactory))
|
||||||
|
, _freeEffects(std::move(descriptor.freeEffects))
|
||||||
, _customTextColor(std::move(descriptor.customTextColor))
|
, _customTextColor(std::move(descriptor.customTextColor))
|
||||||
, _overBg(st::emojiPanRadius, st().overBg)
|
, _overBg(st::emojiPanRadius, st().overBg)
|
||||||
|
, _premiumMark(std::make_unique<StickerPremiumMark>(
|
||||||
|
&session(),
|
||||||
|
st::emojiPremiumLock))
|
||||||
, _collapsedBg(st::emojiPanExpand.height / 2, st().headerFg)
|
, _collapsedBg(st::emojiPanExpand.height / 2, st().headerFg)
|
||||||
, _picker(this, st())
|
, _picker(this, st())
|
||||||
, _showPickerTimer([=] { showPicker(); })
|
, _showPickerTimer([=] { showPicker(); })
|
||||||
|
@ -591,6 +596,10 @@ rpl::producer<std::vector<QString>> EmojiListWidget::searchQueries() const {
|
||||||
return _searchQueries.events();
|
return _searchQueries.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpl::producer<int> EmojiListWidget::recentShownCount() const {
|
||||||
|
return _recentShownCount.value();
|
||||||
|
}
|
||||||
|
|
||||||
void EmojiListWidget::applyNextSearchQuery() {
|
void EmojiListWidget::applyNextSearchQuery() {
|
||||||
if (_searchQuery == _nextSearchQuery) {
|
if (_searchQuery == _nextSearchQuery) {
|
||||||
return;
|
return;
|
||||||
|
@ -612,6 +621,9 @@ void EmojiListWidget::applyNextSearchQuery() {
|
||||||
_searchCustomIds.clear();
|
_searchCustomIds.clear();
|
||||||
}
|
}
|
||||||
resizeToWidth(width());
|
resizeToWidth(width());
|
||||||
|
_recentShownCount = searching
|
||||||
|
? _searchResults.size()
|
||||||
|
: _recent.size();
|
||||||
update();
|
update();
|
||||||
if (modeChanged) {
|
if (modeChanged) {
|
||||||
visibleTopBottomUpdated(getVisibleTop(), getVisibleBottom());
|
visibleTopBottomUpdated(getVisibleTop(), getVisibleBottom());
|
||||||
|
@ -1024,9 +1036,10 @@ int EmojiListWidget::countDesiredHeight(int newWidth) {
|
||||||
const auto minimalLastHeight = std::max(
|
const auto minimalLastHeight = std::max(
|
||||||
minimalHeight - padding.bottom(),
|
minimalHeight - padding.bottom(),
|
||||||
0);
|
0);
|
||||||
return qMax(
|
const auto result = countResult(minimalLastHeight);
|
||||||
minimalHeight,
|
return result
|
||||||
countResult(minimalLastHeight) + padding.bottom());
|
? qMax(minimalHeight, result + padding.bottom())
|
||||||
|
: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int EmojiListWidget::defaultMinimalHeight() const {
|
int EmojiListWidget::defaultMinimalHeight() const {
|
||||||
|
@ -1291,6 +1304,8 @@ void EmojiListWidget::paint(
|
||||||
QRect clip) {
|
QRect clip) {
|
||||||
validateEmojiPaintContext(context);
|
validateEmojiPaintContext(context);
|
||||||
|
|
||||||
|
_paintAsPremium = session().premium();
|
||||||
|
|
||||||
auto fromColumn = floorclamp(
|
auto fromColumn = floorclamp(
|
||||||
clip.x() - _rowsLeft,
|
clip.x() - _rowsLeft,
|
||||||
_singleSize.width(),
|
_singleSize.width(),
|
||||||
|
@ -1455,16 +1470,44 @@ void EmojiListWidget::drawRecent(
|
||||||
QPoint position,
|
QPoint position,
|
||||||
const RecentOne &recent) {
|
const RecentOne &recent) {
|
||||||
_recentPainted = true;
|
_recentPainted = true;
|
||||||
|
const auto locked = (_mode == Mode::MessageEffects)
|
||||||
|
&& !_paintAsPremium
|
||||||
|
&& v::is<RecentEmojiDocument>(recent.id.data)
|
||||||
|
&& !_freeEffects.contains(
|
||||||
|
v::get<RecentEmojiDocument>(recent.id.data).id);
|
||||||
|
auto lockedPainted = false;
|
||||||
|
if (locked) {
|
||||||
|
if (_premiumMarkFrameCache.isNull()) {
|
||||||
|
const auto ratio = style::DevicePixelRatio();
|
||||||
|
_premiumMarkFrameCache = QImage(
|
||||||
|
QSize(_customSingleSize, _customSingleSize) * ratio,
|
||||||
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
|
_premiumMarkFrameCache.setDevicePixelRatio(ratio);
|
||||||
|
}
|
||||||
|
_premiumMarkFrameCache.fill(Qt::transparent);
|
||||||
|
}
|
||||||
if (const auto custom = recent.custom) {
|
if (const auto custom = recent.custom) {
|
||||||
_emojiPaintContext->scale = context.progress;
|
const auto exactPosition = position
|
||||||
_emojiPaintContext->position = position
|
|
||||||
+ _innerPosition
|
+ _innerPosition
|
||||||
+ _customPosition;
|
+ _customPosition;
|
||||||
|
_emojiPaintContext->scale = context.progress;
|
||||||
if (_mode == Mode::ChannelStatus) {
|
if (_mode == Mode::ChannelStatus) {
|
||||||
_emojiPaintContext->internal.forceFirstFrame
|
_emojiPaintContext->internal.forceFirstFrame
|
||||||
= (recent.id == _recent.front().id);
|
= (recent.id == _recent.front().id);
|
||||||
}
|
}
|
||||||
custom->paint(p, *_emojiPaintContext);
|
if (locked) {
|
||||||
|
lockedPainted = custom->ready();
|
||||||
|
|
||||||
|
auto q = Painter(&_premiumMarkFrameCache);
|
||||||
|
_emojiPaintContext->position = QPoint();
|
||||||
|
custom->paint(q, *_emojiPaintContext);
|
||||||
|
q.end();
|
||||||
|
|
||||||
|
p.drawImage(exactPosition, _premiumMarkFrameCache);
|
||||||
|
} else {
|
||||||
|
_emojiPaintContext->position = exactPosition;
|
||||||
|
custom->paint(p, *_emojiPaintContext);
|
||||||
|
}
|
||||||
} else if (const auto emoji = std::get_if<EmojiPtr>(&recent.id.data)) {
|
} else if (const auto emoji = std::get_if<EmojiPtr>(&recent.id.data)) {
|
||||||
if (_mode == Mode::EmojiStatus) {
|
if (_mode == Mode::EmojiStatus) {
|
||||||
position += QPoint(
|
position += QPoint(
|
||||||
|
@ -1478,6 +1521,16 @@ void EmojiListWidget::drawRecent(
|
||||||
} else {
|
} else {
|
||||||
Unexpected("Empty custom emoji in EmojiListWidget::drawRecent.");
|
Unexpected("Empty custom emoji in EmojiListWidget::drawRecent.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (locked) {
|
||||||
|
_premiumMark->paint(
|
||||||
|
p,
|
||||||
|
lockedPainted ? _premiumMarkFrameCache : QImage(),
|
||||||
|
recent.premiumLock,
|
||||||
|
position,
|
||||||
|
_singleSize,
|
||||||
|
width());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmojiListWidget::drawEmoji(
|
void EmojiListWidget::drawEmoji(
|
||||||
|
|
|
@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/round_rect.h"
|
#include "ui/round_rect.h"
|
||||||
#include "base/timer.h"
|
#include "base/timer.h"
|
||||||
|
|
||||||
|
class StickerPremiumMark;
|
||||||
|
|
||||||
namespace style {
|
namespace style {
|
||||||
struct EmojiPan;
|
struct EmojiPan;
|
||||||
} // namespace style
|
} // namespace style
|
||||||
|
@ -89,6 +91,7 @@ struct EmojiListDescriptor {
|
||||||
Fn<std::unique_ptr<Ui::Text::CustomEmoji>(
|
Fn<std::unique_ptr<Ui::Text::CustomEmoji>(
|
||||||
DocumentId,
|
DocumentId,
|
||||||
Fn<void()>)> customRecentFactory;
|
Fn<void()>)> customRecentFactory;
|
||||||
|
base::flat_set<DocumentId> freeEffects;
|
||||||
const style::EmojiPan *st = nullptr;
|
const style::EmojiPan *st = nullptr;
|
||||||
ComposeFeatures features;
|
ComposeFeatures features;
|
||||||
};
|
};
|
||||||
|
@ -148,6 +151,7 @@ public:
|
||||||
const SendMenu::Details &details) override;
|
const SendMenu::Details &details) override;
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<std::vector<QString>> searchQueries() const;
|
[[nodiscard]] rpl::producer<std::vector<QString>> searchQueries() const;
|
||||||
|
[[nodiscard]] rpl::producer<int> recentShownCount() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void visibleTopBottomUpdated(
|
void visibleTopBottomUpdated(
|
||||||
|
@ -400,10 +404,13 @@ private:
|
||||||
int _counts[kEmojiSectionCount];
|
int _counts[kEmojiSectionCount];
|
||||||
std::vector<RecentOne> _recent;
|
std::vector<RecentOne> _recent;
|
||||||
base::flat_set<DocumentId> _recentCustomIds;
|
base::flat_set<DocumentId> _recentCustomIds;
|
||||||
|
base::flat_set<DocumentId> _freeEffects;
|
||||||
base::flat_set<uint64> _repaintsScheduled;
|
base::flat_set<uint64> _repaintsScheduled;
|
||||||
|
rpl::variable<int> _recentShownCount;
|
||||||
std::unique_ptr<Ui::Text::CustomEmojiPaintContext> _emojiPaintContext;
|
std::unique_ptr<Ui::Text::CustomEmojiPaintContext> _emojiPaintContext;
|
||||||
bool _recentPainted = false;
|
bool _recentPainted = false;
|
||||||
bool _grabbingChosen = false;
|
bool _grabbingChosen = false;
|
||||||
|
bool _paintAsPremium = false;
|
||||||
QVector<EmojiPtr> _emoji[kEmojiSectionCount];
|
QVector<EmojiPtr> _emoji[kEmojiSectionCount];
|
||||||
std::vector<CustomSet> _custom;
|
std::vector<CustomSet> _custom;
|
||||||
base::flat_set<DocumentId> _restrictedCustomList;
|
base::flat_set<DocumentId> _restrictedCustomList;
|
||||||
|
@ -417,6 +424,8 @@ private:
|
||||||
Ui::RoundRect _overBg;
|
Ui::RoundRect _overBg;
|
||||||
QImage _searchExpandCache;
|
QImage _searchExpandCache;
|
||||||
|
|
||||||
|
std::unique_ptr<StickerPremiumMark> _premiumMark;
|
||||||
|
QImage _premiumMarkFrameCache;
|
||||||
mutable std::unique_ptr<Ui::RippleAnimation> _colorAllRipple;
|
mutable std::unique_ptr<Ui::RippleAnimation> _colorAllRipple;
|
||||||
bool _colorAllRippleForced = false;
|
bool _colorAllRippleForced = false;
|
||||||
rpl::lifetime _colorAllRippleForcedLifetime;
|
rpl::lifetime _colorAllRippleForcedLifetime;
|
||||||
|
|
|
@ -891,7 +891,7 @@ FieldAutocomplete::Inner::Inner(
|
||||||
_st.pathBg,
|
_st.pathBg,
|
||||||
_st.pathFg,
|
_st.pathFg,
|
||||||
[=] { update(); }))
|
[=] { update(); }))
|
||||||
, _premiumMark(_session)
|
, _premiumMark(_session, st::stickersPremiumLock)
|
||||||
, _previewTimer([=] { showPreview(); }) {
|
, _previewTimer([=] { showPreview(); }) {
|
||||||
_session->downloaderTaskFinished(
|
_session->downloaderTaskFinished(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
|
|
|
@ -219,7 +219,9 @@ StickersListWidget::StickersListWidget(
|
||||||
, _installedWidth(st::stickersTrendingInstalled.font->width(_installedText))
|
, _installedWidth(st::stickersTrendingInstalled.font->width(_installedText))
|
||||||
, _settings(this, tr::lng_stickers_you_have(tr::now))
|
, _settings(this, tr::lng_stickers_you_have(tr::now))
|
||||||
, _previewTimer([=] { showPreview(); })
|
, _previewTimer([=] { showPreview(); })
|
||||||
, _premiumMark(std::make_unique<StickerPremiumMark>(&session()))
|
, _premiumMark(std::make_unique<StickerPremiumMark>(
|
||||||
|
&session(),
|
||||||
|
st::stickersPremiumLock))
|
||||||
, _searchRequestTimer([=] { sendSearchRequest(); }) {
|
, _searchRequestTimer([=] { sendSearchRequest(); }) {
|
||||||
setMouseTracking(true);
|
setMouseTracking(true);
|
||||||
if (st().bg->c.alpha() > 0) {
|
if (st().bg->c.alpha() > 0) {
|
||||||
|
@ -542,8 +544,8 @@ int StickersListWidget::countDesiredHeight(int newWidth) {
|
||||||
const auto minimalLastHeight = (_section == Section::Stickers)
|
const auto minimalLastHeight = (_section == Section::Stickers)
|
||||||
? minimalHeight
|
? minimalHeight
|
||||||
: 0;
|
: 0;
|
||||||
return qMax(minimalHeight, countResult(minimalLastHeight))
|
const auto result = qMax(minimalHeight, countResult(minimalLastHeight));
|
||||||
+ st::stickerPanPadding;
|
return result ? (result + st::stickerPanPadding) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void StickersListWidget::sendSearchRequest() {
|
void StickersListWidget::sendSearchRequest() {
|
||||||
|
@ -675,9 +677,14 @@ void StickersListWidget::refreshSearchRows(
|
||||||
_lastMousePosition = QCursor::pos();
|
_lastMousePosition = QCursor::pos();
|
||||||
|
|
||||||
resizeToWidth(width());
|
resizeToWidth(width());
|
||||||
|
_recentShownCount = _filteredStickers.size();
|
||||||
updateSelected();
|
updateSelected();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpl::producer<int> StickersListWidget::recentShownCount() const {
|
||||||
|
return _recentShownCount.value();
|
||||||
|
}
|
||||||
|
|
||||||
void StickersListWidget::fillLocalSearchRows(const QString &query) {
|
void StickersListWidget::fillLocalSearchRows(const QString &query) {
|
||||||
const auto searchWordsList = TextUtilities::PrepareSearchWords(query);
|
const auto searchWordsList = TextUtilities::PrepareSearchWords(query);
|
||||||
if (searchWordsList.isEmpty()) {
|
if (searchWordsList.isEmpty()) {
|
||||||
|
@ -910,6 +917,7 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
|
||||||
toColumn = _columnCount - toColumn;
|
toColumn = _columnCount - toColumn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_paintAsPremium = session().premium();
|
||||||
_pathGradient->startFrame(0, width(), width() / 2);
|
_pathGradient->startFrame(0, width(), width() / 2);
|
||||||
|
|
||||||
auto &sets = shownSets();
|
auto &sets = shownSets();
|
||||||
|
@ -1489,7 +1497,7 @@ void StickersListWidget::paintSticker(
|
||||||
: (set.id == SearchEmojiSectionSetId())
|
: (set.id == SearchEmojiSectionSetId())
|
||||||
? &_filterStickersCornerEmoji
|
? &_filterStickersCornerEmoji
|
||||||
: nullptr;
|
: nullptr;
|
||||||
if (corner && !corner->empty()) {
|
if (corner && !corner->empty() && _paintAsPremium) {
|
||||||
Assert(index < corner->size());
|
Assert(index < corner->size());
|
||||||
if (const auto emoji = (*corner)[index]) {
|
if (const auto emoji = (*corner)[index]) {
|
||||||
const auto size = Ui::Emoji::GetSizeNormal();
|
const auto size = Ui::Emoji::GetSizeNormal();
|
||||||
|
@ -1960,6 +1968,11 @@ void StickersListWidget::setSection(Section section) {
|
||||||
}
|
}
|
||||||
clearHeavyData();
|
clearHeavyData();
|
||||||
_section = section;
|
_section = section;
|
||||||
|
_recentShownCount = (section == Section::Search)
|
||||||
|
? _filteredStickers.size()
|
||||||
|
: _mySets.empty()
|
||||||
|
? 0
|
||||||
|
: _mySets.front().stickers.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
void StickersListWidget::clearHeavyData() {
|
void StickersListWidget::clearHeavyData() {
|
||||||
|
@ -2242,6 +2255,9 @@ void StickersListWidget::refreshRecentStickers(bool performResize) {
|
||||||
clearSelection();
|
clearSelection();
|
||||||
|
|
||||||
auto recentPack = collectRecentStickers();
|
auto recentPack = collectRecentStickers();
|
||||||
|
if (_section == Section::Stickers) {
|
||||||
|
_recentShownCount = recentPack.size();
|
||||||
|
}
|
||||||
auto recentIt = std::find_if(_mySets.begin(), _mySets.end(), [](auto &set) {
|
auto recentIt = std::find_if(_mySets.begin(), _mySets.end(), [](auto &set) {
|
||||||
return set.id == Data::Stickers::RecentSetId;
|
return set.id == Data::Stickers::RecentSetId;
|
||||||
});
|
});
|
||||||
|
|
|
@ -128,6 +128,7 @@ public:
|
||||||
bool mySetsEmpty() const;
|
bool mySetsEmpty() const;
|
||||||
|
|
||||||
void applySearchQuery(std::vector<QString> &&query);
|
void applySearchQuery(std::vector<QString> &&query);
|
||||||
|
[[nodiscard]] rpl::producer<int> recentShownCount() const;
|
||||||
|
|
||||||
~StickersListWidget();
|
~StickersListWidget();
|
||||||
|
|
||||||
|
@ -388,6 +389,7 @@ private:
|
||||||
base::flat_set<not_null<DocumentData*>> _favedStickersMap;
|
base::flat_set<not_null<DocumentData*>> _favedStickersMap;
|
||||||
std::weak_ptr<Lottie::FrameRenderer> _lottieRenderer;
|
std::weak_ptr<Lottie::FrameRenderer> _lottieRenderer;
|
||||||
|
|
||||||
|
bool _paintAsPremium = false;
|
||||||
bool _showingSetById = false;
|
bool _showingSetById = false;
|
||||||
crl::time _lastScrolledAt = 0;
|
crl::time _lastScrolledAt = 0;
|
||||||
crl::time _lastFullUpdatedAt = 0;
|
crl::time _lastFullUpdatedAt = 0;
|
||||||
|
@ -437,6 +439,7 @@ private:
|
||||||
|
|
||||||
std::vector<not_null<DocumentData*>> _filteredStickers;
|
std::vector<not_null<DocumentData*>> _filteredStickers;
|
||||||
std::vector<EmojiPtr> _filterStickersCornerEmoji;
|
std::vector<EmojiPtr> _filterStickersCornerEmoji;
|
||||||
|
rpl::variable<int> _recentShownCount;
|
||||||
std::map<QString, std::vector<uint64>> _searchCache;
|
std::map<QString, std::vector<uint64>> _searchCache;
|
||||||
std::vector<std::pair<uint64, QStringList>> _searchIndex;
|
std::vector<std::pair<uint64, QStringList>> _searchIndex;
|
||||||
base::Timer _searchRequestTimer;
|
base::Timer _searchRequestTimer;
|
||||||
|
|
|
@ -972,6 +972,7 @@ void Selector::createList() {
|
||||||
: st::reactPanelScrollRounded);
|
: st::reactPanelScrollRounded);
|
||||||
_scroll->hide();
|
_scroll->hide();
|
||||||
|
|
||||||
|
const auto effects = !_reactions.stickers.empty();
|
||||||
const auto st = lifetime().make_state<style::EmojiPan>(_st);
|
const auto st = lifetime().make_state<style::EmojiPan>(_st);
|
||||||
st->padding.setTop(_skipy);
|
st->padding.setTop(_skipy);
|
||||||
if (!_reactions.customAllowed) {
|
if (!_reactions.customAllowed) {
|
||||||
|
@ -979,15 +980,35 @@ void Selector::createList() {
|
||||||
}
|
}
|
||||||
auto lists = _scroll->setOwnedWidget(
|
auto lists = _scroll->setOwnedWidget(
|
||||||
object_ptr<Ui::VerticalLayout>(_scroll));
|
object_ptr<Ui::VerticalLayout>(_scroll));
|
||||||
|
auto recentList = _strip
|
||||||
|
? _unifiedFactoryOwner->unifiedIdsList()
|
||||||
|
: _recent;
|
||||||
|
auto freeEffects = base::flat_set<DocumentId>();
|
||||||
|
if (effects) {
|
||||||
|
auto free = base::flat_set<Data::ReactionId>();
|
||||||
|
free.reserve(_reactions.recent.size());
|
||||||
|
for (const auto &reaction : _reactions.recent) {
|
||||||
|
if (!reaction.premium) {
|
||||||
|
free.emplace(reaction.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto &id : recentList) {
|
||||||
|
const auto reactionId = _strip
|
||||||
|
? _unifiedFactoryOwner->lookupReactionId(id)
|
||||||
|
: Data::ReactionId{ id };
|
||||||
|
if (free.contains(reactionId)) {
|
||||||
|
freeEffects.insert(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
_list = lists->add(
|
_list = lists->add(
|
||||||
object_ptr<EmojiListWidget>(lists, EmojiListDescriptor{
|
object_ptr<EmojiListWidget>(lists, EmojiListDescriptor{
|
||||||
.show = _show,
|
.show = _show,
|
||||||
.mode = _listMode,
|
.mode = _listMode,
|
||||||
.paused = [] { return false; },
|
.paused = [] { return false; },
|
||||||
.customRecentList = (_strip
|
.customRecentList = std::move(recentList),
|
||||||
? _unifiedFactoryOwner->unifiedIdsList()
|
|
||||||
: _recent),
|
|
||||||
.customRecentFactory = _unifiedFactoryOwner->factory(),
|
.customRecentFactory = _unifiedFactoryOwner->factory(),
|
||||||
|
.freeEffects = std::move(freeEffects),
|
||||||
.st = st,
|
.st = st,
|
||||||
}));
|
}));
|
||||||
if (!_reactions.stickers.empty()) {
|
if (!_reactions.stickers.empty()) {
|
||||||
|
@ -1092,6 +1113,27 @@ void Selector::createList() {
|
||||||
) | rpl::start_with_next([=](std::vector<QString> &&query) {
|
) | rpl::start_with_next([=](std::vector<QString> &&query) {
|
||||||
_stickers->applySearchQuery(std::move(query));
|
_stickers->applySearchQuery(std::move(query));
|
||||||
}, _stickers->lifetime());
|
}, _stickers->lifetime());
|
||||||
|
|
||||||
|
|
||||||
|
rpl::combine(
|
||||||
|
_list->recentShownCount(),
|
||||||
|
_stickers->recentShownCount()
|
||||||
|
) | rpl::start_with_next([=](int emoji, int stickers) {
|
||||||
|
_showEmptySearch = !emoji && !stickers;
|
||||||
|
_scroll->update();
|
||||||
|
}, _scroll->lifetime());
|
||||||
|
|
||||||
|
_scroll->paintRequest() | rpl::filter([=] {
|
||||||
|
return _showEmptySearch;
|
||||||
|
}) | rpl::start_with_next([=] {
|
||||||
|
auto p = QPainter(_scroll);
|
||||||
|
p.setPen(st::windowSubTextFg);
|
||||||
|
p.setFont(st::normalFont);
|
||||||
|
p.drawText(
|
||||||
|
_scroll->rect(),
|
||||||
|
tr::lng_effect_none(tr::now),
|
||||||
|
style::al_center);
|
||||||
|
}, _scroll->lifetime());
|
||||||
} else {
|
} else {
|
||||||
_list->setMinimalHeight(geometry.width(), _scroll->height());
|
_list->setMinimalHeight(geometry.width(), _scroll->height());
|
||||||
}
|
}
|
||||||
|
|
|
@ -205,6 +205,7 @@ private:
|
||||||
Ui::PlainShadow *_shadow = nullptr;
|
Ui::PlainShadow *_shadow = nullptr;
|
||||||
rpl::variable<int> _shadowTop = 0;
|
rpl::variable<int> _shadowTop = 0;
|
||||||
rpl::variable<int> _shadowSkip = 0;
|
rpl::variable<int> _shadowSkip = 0;
|
||||||
|
bool _showEmptySearch = false;
|
||||||
|
|
||||||
QImage _paintBuffer;
|
QImage _paintBuffer;
|
||||||
Ui::Animations::Simple _expanding;
|
Ui::Animations::Simple _expanding;
|
||||||
|
|
|
@ -29,9 +29,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/chat/chat_theme.h"
|
#include "ui/chat/chat_theme.h"
|
||||||
#include "ui/effects/path_shift_gradient.h"
|
#include "ui/effects/path_shift_gradient.h"
|
||||||
#include "ui/effects/ripple_animation.h"
|
#include "ui/effects/ripple_animation.h"
|
||||||
|
#include "ui/text/text_utilities.h"
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
|
#include "ui/widgets/labels.h"
|
||||||
#include "ui/widgets/popup_menu.h"
|
#include "ui/widgets/popup_menu.h"
|
||||||
#include "ui/widgets/shadow.h"
|
#include "ui/widgets/shadow.h"
|
||||||
|
#include "ui/wrap/padding_wrap.h"
|
||||||
#include "ui/painter.h"
|
#include "ui/painter.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "data/data_document_media.h"
|
#include "data/data_document_media.h"
|
||||||
|
@ -42,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
#include "settings/settings_premium.h"
|
||||||
#include "window/themes/window_theme.h"
|
#include "window/themes/window_theme.h"
|
||||||
#include "window/section_widget.h"
|
#include "window/section_widget.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
@ -89,6 +93,8 @@ private:
|
||||||
void paintEvent(QPaintEvent *e) override;
|
void paintEvent(QPaintEvent *e) override;
|
||||||
void mousePressEvent(QMouseEvent *e) override;
|
void mousePressEvent(QMouseEvent *e) override;
|
||||||
|
|
||||||
|
[[nodiscard]] bool canSend() const;
|
||||||
|
|
||||||
void setupGeometry(QPoint position);
|
void setupGeometry(QPoint position);
|
||||||
void setupBackground();
|
void setupBackground();
|
||||||
void setupItem();
|
void setupItem();
|
||||||
|
@ -110,6 +116,9 @@ private:
|
||||||
const AdminLog::OwnedItem _replyTo;
|
const AdminLog::OwnedItem _replyTo;
|
||||||
const AdminLog::OwnedItem _item;
|
const AdminLog::OwnedItem _item;
|
||||||
const std::unique_ptr<Ui::FlatButton> _send;
|
const std::unique_ptr<Ui::FlatButton> _send;
|
||||||
|
const std::unique_ptr<Ui::PaddingWrap<Ui::FlatLabel>> _premiumPromoLabel;
|
||||||
|
const not_null<Ui::RpWidget*> _bottom;
|
||||||
|
const Fn<void()> _close;
|
||||||
const Fn<void(Action, Details)> _actionWithEffect;
|
const Fn<void(Action, Details)> _actionWithEffect;
|
||||||
|
|
||||||
QImage _icon;
|
QImage _icon;
|
||||||
|
@ -236,11 +245,26 @@ EffectPreview::EffectPreview(
|
||||||
_replyTo->data()->fullId(),
|
_replyTo->data()->fullId(),
|
||||||
tr::lng_settings_chat_message_reply(tr::now),
|
tr::lng_settings_chat_message_reply(tr::now),
|
||||||
_effectId))
|
_effectId))
|
||||||
, _send(
|
, _send(canSend()
|
||||||
std::make_unique<BottomRounded>(
|
? std::make_unique<BottomRounded>(
|
||||||
this,
|
this,
|
||||||
tr::lng_effect_send(tr::now),
|
tr::lng_effect_send(tr::now),
|
||||||
st::effectPreviewSend))
|
st::effectPreviewSend)
|
||||||
|
: nullptr)
|
||||||
|
, _premiumPromoLabel(canSend()
|
||||||
|
? nullptr
|
||||||
|
: std::make_unique<Ui::PaddingWrap<Ui::FlatLabel>>(
|
||||||
|
this,
|
||||||
|
object_ptr<Ui::FlatLabel>(
|
||||||
|
this,
|
||||||
|
tr::lng_effect_premium(
|
||||||
|
lt_link,
|
||||||
|
tr::lng_effect_premium_link() | Ui::Text::ToLink(),
|
||||||
|
Ui::Text::WithEntities),
|
||||||
|
st::effectPreviewPromoLabel),
|
||||||
|
st::effectPreviewPromoPadding))
|
||||||
|
, _bottom(_send ? ((Ui::RpWidget*)_send.get()) : _premiumPromoLabel.get())
|
||||||
|
, _close(done)
|
||||||
, _actionWithEffect(ComposeActionWithEffect(action, _effectId, done)) {
|
, _actionWithEffect(ComposeActionWithEffect(action, _effectId, done)) {
|
||||||
setupGeometry(position);
|
setupGeometry(position);
|
||||||
setupBackground();
|
setupBackground();
|
||||||
|
@ -291,8 +315,9 @@ void EffectPreview::setupGeometry(QPoint position) {
|
||||||
const auto shadow = st::previewMenu.shadow;
|
const auto shadow = st::previewMenu.shadow;
|
||||||
const auto extend = shadow.extend;
|
const auto extend = shadow.extend;
|
||||||
_inner = QRect(QPoint(extend.left(), extend.top()), innerSize);
|
_inner = QRect(QPoint(extend.left(), extend.top()), innerSize);
|
||||||
|
_bottom->resizeToWidth(_inner.width());
|
||||||
const auto size = _inner.marginsAdded(extend).size()
|
const auto size = _inner.marginsAdded(extend).size()
|
||||||
+ QSize(0, _send->height());
|
+ QSize(0, _bottom->height());
|
||||||
const auto left = std::max(
|
const auto left = std::max(
|
||||||
std::min(
|
std::min(
|
||||||
position.x() - size.width() / 2,
|
position.x() - size.width() / 2,
|
||||||
|
@ -305,11 +330,11 @@ void EffectPreview::setupGeometry(QPoint position) {
|
||||||
parent->height() - size.height()),
|
parent->height() - size.height()),
|
||||||
topMin);
|
topMin);
|
||||||
setGeometry(left, top, size.width(), size.height());
|
setGeometry(left, top, size.width(), size.height());
|
||||||
_send->setGeometry(
|
_bottom->setGeometry(
|
||||||
_inner.x(),
|
_inner.x(),
|
||||||
_inner.y() + _inner.height(),
|
_inner.y() + _inner.height(),
|
||||||
_inner.width(),
|
_inner.width(),
|
||||||
_send->height());
|
_bottom->height());
|
||||||
}
|
}
|
||||||
|
|
||||||
void EffectPreview::setupBackground() {
|
void EffectPreview::setupBackground() {
|
||||||
|
@ -343,7 +368,7 @@ void EffectPreview::setupItem() {
|
||||||
|
|
||||||
void EffectPreview::repaintBackground() {
|
void EffectPreview::repaintBackground() {
|
||||||
const auto ratio = style::DevicePixelRatio();
|
const auto ratio = style::DevicePixelRatio();
|
||||||
const auto inner = _inner.size() + QSize(0, _send->height());
|
const auto inner = _inner.size() + QSize(0, _bottom->height());
|
||||||
auto bg = QImage(
|
auto bg = QImage(
|
||||||
inner * ratio,
|
inner * ratio,
|
||||||
QImage::Format_ARGB32_Premultiplied);
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
|
@ -357,7 +382,7 @@ void EffectPreview::repaintBackground() {
|
||||||
QSize(inner.width(), inner.height() * 5),
|
QSize(inner.width(), inner.height() * 5),
|
||||||
QRect(QPoint(), inner));
|
QRect(QPoint(), inner));
|
||||||
p.fillRect(
|
p.fillRect(
|
||||||
QRect(0, _inner.height(), _inner.width(), _send->height()),
|
QRect(0, _inner.height(), _inner.width(), _bottom->height()),
|
||||||
st::previewMarkRead.bgColor);
|
st::previewMarkRead.bgColor);
|
||||||
auto hq = PainterHighQualityEnabler(p);
|
auto hq = PainterHighQualityEnabler(p);
|
||||||
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
|
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
|
||||||
|
@ -409,14 +434,32 @@ void EffectPreview::createLottie() {
|
||||||
}, raw->lifetime());
|
}, raw->lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool EffectPreview::canSend() const {
|
||||||
|
return !_effect.premium || _show->session().premium();
|
||||||
|
}
|
||||||
|
|
||||||
void EffectPreview::setupSend(Details details) {
|
void EffectPreview::setupSend(Details details) {
|
||||||
_send->setClickedCallback([=] {
|
if (_send) {
|
||||||
_actionWithEffect(Api::SendOptions(), details);
|
_send->setClickedCallback([=] {
|
||||||
});
|
_actionWithEffect(Api::SendOptions(), details);
|
||||||
const auto type = details.type;
|
});
|
||||||
SetupMenuAndShortcuts(_send.get(), _show, [=] {
|
const auto type = details.type;
|
||||||
return Details{ .type = type };
|
SetupMenuAndShortcuts(_send.get(), _show, [=] {
|
||||||
}, _actionWithEffect);
|
return Details{ .type = type };
|
||||||
|
}, _actionWithEffect);
|
||||||
|
} else {
|
||||||
|
_premiumPromoLabel->entity()->setClickHandlerFilter([=](auto&&...) {
|
||||||
|
const auto window = _show->resolveWindow(
|
||||||
|
ChatHelpers::WindowUsage::PremiumPromo);
|
||||||
|
if (window) {
|
||||||
|
if (const auto onstack = _close) {
|
||||||
|
onstack();
|
||||||
|
}
|
||||||
|
Settings::ShowPremium(window, "message_effect");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EffectPreview::checkReady() {
|
bool EffectPreview::checkReady() {
|
||||||
|
|
|
@ -1120,3 +1120,12 @@ effectPreviewSend: FlatButton(previewMarkRead) {
|
||||||
bgColor: transparent;
|
bgColor: transparent;
|
||||||
overBgColor: transparent;
|
overBgColor: transparent;
|
||||||
}
|
}
|
||||||
|
effectPreviewPromoLabel: FlatLabel(defaultFlatLabel) {
|
||||||
|
minWidth: 64px;
|
||||||
|
align: align(top);
|
||||||
|
textFg: windowSubTextFg;
|
||||||
|
style: TextStyle(defaultTextStyle) {
|
||||||
|
font: font(11px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
effectPreviewPromoPadding: margins(4px, 6px, 4px, 6px);
|
||||||
|
|
Loading…
Reference in a new issue