Nice selection of gifts for a collection.

This commit is contained in:
John Preston
2025-08-21 16:14:03 +04:00
parent 9e190cee81
commit 29d77b649b
11 changed files with 201 additions and 84 deletions
+2
View File
@@ -1315,6 +1315,8 @@ PRIVATE
mtproto/special_config_request.cpp
mtproto/special_config_request.h
mtproto/type_utils.h
overview/overview_checkbox.cpp
overview/overview_checkbox.h
overview/overview_layout.cpp
overview/overview_layout.h
overview/overview_layout_delegate.h
@@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "info/channel_statistics/earn/earn_icons.h"
#include "main/main_session.h"
#include "overview/overview_checkbox.h"
#include "settings/settings_credits_graphics.h"
#include "ui/layers/generic_box.h"
#include "ui/text/format_values.h"
@@ -34,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_session_controller.h"
#include "styles/style_credits.h"
#include "styles/style_layers.h"
#include "styles/style_overview.h"
#include "styles/style_premium.h"
namespace Info::PeerGifts {
@@ -78,6 +80,18 @@ GiftButton::~GiftButton() {
unsubscribe();
}
void GiftButton::onStateChanged(State was, StateChangeSource source) {
if (_check) {
const auto diff = state() ^ was;
if (diff & State::Enum::Over) {
_check->setActive(state() & State::Enum::Over);
}
if (diff & State::Enum::Down) {
_check->setPressed(state() & State::Enum::Down);
}
}
}
void GiftButton::unsubscribe() {
if (base::take(_subscribed)) {
_userpic->subscribeToUpdates(nullptr);
@@ -85,6 +99,16 @@ void GiftButton::unsubscribe() {
}
void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) {
_mode = mode;
if (_mode != GiftButtonMode::Selection) {
_check = nullptr;
} else if (!_check) {
_check = std::make_unique<Overview::Layout::Checkbox>(
[=] { update(); },
st::overviewSmallCheck);
}
const auto unique = v::is<GiftTypeStars>(descriptor)
? v::get<GiftTypeStars>(descriptor).info.unique.get()
: nullptr;
@@ -106,7 +130,6 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) {
_descriptor = descriptor;
_resalePrice = resalePrice;
const auto resale = (_resalePrice > 0);
_small = (mode != Mode::Full);
v::match(descriptor, [&](const GiftTypePremium &data) {
const auto months = data.months;
_text = Ui::Text::String(st::giftBoxGiftHeight / 4);
@@ -147,12 +170,12 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) {
const auto soldOut = data.info.limitedCount
&& !data.userpic
&& !data.info.limitedLeft;
_userpic = !data.userpic
_userpic = (!data.userpic || _mode == GiftButtonMode::Selection)
? nullptr
: data.from
? Ui::MakeUserpicThumbnail(data.from)
: Ui::MakeHiddenAuthorThumbnail();
if (_small && !resale) {
if (small() && !resale) {
_price = {};
_stars.reset();
return;
@@ -169,7 +192,7 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) {
? unique->starsForResale
: data.info.starsResellMin)
).append(data.info.resellCount > 1 ? "+" : ""))
: (_small && unique && unique->starsForResale)
: (small() && unique && unique->starsForResale)
? Data::FormatGiftResaleAsked(*unique)
: unique
? tr::lng_gift_transfer_button(
@@ -214,7 +237,7 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) {
_uniquePatternEmoji = nullptr;
_uniquePatternCache.clear();
if (_small && !resale) {
if (small() && !resale) {
_button = QRect();
return;
}
@@ -225,7 +248,7 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) {
QSize(buttonw, buttonh)
).marginsAdded(st::giftBoxButtonPadding);
const auto skipy = _delegate->buttonSize().height()
- (_small
- (small()
? st::giftBoxButtonBottomSmall
: _byStars.isEmpty()
? st::giftBoxButtonBottom
@@ -295,11 +318,17 @@ void GiftButton::setGeometry(QRect inner, QMargins extend) {
}
QMargins GiftButton::currentExtend() const {
const auto progress = _selectedAnimation.value(_selected ? 1. : 0.);
const auto progress = (_mode == Mode::Selection)
? 0.
: _selectedAnimation.value(_selected ? 1. : 0.);
const auto added = anim::interpolate(0, st::giftBoxSelectSkip, progress);
return _extend + QMargins(added, added, added, added);
}
bool GiftButton::small() const {
return _mode != GiftButtonMode::Full;
}
void GiftButton::toggleSelected(bool selected, anim::type animated) {
if (_selected == selected) {
if (animated == anim::type::instant) {
@@ -310,8 +339,14 @@ void GiftButton::toggleSelected(bool selected, anim::type animated) {
const auto duration = st::defaultRoundCheckbox.duration;
_selected = selected;
if (animated == anim::type::instant) {
if (_check) {
_check->finishAnimating();
}
_selectedAnimation.stop();
return;
} else if (_check) {
_check->setChecked(selected, animated);
return;
}
_selectedAnimation.start([=] {
update();
@@ -358,7 +393,9 @@ void GiftButton::paintBackground(QPainter &p, const QImage &background) {
}
auto hq = PainterHighQualityEnabler(p);
const auto progress = _selectedAnimation.value(_selected ? 1. : 0.);
const auto progress = (_mode == Mode::Selection)
? 0.
: _selectedAnimation.value(_selected ? 1. : 0.);
if (progress < 0.01) {
return;
}
@@ -446,7 +483,7 @@ void GiftButton::paintEvent(QPaintEvent *e) {
const auto unique = v::is<GiftTypeStars>(_descriptor)
? v::get<GiftTypeStars>(_descriptor).info.unique.get()
: nullptr;
const auto onsale = (unique && unique->starsForResale && _small);
const auto onsale = unique && unique->starsForResale && small();
const auto requirePremium = v::is<GiftTypeStars>(_descriptor)
&& !v::get<GiftTypeStars>(_descriptor).userpic
&& !v::get<GiftTypeStars>(_descriptor).info.unique
@@ -482,6 +519,14 @@ void GiftButton::paintEvent(QPaintEvent *e) {
const auto image = _userpic->image(st::giftBoxUserpicSize);
const auto skip = st::giftBoxUserpicSkip;
p.drawImage(extend.left() + skip, extend.top() + skip, image);
} else if (_check) {
const auto skip = st::giftBoxUserpicSkip;
_check->paint(
p,
QPoint(extend.left() + skip, extend.top() + skip),
width,
_selected,
true);
}
auto frame = QImage();
@@ -502,7 +547,7 @@ void GiftButton::paintEvent(QPaintEvent *e) {
p.drawImage(
QRect(
(width - size.width()) / 2,
(_small
(small()
? st::giftBoxSmallStickerTop
: _text.isEmpty()
? st::giftBoxStickerStarTop
@@ -516,7 +561,7 @@ void GiftButton::paintEvent(QPaintEvent *e) {
if (hidden) {
const auto topleft = QPoint(
(width - st::giftBoxStickerSize.width()) / 2,
(_small
(small()
? st::giftBoxSmallStickerTop
: _text.isEmpty()
? st::giftBoxStickerStarTop
@@ -781,7 +826,7 @@ QSize Delegate::buttonSize() {
const auto available = width - padding.left() - padding.right();
const auto singlew = (available - 2 * st::giftBoxGiftSkip.x())
/ kGiftsPerRow;
const auto minimal = (_mode == GiftButtonMode::Minimal);
const auto minimal = (_mode != GiftButtonMode::Full);
_single = QSize(
singlew,
minimal ? st::giftBoxGiftSmall : st::giftBoxGiftHeight);
@@ -34,6 +34,10 @@ namespace Main {
class Session;
} // namespace Main
namespace Overview::Layout {
class Checkbox;
} // namespace Overview::Layout
namespace Ui {
class DynamicImage;
} // namespace Ui
@@ -118,9 +122,10 @@ struct GiftBadge {
const GiftBadge &) = default;
};
enum class GiftButtonMode {
enum class GiftButtonMode : uint8 {
Full,
Minimal,
Selection,
};
class GiftButtonDelegate {
@@ -173,7 +178,9 @@ private:
void setDocument(not_null<DocumentData*> document);
[[nodiscard]] QMargins currentExtend() const;
[[nodiscard]] bool small() const;
void onStateChanged(State was, StateChangeSource source) override;
void unsubscribe();
const not_null<GiftButtonDelegate*> _delegate;
@@ -190,11 +197,12 @@ private:
base::flat_map<float64, QImage> _uniquePatternCache;
std::optional<Ui::Premium::ColoredMiniStars> _stars;
Ui::Animations::Simple _selectedAnimation;
std::unique_ptr<Overview::Layout::Checkbox> _check;
int _resalePrice = 0;
GiftButtonMode _mode = GiftButtonMode::Full;
bool _subscribed = false;
bool _patterned = false;
bool _selected = false;
bool _small = false;
QRect _button;
QMargins _extend;
@@ -201,6 +201,7 @@ private:
const not_null<Window::SessionController*> _window;
const not_null<PeerData*> _peer;
const int _addingToCollectionId = 0;
const GiftButtonMode _mode;
rpl::variable<Descriptor> _descriptor;
Delegate _delegate;
@@ -267,8 +268,11 @@ InnerWidget::InnerWidget(
, _window(window)
, _peer(peer)
, _addingToCollectionId(addingToCollectionId)
, _mode(_addingToCollectionId
? GiftButtonMode::Selection
: GiftButtonMode::Minimal)
, _descriptor(std::move(descriptor))
, _delegate(&_window->session(), GiftButtonMode::Minimal)
, _delegate(&_window->session(), _mode)
, _all(std::move(all))
, _entries(&_all)
, _list(&_entries->list)
@@ -750,7 +754,7 @@ void InnerWidget::validateButtons() {
view.button->toggleSelected(
_addingToCollectionId && _inCollection.contains(manageId),
anim::type::instant);
view.button->setDescriptor(descriptor, GiftButton::Mode::Minimal);
view.button->setDescriptor(descriptor, _mode);
view.button->setClickedCallback(callback);
return true;
};
@@ -0,0 +1,60 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "overview/overview_checkbox.h"
#include "styles/style_overview.h"
#include "styles/style_widgets.h"
namespace Overview::Layout {
void Checkbox::paint(
QPainter &p,
QPoint position,
int outerWidth,
bool selected,
bool selecting) {
_check.setDisplayInactive(selecting);
_check.setChecked(selected);
const auto pression = _pression.value((_active && _pressed) ? 1. : 0.);
const auto scale = 1. - (1. - st::overviewCheckPressedSize) * pression;
_check.paint(p, position.x(), position.y(), outerWidth, scale);
}
void Checkbox::setActive(bool active) {
_active = active;
if (_pressed) {
startAnimation();
}
}
void Checkbox::setPressed(bool pressed) {
_pressed = pressed;
if (_active) {
startAnimation();
}
}
void Checkbox::setChecked(bool checked, anim::type animated) {
_check.setChecked(checked, animated);
}
void Checkbox::startAnimation() {
const auto showPressed = (_pressed && _active);
_pression.start(
_updateCallback,
showPressed ? 0. : 1.,
showPressed ? 1. : 0.,
st::overviewCheck.duration);
}
void Checkbox::finishAnimating() {
_pression.stop();
_check.finishAnimating();
}
} // namespace Overview::Layout
@@ -0,0 +1,54 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "ui/effects/round_checkbox.h"
namespace style {
struct RoundCheckbox;
} // namespace style
namespace Overview::Layout {
class Checkbox {
public:
template <typename UpdateCallback>
Checkbox(UpdateCallback callback, const style::RoundCheckbox &st)
: _updateCallback(callback)
, _check(st, _updateCallback) {
}
void paint(
QPainter &p,
QPoint position,
int outerWidth,
bool selected,
bool selecting);
void setActive(bool active);
void setPressed(bool pressed);
void setChecked(bool checked, anim::type animated = anim::type::normal);
void finishAnimating();
void invalidateCache() {
_check.invalidateCache();
}
private:
void startAnimation();
Fn<void()> _updateCallback;
Ui::RoundCheckbox _check;
Ui::Animations::Simple _pression;
bool _active = false;
bool _pressed = false;
};
} // namespace Overview::Layout
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "overview/overview_layout.h"
#include "overview/overview_checkbox.h"
#include "overview/overview_layout_delegate.h"
#include "core/ui_integration.h" // TextContext
#include "data/data_document.h"
@@ -49,8 +50,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_chat_helpers.h"
#include "styles/style_overview.h"
namespace Overview {
namespace Layout {
namespace Overview::Layout {
namespace {
using TextState = HistoryView::TextState;
@@ -123,62 +123,6 @@ void PaintSensitiveTag(Painter &p, QRect r) {
} // namespace
class Checkbox {
public:
template <typename UpdateCallback>
Checkbox(UpdateCallback callback, const style::RoundCheckbox &st)
: _updateCallback(callback)
, _check(st, _updateCallback) {
}
void paint(Painter &p, QPoint position, int outerWidth, bool selected, bool selecting);
void setActive(bool active);
void setPressed(bool pressed);
void invalidateCache() {
_check.invalidateCache();
}
private:
void startAnimation();
Fn<void()> _updateCallback;
Ui::RoundCheckbox _check;
Ui::Animations::Simple _pression;
bool _active = false;
bool _pressed = false;
};
void Checkbox::paint(Painter &p, QPoint position, int outerWidth, bool selected, bool selecting) {
_check.setDisplayInactive(selecting);
_check.setChecked(selected);
const auto pression = _pression.value((_active && _pressed) ? 1. : 0.);
const auto masterScale = 1. - (1. - st::overviewCheckPressedSize) * pression;
_check.paint(p, position.x(), position.y(), outerWidth, masterScale);
}
void Checkbox::setActive(bool active) {
_active = active;
if (_pressed) {
startAnimation();
}
}
void Checkbox::setPressed(bool pressed) {
_pressed = pressed;
if (_active) {
startAnimation();
}
}
void Checkbox::startAnimation() {
auto showPressed = (_pressed && _active);
_pression.start(_updateCallback, showPressed ? 0. : 1., showPressed ? 1. : 0., st::overviewCheck.duration);
}
ItemBase::ItemBase(
not_null<Delegate*> delegate,
not_null<HistoryItem*> parent)
@@ -2501,5 +2445,4 @@ void Gif::updateStatusText() {
}
}
} // namespace Layout
} // namespace Overview
} // namespace Overview::Layout
@@ -31,8 +31,7 @@ namespace Ui {
class SpoilerAnimation;
} // namespace Ui
namespace Overview {
namespace Layout {
namespace Overview::Layout {
class Checkbox;
class ItemBase;
@@ -512,5 +511,4 @@ private:
};
} // namespace Layout
} // namespace Overview
} // namespace Overview::Layout
@@ -9,8 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class StickerPremiumMark;
namespace Overview {
namespace Layout {
namespace Overview::Layout {
class ItemBase;
@@ -31,5 +30,4 @@ public:
};
} // namespace Layout
} // namespace Overview
} // namespace Overview::Layout
@@ -308,6 +308,10 @@ void RoundCheckbox::setChecked(bool newChecked, anim::type animated) {
}
}
void RoundCheckbox::finishAnimating() {
_checkedProgress.stop();
}
void RoundCheckbox::invalidateCache() {
if (!_inactiveCacheBg.isNull() || !_inactiveCacheFg.isNull()) {
prepareInactiveCache();
@@ -34,6 +34,7 @@ public:
void setChecked(
bool newChecked,
anim::type animated = anim::type::normal);
void finishAnimating();
void invalidateCache();