mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Animate reactions strip appearance.
This commit is contained in:
parent
a0d5456a4d
commit
8d2ebdbb99
7 changed files with 190 additions and 438 deletions
|
@ -314,3 +314,5 @@ stickersPremiumLock: icon{{ "emoji/premium_lock", premiumButtonFg }};
|
||||||
|
|
||||||
reactStripExtend: margins(21px, 49px, 39px, 0px);
|
reactStripExtend: margins(21px, 49px, 39px, 0px);
|
||||||
reactStripHeight: 40px;
|
reactStripHeight: 40px;
|
||||||
|
reactStripSize: 32px;
|
||||||
|
reactStripSkip: 7px;
|
||||||
|
|
|
@ -33,13 +33,7 @@ constexpr auto kToggleDuration = crl::time(120);
|
||||||
constexpr auto kActivateDuration = crl::time(150);
|
constexpr auto kActivateDuration = crl::time(150);
|
||||||
constexpr auto kExpandDuration = crl::time(300);
|
constexpr auto kExpandDuration = crl::time(300);
|
||||||
constexpr auto kCollapseDuration = crl::time(250);
|
constexpr auto kCollapseDuration = crl::time(250);
|
||||||
constexpr auto kBgCacheIndex = 0;
|
constexpr auto kEmojiCacheIndex = 0;
|
||||||
constexpr auto kShadowCacheIndex = 0;
|
|
||||||
constexpr auto kEmojiCacheIndex = 1;
|
|
||||||
constexpr auto kCacheColumsCount = 2;
|
|
||||||
constexpr auto kOverlayMaskCacheIndex = 0;
|
|
||||||
constexpr auto kOverlayShadowCacheIndex = 1;
|
|
||||||
constexpr auto kOverlayCacheColumsCount = 2;
|
|
||||||
constexpr auto kButtonShowDelay = crl::time(300);
|
constexpr auto kButtonShowDelay = crl::time(300);
|
||||||
constexpr auto kButtonExpandDelay = crl::time(25);
|
constexpr auto kButtonExpandDelay = crl::time(25);
|
||||||
constexpr auto kButtonHideDelay = crl::time(300);
|
constexpr auto kButtonHideDelay = crl::time(300);
|
||||||
|
@ -358,53 +352,19 @@ Manager::Manager(
|
||||||
: _iconFactory(std::move(iconFactory))
|
: _iconFactory(std::move(iconFactory))
|
||||||
, _outer(CountOuterSize())
|
, _outer(CountOuterSize())
|
||||||
, _inner(QRect({}, st::reactionCornerSize))
|
, _inner(QRect({}, st::reactionCornerSize))
|
||||||
, _overlayFull(
|
, _cachedRound(
|
||||||
QRect(0, 0, _inner.width(), _inner.width()).marginsAdded(
|
st::reactionCornerSize,
|
||||||
st::reactionCornerShadow
|
st::reactionCornerShadow,
|
||||||
).size())
|
_inner.width())
|
||||||
, _uniqueLimit(std::move(uniqueLimitValue))
|
, _uniqueLimit(std::move(uniqueLimitValue))
|
||||||
, _buttonShowTimer([=] { showButtonDelayed(); })
|
, _buttonShowTimer([=] { showButtonDelayed(); })
|
||||||
, _buttonUpdate(std::move(buttonUpdate)) {
|
, _buttonUpdate(std::move(buttonUpdate)) {
|
||||||
static_assert(!(kFramesCount % kDivider));
|
|
||||||
|
|
||||||
_inner.translate(QRect({}, _outer).center() - _inner.center());
|
_inner.translate(QRect({}, _outer).center() - _inner.center());
|
||||||
|
|
||||||
const auto ratio = style::DevicePixelRatio();
|
_emojiParts = _cachedRound.PrepareFramesCache(_outer);
|
||||||
_cacheBg = QImage(
|
_expandedBuffer = _cachedRound.PrepareImage(QSize(
|
||||||
_outer.width() * kDivider * ratio,
|
_outer.width(),
|
||||||
_outer.height() * kFramesCount / kDivider * ratio,
|
_outer.height() + st::reactionCornerAddedHeightMax));
|
||||||
QImage::Format_ARGB32_Premultiplied);
|
|
||||||
_cacheBg.setDevicePixelRatio(ratio);
|
|
||||||
_cacheBg.fill(Qt::transparent);
|
|
||||||
_cacheParts = QImage(
|
|
||||||
_outer.width() * kDivider * kCacheColumsCount * ratio,
|
|
||||||
_outer.height() * kFramesCount / kDivider * ratio,
|
|
||||||
QImage::Format_ARGB32_Premultiplied);
|
|
||||||
_cacheParts.setDevicePixelRatio(ratio);
|
|
||||||
_cacheParts.fill(Qt::transparent);
|
|
||||||
_overlayCacheParts = QImage(
|
|
||||||
_overlayFull.width() * kDivider * kOverlayCacheColumsCount * ratio,
|
|
||||||
_overlayFull.height() * kFramesCount / kDivider * ratio,
|
|
||||||
QImage::Format_ARGB32_Premultiplied);
|
|
||||||
_overlayCacheParts.setDevicePixelRatio(ratio);
|
|
||||||
_overlayMaskScaled = QImage(
|
|
||||||
_overlayFull * ratio,
|
|
||||||
QImage::Format_ARGB32_Premultiplied);
|
|
||||||
_overlayMaskScaled.setDevicePixelRatio(ratio);
|
|
||||||
_overlayShadowScaled = QImage(
|
|
||||||
_overlayFull * ratio,
|
|
||||||
QImage::Format_ARGB32_Premultiplied);
|
|
||||||
_overlayShadowScaled.setDevicePixelRatio(ratio);
|
|
||||||
_shadowBuffer = QImage(
|
|
||||||
_outer * ratio,
|
|
||||||
QImage::Format_ARGB32_Premultiplied);
|
|
||||||
_shadowBuffer.setDevicePixelRatio(ratio);
|
|
||||||
_expandedBuffer = QImage(
|
|
||||||
_outer.width() * ratio,
|
|
||||||
(_outer.height() + st::reactionCornerAddedHeightMax) * ratio,
|
|
||||||
QImage::Format_ARGB32_Premultiplied);
|
|
||||||
_expandedBuffer.setDevicePixelRatio(ratio);
|
|
||||||
|
|
||||||
if (wheelEventsTarget) {
|
if (wheelEventsTarget) {
|
||||||
stealWheelEvents(wheelEventsTarget);
|
stealWheelEvents(wheelEventsTarget);
|
||||||
}
|
}
|
||||||
|
@ -736,7 +696,6 @@ void Manager::resolveMainReactionIcon() {
|
||||||
|
|
||||||
void Manager::setMainReactionIcon() {
|
void Manager::setMainReactionIcon() {
|
||||||
_mainReactionLifetime.destroy();
|
_mainReactionLifetime.destroy();
|
||||||
ranges::fill(_validBg, false);
|
|
||||||
ranges::fill(_validEmoji, false);
|
ranges::fill(_validEmoji, false);
|
||||||
loadIcons();
|
loadIcons();
|
||||||
const auto i = _loadCache.find(_mainReactionMedia->owner());
|
const auto i = _loadCache.find(_mainReactionMedia->owner());
|
||||||
|
@ -1013,17 +972,13 @@ void Manager::paintButton(
|
||||||
const auto q = expanded ? &layeredPainter.emplace(&_expandedBuffer) : &p;
|
const auto q = expanded ? &layeredPainter.emplace(&_expandedBuffer) : &p;
|
||||||
const auto shadow = context.st->shadowFg()->c;
|
const auto shadow = context.st->shadowFg()->c;
|
||||||
const auto background = context.st->windowBg()->c;
|
const auto background = context.st->windowBg()->c;
|
||||||
setShadowColor(shadow);
|
_cachedRound.setShadowColor(shadow);
|
||||||
setBackgroundColor(background);
|
_cachedRound.setBackgroundColor(background);
|
||||||
if (expanded) {
|
if (expanded) {
|
||||||
q->fillRect(QRect(QPoint(), size), context.st->windowBg());
|
q->fillRect(QRect(QPoint(), size), context.st->windowBg());
|
||||||
} else {
|
} else {
|
||||||
const auto source = validateFrame(
|
const auto frame = _cachedRound.validateFrame(frameIndex, scale);
|
||||||
frameIndex,
|
p.drawImage(position, *frame.image, frame.rect);
|
||||||
scale,
|
|
||||||
background,
|
|
||||||
shadow);
|
|
||||||
p.drawImage(position, _cacheBg, source);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto current = (button == _button.get());
|
const auto current = (button == _button.get());
|
||||||
|
@ -1068,18 +1023,25 @@ void Manager::paintButton(
|
||||||
? QPoint(0, expanded - appearShift)
|
? QPoint(0, expanded - appearShift)
|
||||||
: QPoint(0, appearShift);
|
: QPoint(0, appearShift);
|
||||||
q->setOpacity(1. - opacity);
|
q->setOpacity(1. - opacity);
|
||||||
q->drawImage(appearPosition, _cacheParts, source);
|
q->drawImage(appearPosition, _emojiParts, source);
|
||||||
q->setOpacity(1.);
|
q->setOpacity(1.);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
p.drawImage(mainEmojiPosition, _cacheParts, source);
|
p.drawImage(mainEmojiPosition, _emojiParts, source);
|
||||||
}
|
}
|
||||||
if (current && !expanded) {
|
if (current && !expanded) {
|
||||||
clearAppearAnimations();
|
clearAppearAnimations();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (expanded) {
|
if (expanded) {
|
||||||
overlayExpandedBorder(*q, size, expandRatio, scale, shadow);
|
const auto radiusMin = _inner.height() / 2.;
|
||||||
|
const auto radiusMax = _inner.width() / 2.;
|
||||||
|
_cachedRound.overlayExpandedBorder(
|
||||||
|
*q,
|
||||||
|
size,
|
||||||
|
expandRatio,
|
||||||
|
radiusMin + expandRatio * (radiusMax - radiusMin),
|
||||||
|
scale);
|
||||||
layeredPainter.reset();
|
layeredPainter.reset();
|
||||||
p.drawImage(
|
p.drawImage(
|
||||||
geometry,
|
geometry,
|
||||||
|
@ -1097,6 +1059,10 @@ void Manager::paintInnerGradients(
|
||||||
not_null<Button*> button,
|
not_null<Button*> button,
|
||||||
int scroll,
|
int scroll,
|
||||||
float64 expandRatio) {
|
float64 expandRatio) {
|
||||||
|
if (_gradientBackground != background) {
|
||||||
|
_gradientBackground = background;
|
||||||
|
_topGradient = _bottomGradient = QImage();
|
||||||
|
}
|
||||||
const auto endScroll = button->scrollMax() - scroll;
|
const auto endScroll = button->scrollMax() - scroll;
|
||||||
const auto size = st::reactionGradientSize;
|
const auto size = st::reactionGradientSize;
|
||||||
const auto ensureGradient = [&](QImage &gradient, bool top) {
|
const auto ensureGradient = [&](QImage &gradient, bool top) {
|
||||||
|
@ -1131,164 +1097,6 @@ void Manager::paintInnerGradients(
|
||||||
p.setOpacity(1.);
|
p.setOpacity(1.);
|
||||||
}
|
}
|
||||||
|
|
||||||
Manager::OverlayImage Manager::validateOverlayMask(
|
|
||||||
int frameIndex,
|
|
||||||
QSize innerSize,
|
|
||||||
float64 radius,
|
|
||||||
float64 scale) {
|
|
||||||
const auto ratio = style::DevicePixelRatio();
|
|
||||||
const auto cached = (scale == 1.);
|
|
||||||
const auto full = cached
|
|
||||||
? overlayCacheRect(frameIndex, kOverlayMaskCacheIndex)
|
|
||||||
: QRect(QPoint(), _overlayFull * ratio);
|
|
||||||
|
|
||||||
const auto maskSize = QSize(
|
|
||||||
_overlayFull.width(),
|
|
||||||
_overlayFull.height() + innerSize.height() - innerSize.width());
|
|
||||||
|
|
||||||
const auto result = OverlayImage{
|
|
||||||
.cache = cached ? &_overlayCacheParts : &_overlayMaskScaled,
|
|
||||||
.source = QRect(full.topLeft(), maskSize * ratio),
|
|
||||||
};
|
|
||||||
if (cached && _validOverlayMask[frameIndex]) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto p = QPainter(result.cache.get());
|
|
||||||
const auto position = full.topLeft() / ratio;
|
|
||||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
|
||||||
p.fillRect(QRect(position, maskSize), Qt::transparent);
|
|
||||||
|
|
||||||
auto hq = PainterHighQualityEnabler(p);
|
|
||||||
const auto inner = QRect(position + _inner.topLeft(), innerSize);
|
|
||||||
p.setPen(Qt::NoPen);
|
|
||||||
p.setBrush(Qt::white);
|
|
||||||
if (scale != 1.) {
|
|
||||||
const auto center = inner.center();
|
|
||||||
p.save();
|
|
||||||
p.translate(center);
|
|
||||||
p.scale(scale, scale);
|
|
||||||
p.translate(-center);
|
|
||||||
}
|
|
||||||
p.drawRoundedRect(inner, radius, radius);
|
|
||||||
if (scale != 1.) {
|
|
||||||
p.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cached) {
|
|
||||||
_validOverlayMask[frameIndex] = true;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Manager::OverlayImage Manager::validateOverlayShadow(
|
|
||||||
int frameIndex,
|
|
||||||
QSize innerSize,
|
|
||||||
float64 radius,
|
|
||||||
float64 scale,
|
|
||||||
const QColor &shadow,
|
|
||||||
const OverlayImage &mask) {
|
|
||||||
const auto ratio = style::DevicePixelRatio();
|
|
||||||
const auto cached = (scale == 1.);
|
|
||||||
const auto full = cached
|
|
||||||
? overlayCacheRect(frameIndex, kOverlayShadowCacheIndex)
|
|
||||||
: QRect(QPoint(), _overlayFull * ratio);
|
|
||||||
|
|
||||||
const auto maskSize = QSize(
|
|
||||||
_overlayFull.width(),
|
|
||||||
_overlayFull.height() + innerSize.height() - innerSize.width());
|
|
||||||
|
|
||||||
const auto result = OverlayImage{
|
|
||||||
.cache = cached ? &_overlayCacheParts : &_overlayShadowScaled,
|
|
||||||
.source = QRect(full.topLeft(), maskSize * ratio),
|
|
||||||
};
|
|
||||||
if (cached && _validOverlayShadow[frameIndex]) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto position = full.topLeft() / ratio;
|
|
||||||
|
|
||||||
_overlayShadowScaled.fill(Qt::transparent);
|
|
||||||
const auto inner = QRect(_inner.topLeft(), innerSize);
|
|
||||||
const auto add = style::ConvertScale(2.5);
|
|
||||||
const auto shift = style::ConvertScale(0.5);
|
|
||||||
const auto extended = QRectF(inner).marginsAdded({ add, add, add, add });
|
|
||||||
{
|
|
||||||
auto p = QPainter(&_overlayShadowScaled);
|
|
||||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
|
||||||
auto hq = PainterHighQualityEnabler(p);
|
|
||||||
p.setPen(Qt::NoPen);
|
|
||||||
p.setBrush(shadow);
|
|
||||||
if (scale != 1.) {
|
|
||||||
const auto center = inner.center();
|
|
||||||
p.translate(center);
|
|
||||||
p.scale(scale, scale);
|
|
||||||
p.translate(-center);
|
|
||||||
}
|
|
||||||
p.drawRoundedRect(extended.translated(0, shift), radius, radius);
|
|
||||||
p.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
_overlayShadowScaled = Images::Blur(std::move(_overlayShadowScaled));
|
|
||||||
|
|
||||||
auto q = Painter(result.cache);
|
|
||||||
if (result.cache != &_overlayShadowScaled) {
|
|
||||||
q.setCompositionMode(QPainter::CompositionMode_Source);
|
|
||||||
q.drawImage(
|
|
||||||
QRect(position, maskSize),
|
|
||||||
_overlayShadowScaled,
|
|
||||||
QRect(QPoint(), maskSize * ratio));
|
|
||||||
}
|
|
||||||
q.setCompositionMode(QPainter::CompositionMode_DestinationOut);
|
|
||||||
q.drawImage(QRect(position, maskSize), *mask.cache, mask.source);
|
|
||||||
|
|
||||||
if (cached) {
|
|
||||||
_validOverlayShadow[frameIndex] = true;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Manager::overlayExpandedBorder(
|
|
||||||
Painter &p,
|
|
||||||
QSize size,
|
|
||||||
float64 expandRatio,
|
|
||||||
float64 scale,
|
|
||||||
const QColor &shadow) {
|
|
||||||
const auto radiusMin = _inner.height() / 2.;
|
|
||||||
const auto radiusMax = _inner.width() / 2.;
|
|
||||||
const auto progress = expandRatio;
|
|
||||||
const auto frame = int(base::SafeRound(progress * (kFramesCount - 1)));
|
|
||||||
const auto radius = radiusMin
|
|
||||||
+ (frame / float64(kFramesCount - 1)) * (radiusMax - radiusMin);
|
|
||||||
const auto innerSize = QSize(_inner.width(), int(std::ceil(radius * 2)));
|
|
||||||
|
|
||||||
const auto overlayMask = validateOverlayMask(
|
|
||||||
frame,
|
|
||||||
innerSize,
|
|
||||||
radius,
|
|
||||||
scale);
|
|
||||||
const auto overlayShadow = validateOverlayShadow(
|
|
||||||
frame,
|
|
||||||
innerSize,
|
|
||||||
radius,
|
|
||||||
scale,
|
|
||||||
shadow,
|
|
||||||
overlayMask);
|
|
||||||
|
|
||||||
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
|
|
||||||
paintLongImage(
|
|
||||||
p,
|
|
||||||
QRect(QPoint(), size),
|
|
||||||
*overlayMask.cache,
|
|
||||||
overlayMask.source);
|
|
||||||
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
|
||||||
paintLongImage(
|
|
||||||
p,
|
|
||||||
QRect(QPoint(), size),
|
|
||||||
*overlayShadow.cache,
|
|
||||||
overlayShadow.source);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Manager::onlyMainEmojiVisible() const {
|
bool Manager::onlyMainEmojiVisible() const {
|
||||||
if (_icons.empty()) {
|
if (_icons.empty()) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -1330,37 +1138,6 @@ void Manager::clearAppearAnimations() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Manager::paintLongImage(
|
|
||||||
QPainter &p,
|
|
||||||
QRect geometry,
|
|
||||||
const QImage &image,
|
|
||||||
QRect source) {
|
|
||||||
const auto factor = style::DevicePixelRatio();
|
|
||||||
const auto sourceHeight = (source.height() / factor);
|
|
||||||
const auto part = (sourceHeight / 2) - 1;
|
|
||||||
const auto fill = geometry.height() - 2 * part;
|
|
||||||
const auto half = part * factor;
|
|
||||||
const auto top = source.height() - half;
|
|
||||||
p.drawImage(
|
|
||||||
geometry.topLeft(),
|
|
||||||
image,
|
|
||||||
QRect(source.x(), source.y(), source.width(), half));
|
|
||||||
p.drawImage(
|
|
||||||
QRect(
|
|
||||||
geometry.topLeft() + QPoint(0, part),
|
|
||||||
QSize(source.width() / factor, fill)),
|
|
||||||
image,
|
|
||||||
QRect(
|
|
||||||
source.x(),
|
|
||||||
source.y() + half,
|
|
||||||
source.width(),
|
|
||||||
top - half));
|
|
||||||
p.drawImage(
|
|
||||||
geometry.topLeft() + QPoint(0, part + fill),
|
|
||||||
image,
|
|
||||||
QRect(source.x(), source.y() + top, source.width(), half));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Manager::paintAllEmoji(
|
void Manager::paintAllEmoji(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
not_null<Button*> button,
|
not_null<Button*> button,
|
||||||
|
@ -1493,76 +1270,16 @@ void Manager::clearStateForSelectFinished(ReactionIcons &icon) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Manager::setShadowColor(const QColor &shadow) {
|
|
||||||
if (_shadow == shadow) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_shadow = shadow;
|
|
||||||
ranges::fill(_validBg, false);
|
|
||||||
ranges::fill(_validShadow, false);
|
|
||||||
ranges::fill(_validOverlayShadow, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
QRect Manager::cacheRect(int frameIndex, int columnIndex) const {
|
|
||||||
const auto ratio = style::DevicePixelRatio();
|
|
||||||
const auto origin = QPoint(
|
|
||||||
_outer.width() * (kDivider * columnIndex + (frameIndex % kDivider)),
|
|
||||||
_outer.height() * (frameIndex / kDivider));
|
|
||||||
return QRect(ratio * origin, ratio * _outer);
|
|
||||||
}
|
|
||||||
|
|
||||||
QRect Manager::overlayCacheRect(int frameIndex, int columnIndex) const {
|
|
||||||
const auto ratio = style::DevicePixelRatio();
|
|
||||||
const auto size = _overlayFull;
|
|
||||||
const auto origin = QPoint(
|
|
||||||
size.width() * (kDivider * columnIndex + (frameIndex % kDivider)),
|
|
||||||
size.height() * (frameIndex / kDivider));
|
|
||||||
return QRect(ratio * origin, ratio * size);
|
|
||||||
}
|
|
||||||
|
|
||||||
QRect Manager::validateShadow(
|
|
||||||
int frameIndex,
|
|
||||||
float64 scale,
|
|
||||||
const QColor &shadow) {
|
|
||||||
const auto result = cacheRect(frameIndex, kShadowCacheIndex);
|
|
||||||
if (_validShadow[frameIndex]) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
_shadowBuffer.fill(Qt::transparent);
|
|
||||||
auto p = QPainter(&_shadowBuffer);
|
|
||||||
auto hq = PainterHighQualityEnabler(p);
|
|
||||||
const auto center = _inner.center();
|
|
||||||
const auto add = style::ConvertScale(2.5);
|
|
||||||
const auto shift = style::ConvertScale(0.5);
|
|
||||||
const auto big = QRectF(_inner).marginsAdded({ add, add, add, add });
|
|
||||||
const auto radius = big.height() / 2.;
|
|
||||||
p.setPen(Qt::NoPen);
|
|
||||||
p.setBrush(shadow);
|
|
||||||
if (scale != 1.) {
|
|
||||||
p.translate(center);
|
|
||||||
p.scale(scale, scale);
|
|
||||||
p.translate(-center);
|
|
||||||
}
|
|
||||||
p.drawRoundedRect(big.translated(0, shift), radius, radius);
|
|
||||||
p.end();
|
|
||||||
_shadowBuffer = Images::Blur(std::move(_shadowBuffer));
|
|
||||||
|
|
||||||
auto q = QPainter(&_cacheParts);
|
|
||||||
q.setCompositionMode(QPainter::CompositionMode_Source);
|
|
||||||
q.drawImage(result.topLeft() / style::DevicePixelRatio(), _shadowBuffer);
|
|
||||||
|
|
||||||
_validShadow[frameIndex] = true;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
QRect Manager::validateEmoji(int frameIndex, float64 scale) {
|
QRect Manager::validateEmoji(int frameIndex, float64 scale) {
|
||||||
const auto result = cacheRect(frameIndex, kEmojiCacheIndex);
|
const auto result = _cachedRound.FrameCacheRect(
|
||||||
|
frameIndex,
|
||||||
|
kEmojiCacheIndex,
|
||||||
|
_outer);
|
||||||
if (_validEmoji[frameIndex]) {
|
if (_validEmoji[frameIndex]) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto p = QPainter(&_cacheParts);
|
auto p = QPainter(&_emojiParts);
|
||||||
const auto ratio = style::DevicePixelRatio();
|
const auto ratio = style::DevicePixelRatio();
|
||||||
const auto position = result.topLeft() / ratio;
|
const auto position = result.topLeft() / ratio;
|
||||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
p.setCompositionMode(QPainter::CompositionMode_Source);
|
||||||
|
@ -1590,54 +1307,6 @@ QRect Manager::validateEmoji(int frameIndex, float64 scale) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Manager::setBackgroundColor(const QColor &background) {
|
|
||||||
if (_background == background) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_background = background;
|
|
||||||
_topGradient = QImage();
|
|
||||||
_bottomGradient = QImage();
|
|
||||||
ranges::fill(_validBg, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
QRect Manager::validateFrame(
|
|
||||||
int frameIndex,
|
|
||||||
float64 scale,
|
|
||||||
const QColor &background,
|
|
||||||
const QColor &shadow) {
|
|
||||||
const auto result = cacheRect(frameIndex, kBgCacheIndex);
|
|
||||||
if (_validBg[frameIndex]) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto shadowSource = validateShadow(frameIndex, scale, shadow);
|
|
||||||
const auto position = result.topLeft() / style::DevicePixelRatio();
|
|
||||||
auto p = QPainter(&_cacheBg);
|
|
||||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
|
||||||
p.drawImage(position, _cacheParts, shadowSource);
|
|
||||||
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
|
||||||
|
|
||||||
auto hq = PainterHighQualityEnabler(p);
|
|
||||||
const auto inner = _inner.translated(position);
|
|
||||||
const auto radius = inner.height() / 2.;
|
|
||||||
p.setPen(Qt::NoPen);
|
|
||||||
p.setBrush(background);
|
|
||||||
if (scale != 1.) {
|
|
||||||
const auto center = inner.center();
|
|
||||||
p.save();
|
|
||||||
p.translate(center);
|
|
||||||
p.scale(scale, scale);
|
|
||||||
p.translate(-center);
|
|
||||||
}
|
|
||||||
p.drawRoundedRect(inner, radius, radius);
|
|
||||||
if (scale != 1.) {
|
|
||||||
p.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
_validBg[frameIndex] = true;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<QRect> Manager::lookupEffectArea(FullMsgId itemId) const {
|
std::optional<QRect> Manager::lookupEffectArea(FullMsgId itemId) const {
|
||||||
const auto i = _activeEffectAreas.find(itemId);
|
const auto i = _activeEffectAreas.find(itemId);
|
||||||
return (i != end(_activeEffectAreas))
|
return (i != end(_activeEffectAreas))
|
||||||
|
|
|
@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ui/effects/animations.h"
|
#include "ui/effects/animations.h"
|
||||||
|
#include "ui/effects/round_area_with_shadow.h"
|
||||||
#include "ui/widgets/scroll_area.h"
|
#include "ui/widgets/scroll_area.h"
|
||||||
#include "data/data_message_reaction_id.h"
|
#include "data/data_message_reaction_id.h"
|
||||||
#include "ui/chat/chat_style.h"
|
#include "ui/chat/chat_style.h"
|
||||||
|
@ -228,11 +229,7 @@ private:
|
||||||
mutable bool selected = false;
|
mutable bool selected = false;
|
||||||
mutable bool selectAnimated = false;
|
mutable bool selectAnimated = false;
|
||||||
};
|
};
|
||||||
struct OverlayImage {
|
static constexpr auto kFramesCount = Ui::RoundAreaWithShadow::kFramesCount;
|
||||||
not_null<QImage*> cache;
|
|
||||||
QRect source;
|
|
||||||
};
|
|
||||||
static constexpr auto kFramesCount = 32;
|
|
||||||
|
|
||||||
void applyListFilters();
|
void applyListFilters();
|
||||||
void showButtonDelayed();
|
void showButtonDelayed();
|
||||||
|
@ -268,49 +265,12 @@ private:
|
||||||
not_null<Button*> button,
|
not_null<Button*> button,
|
||||||
int scroll,
|
int scroll,
|
||||||
float64 expandRatio);
|
float64 expandRatio);
|
||||||
void overlayExpandedBorder(
|
|
||||||
Painter &p,
|
|
||||||
QSize size,
|
|
||||||
float64 expandRatio,
|
|
||||||
float64 scale,
|
|
||||||
const QColor &shadow);
|
|
||||||
void paintLongImage(
|
|
||||||
QPainter &p,
|
|
||||||
QRect geometry,
|
|
||||||
const QImage &image,
|
|
||||||
QRect source);
|
|
||||||
|
|
||||||
void resolveMainReactionIcon();
|
void resolveMainReactionIcon();
|
||||||
void setMainReactionIcon();
|
void setMainReactionIcon();
|
||||||
void clearAppearAnimations();
|
void clearAppearAnimations();
|
||||||
[[nodiscard]] QRect cacheRect(int frameIndex, int columnIndex) const;
|
[[nodiscard]] QRect cacheRect(int frameIndex, int columnIndex) const;
|
||||||
[[nodiscard]] QRect overlayCacheRect(
|
|
||||||
int frameIndex,
|
|
||||||
int columnIndex) const;
|
|
||||||
QRect validateShadow(
|
|
||||||
int frameIndex,
|
|
||||||
float64 scale,
|
|
||||||
const QColor &shadow);
|
|
||||||
QRect validateEmoji(int frameIndex, float64 scale);
|
QRect validateEmoji(int frameIndex, float64 scale);
|
||||||
QRect validateFrame(
|
|
||||||
int frameIndex,
|
|
||||||
float64 scale,
|
|
||||||
const QColor &background,
|
|
||||||
const QColor &shadow);
|
|
||||||
OverlayImage validateOverlayMask(
|
|
||||||
int frameIndex,
|
|
||||||
QSize innerSize,
|
|
||||||
float64 radius,
|
|
||||||
float64 scale);
|
|
||||||
OverlayImage validateOverlayShadow(
|
|
||||||
int frameIndex,
|
|
||||||
QSize innerSize,
|
|
||||||
float64 radius,
|
|
||||||
float64 scale,
|
|
||||||
const QColor &shadow,
|
|
||||||
const OverlayImage &mask);
|
|
||||||
void setBackgroundColor(const QColor &background);
|
|
||||||
void setShadowColor(const QColor &shadow);
|
|
||||||
|
|
||||||
void setSelectedIcon(int index) const;
|
void setSelectedIcon(int index) const;
|
||||||
void clearStateForHidden(ReactionIcons &icon);
|
void clearStateForHidden(ReactionIcons &icon);
|
||||||
|
@ -338,24 +298,14 @@ private:
|
||||||
Data::ReactionsFilter _filter;
|
Data::ReactionsFilter _filter;
|
||||||
QSize _outer;
|
QSize _outer;
|
||||||
QRect _inner;
|
QRect _inner;
|
||||||
QSize _overlayFull;
|
Ui::RoundAreaWithShadow _cachedRound;
|
||||||
QImage _cacheBg;
|
QImage _emojiParts;
|
||||||
QImage _cacheParts;
|
|
||||||
QImage _overlayCacheParts;
|
|
||||||
QImage _overlayMaskScaled;
|
|
||||||
QImage _overlayShadowScaled;
|
|
||||||
QImage _shadowBuffer;
|
|
||||||
QImage _expandedBuffer;
|
QImage _expandedBuffer;
|
||||||
|
QColor _gradientBackground;
|
||||||
QImage _topGradient;
|
QImage _topGradient;
|
||||||
QImage _bottomGradient;
|
QImage _bottomGradient;
|
||||||
std::array<bool, kFramesCount> _validBg = { { false } };
|
|
||||||
std::array<bool, kFramesCount> _validShadow = { { false } };
|
|
||||||
std::array<bool, kFramesCount> _validEmoji = { { false } };
|
std::array<bool, kFramesCount> _validEmoji = { { false } };
|
||||||
std::array<bool, kFramesCount> _validOverlayMask = { { false } };
|
|
||||||
std::array<bool, kFramesCount> _validOverlayShadow = { { false } };
|
|
||||||
QColor _background;
|
|
||||||
QColor _gradient;
|
QColor _gradient;
|
||||||
QColor _shadow;
|
|
||||||
|
|
||||||
std::shared_ptr<Data::DocumentMedia> _mainReactionMedia;
|
std::shared_ptr<Data::DocumentMedia> _mainReactionMedia;
|
||||||
std::shared_ptr<Lottie::Icon> _mainReactionIcon;
|
std::shared_ptr<Lottie::Icon> _mainReactionIcon;
|
||||||
|
|
|
@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "styles/style_chat_helpers.h"
|
#include "styles/style_chat_helpers.h"
|
||||||
|
#include "styles/style_chat.h"
|
||||||
|
|
||||||
namespace HistoryView::Reactions {
|
namespace HistoryView::Reactions {
|
||||||
|
|
||||||
|
@ -114,15 +115,34 @@ void Selector::hide(anim::type animated) {
|
||||||
PopupSelector::PopupSelector(
|
PopupSelector::PopupSelector(
|
||||||
not_null<QWidget*> parent,
|
not_null<QWidget*> parent,
|
||||||
PossibleReactions reactions)
|
PossibleReactions reactions)
|
||||||
: RpWidget(parent) {
|
: RpWidget(parent)
|
||||||
|
, _reactions(std::move(reactions))
|
||||||
|
, _cachedRound(
|
||||||
|
QSize(st::reactStripSkip * 2 + st::reactStripSize, st::reactStripHeight),
|
||||||
|
st::reactionCornerShadow,
|
||||||
|
st::reactStripHeight)
|
||||||
|
, _size(st::reactStripSize)
|
||||||
|
, _skipx(st::reactStripSkip)
|
||||||
|
, _skipy((st::reactStripHeight - st::reactStripSize) / 2)
|
||||||
|
, _skipBottom(st::reactStripHeight - st::reactStripSize - _skipy) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int PopupSelector::countWidth(int desiredWidth, int maxWidth) {
|
int PopupSelector::countWidth(int desiredWidth, int maxWidth) {
|
||||||
return maxWidth;
|
const auto added = _reactions.customAllowed
|
||||||
|
|| _reactions.morePremiumAvailable;
|
||||||
|
const auto possibleColumns = std::min(
|
||||||
|
(desiredWidth - 2 * _skipx + _size - 1) / _size,
|
||||||
|
(maxWidth - 2 * _skipx) / _size);
|
||||||
|
_columns = std::min(
|
||||||
|
possibleColumns,
|
||||||
|
int(_reactions.recent.size()) + (added ? 1 : 0));
|
||||||
|
_small = (possibleColumns - _columns > 1);
|
||||||
|
_recentRows = (_reactions.recent.size() + _columns - 1) / _columns;
|
||||||
|
return 2 * _skipx + _columns * _size;
|
||||||
}
|
}
|
||||||
|
|
||||||
QMargins PopupSelector::extentsForShadow() const {
|
QMargins PopupSelector::extentsForShadow() const {
|
||||||
return st::defaultPopupMenu.shadow.extend;
|
return st::reactionCornerShadow;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PopupSelector::extendTopForCategories() const {
|
int PopupSelector::extendTopForCategories() const {
|
||||||
|
@ -130,11 +150,80 @@ int PopupSelector::extendTopForCategories() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
int PopupSelector::desiredHeight() const {
|
int PopupSelector::desiredHeight() const {
|
||||||
return st::emojiPanMaxHeight;
|
return _reactions.customAllowed
|
||||||
|
? st::emojiPanMaxHeight
|
||||||
|
: (_skipy + _recentRows * _size + _skipBottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PopupSelector::initGeometry(int innerTop) {
|
||||||
|
const auto extents = extentsForShadow();
|
||||||
|
const auto parent = parentWidget()->rect();
|
||||||
|
const auto innerWidth = 2 * _skipx + _columns * _size;
|
||||||
|
const auto innerHeight = st::reactStripHeight;
|
||||||
|
const auto width = innerWidth + extents.left() + extents.right();
|
||||||
|
const auto height = innerHeight + extents.top() + extents.bottom();
|
||||||
|
const auto left = style::RightToLeft() ? 0 : (parent.width() - width);
|
||||||
|
const auto top = innerTop - extents.top();
|
||||||
|
setGeometry(left, top, width, height);
|
||||||
|
_inner = rect().marginsRemoved(extents);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PopupSelector::updateShowState(
|
||||||
|
float64 progress,
|
||||||
|
float64 opacity,
|
||||||
|
bool appearing,
|
||||||
|
bool toggling) {
|
||||||
|
_appearing = appearing;
|
||||||
|
_toggling = toggling;
|
||||||
|
_appearProgress = progress;
|
||||||
|
_appearOpacity = opacity;
|
||||||
|
if (_appearing && isHidden()) {
|
||||||
|
show();
|
||||||
|
} else if (_toggling && !isHidden()) {
|
||||||
|
hide();
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PopupSelector::paintAppearing(QPainter &p) {
|
||||||
|
p.setOpacity(_appearOpacity);
|
||||||
|
|
||||||
|
const auto factor = style::DevicePixelRatio();
|
||||||
|
if (_appearBuffer.size() != size() * factor) {
|
||||||
|
_appearBuffer = _cachedRound.PrepareImage(size());
|
||||||
|
}
|
||||||
|
_appearBuffer.fill(st::defaultPopupMenu.menu.itemBg->c);
|
||||||
|
auto q = QPainter(&_appearBuffer);
|
||||||
|
const auto extents = extentsForShadow();
|
||||||
|
const auto appearedWidth = anim::interpolate(
|
||||||
|
_skipx * 2 + _size,
|
||||||
|
_inner.width(),
|
||||||
|
_appearProgress);
|
||||||
|
const auto fullWidth = appearedWidth + extents.left() + extents.right();
|
||||||
|
const auto size = QSize(fullWidth, height());
|
||||||
|
_cachedRound.setBackgroundColor(st::defaultPopupMenu.menu.itemBg->c);
|
||||||
|
_cachedRound.setShadowColor(st::shadowFg->c);
|
||||||
|
const auto radius = st::reactStripHeight / 2;
|
||||||
|
_cachedRound.overlayExpandedBorder(q, size, _appearProgress, radius, 1.);
|
||||||
|
q.end();
|
||||||
|
|
||||||
|
p.drawImage(
|
||||||
|
QPoint(),
|
||||||
|
_appearBuffer,
|
||||||
|
QRect(QPoint(), size * style::DevicePixelRatio()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PopupSelector::paintBg(QPainter &p) {
|
||||||
|
_cachedRound.FillWithImage(p, rect(), _cachedRound.validateFrame(0, 1.));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PopupSelector::paintEvent(QPaintEvent *e) {
|
void PopupSelector::paintEvent(QPaintEvent *e) {
|
||||||
QPainter(this).fillRect(e->rect(), QColor(0, 128, 0, 128));
|
auto p = QPainter(this);
|
||||||
|
if (_appearing) {
|
||||||
|
paintAppearing(p);
|
||||||
|
} else {
|
||||||
|
paintBg(p);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] PossibleReactions LookupPossibleReactions(
|
[[nodiscard]] PossibleReactions LookupPossibleReactions(
|
||||||
|
@ -196,7 +285,7 @@ void PopupSelector::paintEvent(QPaintEvent *e) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SetupSelectorInMenuGeometry(
|
bool AdjustMenuGeometryForSelector(
|
||||||
not_null<Ui::PopupMenu*> menu,
|
not_null<Ui::PopupMenu*> menu,
|
||||||
QPoint desiredPosition,
|
QPoint desiredPosition,
|
||||||
not_null<PopupSelector*> selector) {
|
not_null<PopupSelector*> selector) {
|
||||||
|
@ -267,18 +356,34 @@ AttachSelectorResult AttachSelectorToMenu(
|
||||||
const auto selector = Ui::CreateChild<PopupSelector>(
|
const auto selector = Ui::CreateChild<PopupSelector>(
|
||||||
menu.get(),
|
menu.get(),
|
||||||
std::move(reactions));
|
std::move(reactions));
|
||||||
if (!SetupSelectorInMenuGeometry(menu, desiredPosition, selector)) {
|
if (!AdjustMenuGeometryForSelector(menu, desiredPosition, selector)) {
|
||||||
return AttachSelectorResult::Failed;
|
return AttachSelectorResult::Failed;
|
||||||
}
|
}
|
||||||
const auto extents = selector->extentsForShadow();
|
const auto selectorInnerTop = menu->preparedPadding().top()
|
||||||
const auto categoriesTop = selector->extendTopForCategories();
|
- st::reactStripExtend.top();
|
||||||
selector->setGeometry(
|
selector->initGeometry(selectorInnerTop);
|
||||||
extents.left(),
|
|
||||||
menu->preparedPadding().top() - st::reactStripExtend.top(),
|
|
||||||
menu->width() - extents.left() - extents.right(),
|
|
||||||
st::reactStripHeight);
|
|
||||||
selector->lower();
|
|
||||||
selector->show();
|
selector->show();
|
||||||
|
|
||||||
|
const auto correctTop = selector->y();
|
||||||
|
menu->showStateValue(
|
||||||
|
) | rpl::start_with_next([=](Ui::PopupMenu::ShowState state) {
|
||||||
|
const auto origin = menu->preparedOrigin();
|
||||||
|
using Origin = Ui::PanelAnimation::Origin;
|
||||||
|
if (origin == Origin::BottomLeft || origin == Origin::BottomRight) {
|
||||||
|
const auto add = state.appearing
|
||||||
|
? (menu->rect().marginsRemoved(
|
||||||
|
menu->preparedPadding()
|
||||||
|
).height() - state.appearingHeight)
|
||||||
|
: 0;
|
||||||
|
selector->move(selector->x(), correctTop + add);
|
||||||
|
}
|
||||||
|
selector->updateShowState(
|
||||||
|
std::min(state.widthProgress, state.heightProgress),
|
||||||
|
state.opacity,
|
||||||
|
state.appearing,
|
||||||
|
state.toggling);
|
||||||
|
}, selector->lifetime());
|
||||||
|
|
||||||
return AttachSelectorResult::Attached;
|
return AttachSelectorResult::Attached;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "base/unique_qptr.h"
|
#include "base/unique_qptr.h"
|
||||||
#include "ui/effects/animation_value.h"
|
#include "ui/effects/animation_value.h"
|
||||||
|
#include "ui/effects/round_area_with_shadow.h"
|
||||||
#include "ui/rp_widget.h"
|
#include "ui/rp_widget.h"
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
|
@ -69,12 +70,38 @@ public:
|
||||||
[[nodiscard]] QMargins extentsForShadow() const;
|
[[nodiscard]] QMargins extentsForShadow() const;
|
||||||
[[nodiscard]] int extendTopForCategories() const;
|
[[nodiscard]] int extendTopForCategories() const;
|
||||||
[[nodiscard]] int desiredHeight() const;
|
[[nodiscard]] int desiredHeight() const;
|
||||||
|
void initGeometry(int innerTop);
|
||||||
|
|
||||||
protected:
|
void updateShowState(
|
||||||
void paintEvent(QPaintEvent *e);
|
float64 progress,
|
||||||
|
float64 opacity,
|
||||||
|
bool appearing,
|
||||||
|
bool toggling);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static constexpr int kFramesCount = 32;
|
||||||
|
|
||||||
|
void paintEvent(QPaintEvent *e);
|
||||||
|
|
||||||
|
void paintAppearing(QPainter &p);
|
||||||
|
void paintBg(QPainter &p);
|
||||||
|
|
||||||
|
PossibleReactions _reactions;
|
||||||
|
QImage _appearBuffer;
|
||||||
|
Ui::RoundAreaWithShadow _cachedRound;
|
||||||
|
float64 _appearProgress = 0.;
|
||||||
|
float64 _appearOpacity = 0.;
|
||||||
|
QRect _inner;
|
||||||
|
QMargins _padding;
|
||||||
|
int _size = 0;
|
||||||
|
int _recentRows = 0;
|
||||||
int _columns = 0;
|
int _columns = 0;
|
||||||
|
int _skipx = 0;
|
||||||
|
int _skipy = 0;
|
||||||
|
int _skipBottom = 0;
|
||||||
|
bool _appearing = false;
|
||||||
|
bool _toggling = false;
|
||||||
|
bool _small = false;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -408,8 +408,7 @@ TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const {
|
||||||
auto pixTop = (minHeight() - _contentSize.height()) / 2;
|
auto pixTop = (minHeight() - _contentSize.height()) / 2;
|
||||||
// Link of content can be nullptr (e.g. sticker without stickerpack).
|
// Link of content can be nullptr (e.g. sticker without stickerpack).
|
||||||
// So we have to process it to avoid overriding the previous result.
|
// So we have to process it to avoid overriding the previous result.
|
||||||
if (_content->link()
|
if (_content->link() && inner.contains(point)) {
|
||||||
&& QRect({ pixLeft, pixTop }, _contentSize).contains(point)) {
|
|
||||||
result.link = _content->link();
|
result.link = _content->link();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit a76cdf7edffb6e9cbef515a0e599c3c14a8a5b53
|
Subproject commit eed9293c80eefd7b6d5398b4a65bf7822a1364fc
|
Loading…
Add table
Reference in a new issue