Initial suggested reaction implementation.

This commit is contained in:
John Preston 2023-08-29 21:34:21 +04:00
parent d5b429e910
commit f3e65181cd
9 changed files with 479 additions and 130 deletions

View file

@ -106,6 +106,99 @@ std::unique_ptr<Ui::PathShiftGradient> MakePathShiftGradient(
st->paletteChanged()); st->paletteChanged());
} }
bool DefaultElementDelegate::elementUnderCursor(
not_null<const Element*> view) {
return false;
}
float64 DefaultElementDelegate::elementHighlightOpacity(
not_null<const HistoryItem*> item) const {
return 0.;
}
bool DefaultElementDelegate::elementInSelectionMode() {
return false;
}
bool DefaultElementDelegate::elementIntersectsRange(
not_null<const Element*> view,
int from,
int till) {
return true;
}
void DefaultElementDelegate::elementStartStickerLoop(
not_null<const Element*> view) {
}
void DefaultElementDelegate::elementShowPollResults(
not_null<PollData*> poll,
FullMsgId context) {
}
void DefaultElementDelegate::elementOpenPhoto(
not_null<PhotoData*> photo,
FullMsgId context) {
}
void DefaultElementDelegate::elementOpenDocument(
not_null<DocumentData*> document,
FullMsgId context,
bool showInMediaView) {
}
void DefaultElementDelegate::elementCancelUpload(const FullMsgId &context) {
}
void DefaultElementDelegate::elementShowTooltip(
const TextWithEntities &text,
Fn<void()> hiddenCallback) {
}
bool DefaultElementDelegate::elementHideReply(
not_null<const Element*> view) {
return false;
}
bool DefaultElementDelegate::elementShownUnread(
not_null<const Element*> view) {
return view->data()->unread(view->data()->history());
}
void DefaultElementDelegate::elementSendBotCommand(
const QString &command,
const FullMsgId &context) {
}
void DefaultElementDelegate::elementHandleViaClick(
not_null<UserData*> bot) {
}
bool DefaultElementDelegate::elementIsChatWide() {
return false;
}
void DefaultElementDelegate::elementReplyTo(const FullMsgId &to) {
}
void DefaultElementDelegate::elementStartInteraction(
not_null<const Element*> view) {
}
void DefaultElementDelegate::elementStartPremium(
not_null<const Element*> view,
Element *replacing) {
}
void DefaultElementDelegate::elementCancelPremium(
not_null<const Element*> view) {
}
QString DefaultElementDelegate::elementAuthorRank(
not_null<const Element*> view) {
return {};
}
SimpleElementDelegate::SimpleElementDelegate( SimpleElementDelegate::SimpleElementDelegate(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
Fn<void()> update) Fn<void()> update)
@ -118,106 +211,15 @@ SimpleElementDelegate::SimpleElementDelegate(
SimpleElementDelegate::~SimpleElementDelegate() = default; SimpleElementDelegate::~SimpleElementDelegate() = default;
bool SimpleElementDelegate::elementUnderCursor(
not_null<const Element*> view) {
return false;
}
float64 SimpleElementDelegate::elementHighlightOpacity(
not_null<const HistoryItem*> item) const {
return 0.;
}
bool SimpleElementDelegate::elementInSelectionMode() {
return false;
}
bool SimpleElementDelegate::elementIntersectsRange(
not_null<const Element*> view,
int from,
int till) {
return true;
}
void SimpleElementDelegate::elementStartStickerLoop(
not_null<const Element*> view) {
}
void SimpleElementDelegate::elementShowPollResults(
not_null<PollData*> poll,
FullMsgId context) {
}
void SimpleElementDelegate::elementOpenPhoto(
not_null<PhotoData*> photo,
FullMsgId context) {
}
void SimpleElementDelegate::elementOpenDocument(
not_null<DocumentData*> document,
FullMsgId context,
bool showInMediaView) {
}
void SimpleElementDelegate::elementCancelUpload(const FullMsgId &context) {
}
void SimpleElementDelegate::elementShowTooltip(
const TextWithEntities &text,
Fn<void()> hiddenCallback) {
}
bool SimpleElementDelegate::elementAnimationsPaused() { bool SimpleElementDelegate::elementAnimationsPaused() {
return _controller->isGifPausedAtLeastFor(Window::GifPauseReason::Any); return _controller->isGifPausedAtLeastFor(Window::GifPauseReason::Any);
} }
bool SimpleElementDelegate::elementHideReply(not_null<const Element*> view) {
return false;
}
bool SimpleElementDelegate::elementShownUnread(
not_null<const Element*> view) {
return view->data()->unread(view->data()->history());
}
void SimpleElementDelegate::elementSendBotCommand(
const QString &command,
const FullMsgId &context) {
}
void SimpleElementDelegate::elementHandleViaClick(not_null<UserData*> bot) {
}
bool SimpleElementDelegate::elementIsChatWide() {
return false;
}
auto SimpleElementDelegate::elementPathShiftGradient() auto SimpleElementDelegate::elementPathShiftGradient()
-> not_null<Ui::PathShiftGradient*> { -> not_null<Ui::PathShiftGradient*> {
return _pathGradient.get(); return _pathGradient.get();
} }
void SimpleElementDelegate::elementReplyTo(const FullMsgId &to) {
}
void SimpleElementDelegate::elementStartInteraction(
not_null<const Element*> view) {
}
void SimpleElementDelegate::elementStartPremium(
not_null<const Element*> view,
Element *replacing) {
}
void SimpleElementDelegate::elementCancelPremium(
not_null<const Element*> view) {
}
QString SimpleElementDelegate::elementAuthorRank(
not_null<const Element*> view) {
return {};
}
TextSelection UnshiftItemSelection( TextSelection UnshiftItemSelection(
TextSelection selection, TextSelection selection,
uint16 byLength) { uint16 byLength) {
@ -611,7 +613,24 @@ bool Element::isHidden() const {
return isHiddenByGroup(); return isHiddenByGroup();
} }
void Element::overrideMedia(std::unique_ptr<Media> media) {
Expects(!history()->owner().groups().find(data()));
_text = Ui::Text::String(st::msgMinWidth);
_textWidth = -1;
_textHeight = 0;
_media = std::move(media);
if (!pendingResize()) {
history()->owner().requestViewResize(this);
}
_flags |= Flag::MediaOverriden;
}
void Element::refreshMedia(Element *replacing) { void Element::refreshMedia(Element *replacing) {
if (_flags & Flag::MediaOverriden) {
return;
}
_flags &= ~Flag::HiddenByGroup; _flags &= ~Flag::HiddenByGroup;
const auto item = data(); const auto item = data();

View file

@ -117,13 +117,8 @@ public:
not_null<const Ui::ChatStyle*> st, not_null<const Ui::ChatStyle*> st,
Fn<void()> update); Fn<void()> update);
class SimpleElementDelegate : public ElementDelegate { class DefaultElementDelegate : public ElementDelegate {
public: public:
SimpleElementDelegate(
not_null<Window::SessionController*> controller,
Fn<void()> update);
~SimpleElementDelegate();
bool elementUnderCursor(not_null<const Element*> view) override; bool elementUnderCursor(not_null<const Element*> view) override;
[[nodiscard]] float64 elementHighlightOpacity( [[nodiscard]] float64 elementHighlightOpacity(
not_null<const HistoryItem*> item) const override; not_null<const HistoryItem*> item) const override;
@ -147,7 +142,6 @@ public:
void elementShowTooltip( void elementShowTooltip(
const TextWithEntities &text, const TextWithEntities &text,
Fn<void()> hiddenCallback) override; Fn<void()> hiddenCallback) override;
bool elementAnimationsPaused() override;
bool elementHideReply(not_null<const Element*> view) override; bool elementHideReply(not_null<const Element*> view) override;
bool elementShownUnread(not_null<const Element*> view) override; bool elementShownUnread(not_null<const Element*> view) override;
void elementSendBotCommand( void elementSendBotCommand(
@ -155,7 +149,6 @@ public:
const FullMsgId &context) override; const FullMsgId &context) override;
void elementHandleViaClick(not_null<UserData*> bot) override; void elementHandleViaClick(not_null<UserData*> bot) override;
bool elementIsChatWide() override; bool elementIsChatWide() override;
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
void elementReplyTo(const FullMsgId &to) override; void elementReplyTo(const FullMsgId &to) override;
void elementStartInteraction(not_null<const Element*> view) override; void elementStartInteraction(not_null<const Element*> view) override;
void elementStartPremium( void elementStartPremium(
@ -164,6 +157,17 @@ public:
void elementCancelPremium(not_null<const Element*> view) override; void elementCancelPremium(not_null<const Element*> view) override;
QString elementAuthorRank(not_null<const Element*> view) override; QString elementAuthorRank(not_null<const Element*> view) override;
};
class SimpleElementDelegate : public DefaultElementDelegate {
public:
SimpleElementDelegate(
not_null<Window::SessionController*> controller,
Fn<void()> update);
~SimpleElementDelegate();
bool elementAnimationsPaused() override;
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
protected: protected:
[[nodiscard]] not_null<Window::SessionController*> controller() const { [[nodiscard]] not_null<Window::SessionController*> controller() const {
@ -264,6 +268,7 @@ public:
CustomEmojiRepainting = 0x0100, CustomEmojiRepainting = 0x0100,
ScheduledUntilOnline = 0x0200, ScheduledUntilOnline = 0x0200,
TopicRootReply = 0x0400, TopicRootReply = 0x0400,
MediaOverriden = 0x0800,
}; };
using Flags = base::flags<Flag>; using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; } friend inline constexpr auto is_flag_type(Flag) { return true; }
@ -480,6 +485,8 @@ public:
Data::ReactionId, Data::ReactionId,
std::unique_ptr<Ui::ReactionFlyAnimation>>; std::unique_ptr<Ui::ReactionFlyAnimation>>;
void overrideMedia(std::unique_ptr<Media> media);
virtual ~Element(); virtual ~Element();
static void Hovered(Element *view); static void Hovered(Element *view);

View file

@ -509,27 +509,27 @@ void Controller::initLayout() {
.nameBoundingRect = nameBoundingRect(right, false), .nameBoundingRect = nameBoundingRect(right, false),
.nameFontSize = nameFontSize, .nameFontSize = nameFontSize,
}; };
if (!_locationAreas.empty()) { if (!_areas.empty()) {
rebuildLocationAreas(layout); rebuildActiveAreas(layout);
} }
return layout; return layout;
}); });
} }
void Controller::rebuildLocationAreas(const Layout &layout) const { void Controller::rebuildActiveAreas(const Layout &layout) const {
Expects(_locations.size() == _locationAreas.size());
const auto origin = layout.content.topLeft(); const auto origin = layout.content.topLeft();
const auto scale = layout.content.size(); const auto scale = layout.content.size();
for (auto i = 0, count = int(_locations.size()); i != count; ++i) { for (auto &area : _areas) {
auto &area = _locationAreas[i]; const auto &general = area.original;
const auto &general = _locations[i].area.geometry;
area.geometry = QRect( area.geometry = QRect(
int(base::SafeRound(general.x() * scale.width())), int(base::SafeRound(general.x() * scale.width())),
int(base::SafeRound(general.y() * scale.height())), int(base::SafeRound(general.y() * scale.height())),
int(base::SafeRound(general.width() * scale.width())), int(base::SafeRound(general.width() * scale.width())),
int(base::SafeRound(general.height() * scale.height())) int(base::SafeRound(general.height() * scale.height()))
).translated(origin); ).translated(origin);
if (const auto reaction = area.reaction.get()) {
reaction->setAreaGeometry(area.geometry);
}
} }
} }
@ -914,9 +914,16 @@ bool Controller::changeShown(Data::Story *story) {
const auto &locations = story const auto &locations = story
? story->locations() ? story->locations()
: std::vector<Data::StoryLocation>(); : std::vector<Data::StoryLocation>();
const auto &suggestedReactions = story
? story->suggestedReactions()
: std::vector<Data::SuggestedReaction>();
if (_locations != locations) { if (_locations != locations) {
_locations = locations; _locations = locations;
_locationAreas.clear(); _areas.clear();
}
if (_suggestedReactions != suggestedReactions) {
_suggestedReactions = suggestedReactions;
_areas.clear();
} }
return true; return true;
@ -1082,26 +1089,47 @@ void Controller::updatePlayback(const Player::TrackState &state) {
} }
} }
ClickHandlerPtr Controller::lookupLocationHandler(QPoint point) const { ClickHandlerPtr Controller::lookupAreaHandler(QPoint point) const {
const auto &layout = _layout.current(); const auto &layout = _layout.current();
if (_locations.empty() || !layout) { if ((_locations.empty() && _suggestedReactions.empty()) || !layout) {
return nullptr; return nullptr;
} else if (_locationAreas.empty()) { } else if (_areas.empty()) {
_locationAreas = _locations | ranges::views::transform([]( _areas.reserve(_locations.size() + _suggestedReactions.size());
const Data::StoryLocation &location) { for (const auto &location : _locations) {
return LocationArea{ _areas.push_back({
.original = location.area.geometry,
.rotation = location.area.rotation, .rotation = location.area.rotation,
.handler = std::make_shared<LocationClickHandler>( .handler = std::make_shared<LocationClickHandler>(
location.point), location.point),
}; });
}) | ranges::to_vector; }
rebuildLocationAreas(*layout); for (const auto &suggestedReaction : _suggestedReactions) {
const auto id = suggestedReaction.reaction;
_areas.push_back({
.original = suggestedReaction.area.geometry,
.rotation = suggestedReaction.area.rotation,
.handler = std::make_shared<LambdaClickHandler>([=] {
_reactions->applyLike(id);
}),
.reaction = _reactions->makeSuggestedReactionWidget(
suggestedReaction),
});
}
rebuildActiveAreas(*layout);
} }
for (const auto &area : _locationAreas) { const auto circleContains = [&](QRect circle) {
const auto radius = std::min(circle.width(), circle.height()) / 2;
const auto delta = circle.center() - point;
return QPoint::dotProduct(delta, delta) < (radius * radius);
};
for (const auto &area : _areas) {
const auto center = area.geometry.center(); const auto center = area.geometry.center();
const auto angle = -area.rotation; const auto angle = -area.rotation;
if (area.geometry.contains(Rotated(point, center, angle))) { const auto contains = area.reaction
? circleContains(area.geometry)
: area.geometry.contains(Rotated(point, center, angle));
if (contains) {
return area.handler; return area.handler;
} }
} }

View file

@ -68,6 +68,7 @@ enum class SiblingType;
struct ContentLayout; struct ContentLayout;
class CaptionFullView; class CaptionFullView;
enum class ReactionsMode; enum class ReactionsMode;
class SuggestedReactionView;
enum class HeaderLayout { enum class HeaderLayout {
Normal, Normal,
@ -135,7 +136,7 @@ public:
void ready(); void ready();
void updateVideoPlayback(const Player::TrackState &state); void updateVideoPlayback(const Player::TrackState &state);
[[nodiscard]] ClickHandlerPtr lookupLocationHandler(QPoint point) const; [[nodiscard]] ClickHandlerPtr lookupAreaHandler(QPoint point) const;
[[nodiscard]] bool subjumpAvailable(int delta) const; [[nodiscard]] bool subjumpAvailable(int delta) const;
[[nodiscard]] bool subjumpFor(int delta); [[nodiscard]] bool subjumpFor(int delta);
@ -197,10 +198,12 @@ private:
return peerId != 0; return peerId != 0;
} }
}; };
struct LocationArea { struct ActiveArea {
QRectF original;
QRect geometry; QRect geometry;
float64 rotation = 0.; float64 rotation = 0.;
ClickHandlerPtr handler; ClickHandlerPtr handler;
std::unique_ptr<SuggestedReactionView> reaction;
}; };
void initLayout(); void initLayout();
@ -215,7 +218,7 @@ private:
void updateContentFaded(); void updateContentFaded();
void updatePlayingAllowed(); void updatePlayingAllowed();
void setPlayingAllowed(bool allowed); void setPlayingAllowed(bool allowed);
void rebuildLocationAreas(const Layout &layout) const; void rebuildActiveAreas(const Layout &layout) const;
void hideSiblings(); void hideSiblings();
void showSiblings(not_null<Main::Session*> session); void showSiblings(not_null<Main::Session*> session);
@ -284,7 +287,8 @@ private:
bool _viewed = false; bool _viewed = false;
std::vector<Data::StoryLocation> _locations; std::vector<Data::StoryLocation> _locations;
mutable std::vector<LocationArea> _locationAreas; std::vector<Data::SuggestedReaction> _suggestedReactions;
mutable std::vector<ActiveArea> _areas;
std::vector<CachedSource> _cachedSourcesList; std::vector<CachedSource> _cachedSourcesList;
int _cachedSourceIndex = -1; int _cachedSourceIndex = -1;

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/stories/media_stories_reactions.h" #include "media/stories/media_stories_reactions.h"
#include "base/event_filter.h" #include "base/event_filter.h"
#include "base/unixtime.h"
#include "boxes/premium_preview_box.h" #include "boxes/premium_preview_box.h"
#include "chat_helpers/compose/compose_show.h" #include "chat_helpers/compose/compose_show.h"
#include "data/data_changes.h" #include "data/data_changes.h"
@ -16,17 +17,29 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_message_reactions.h" #include "data/data_message_reactions.h"
#include "data/data_peer.h" #include "data/data_peer.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "history/admin_log/history_admin_log_item.h"
#include "history/view/media/history_view_custom_emoji.h"
#include "history/view/media/history_view_media_unwrapped.h"
#include "history/view/reactions/history_view_reactions_selector.h" #include "history/view/reactions/history_view_reactions_selector.h"
#include "history/view/history_view_element.h"
#include "history/history_item_reply_markup.h"
#include "history/history_item.h"
#include "history/history.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "media/stories/media_stories_controller.h" #include "media/stories/media_stories_controller.h"
#include "ui/chat/chat_style.h"
#include "ui/effects/emoji_fly_animation.h" #include "ui/effects/emoji_fly_animation.h"
#include "ui/effects/path_shift_gradient.h"
#include "ui/effects/reaction_fly_animation.h" #include "ui/effects/reaction_fly_animation.h"
#include "ui/text/text_isolated_emoji.h"
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
#include "ui/animated_icon.h" #include "ui/animated_icon.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "styles/style_chat_helpers.h" #include "styles/style_chat_helpers.h"
#include "styles/style_chat.h"
#include "styles/style_media_view.h" #include "styles/style_media_view.h"
#include "styles/style_widgets.h" #include "styles/style_widgets.h"
#include "styles/style_window.h"
namespace Media::Stories { namespace Media::Stories {
namespace { namespace {
@ -34,6 +47,241 @@ namespace {
constexpr auto kReactionScaleOutTarget = 0.7; constexpr auto kReactionScaleOutTarget = 0.7;
constexpr auto kReactionScaleOutDuration = crl::time(1000); constexpr auto kReactionScaleOutDuration = crl::time(1000);
constexpr auto kMessageReactionScaleOutDuration = crl::time(400); constexpr auto kMessageReactionScaleOutDuration = crl::time(400);
constexpr auto kSuggestedBubbleSize = 1.0;
constexpr auto kSuggestedTailBigSize = 0.264;
constexpr auto kSuggestedTailBigOffset = 0.464;
constexpr auto kSuggestedTailSmallSize = 0.110;
constexpr auto kSuggestedTailSmallOffset = 0.697;
constexpr auto kSuggestedTailBigRotation = -42.29;
constexpr auto kSuggestedTailSmallRotation = -40.87;
constexpr auto kSuggestedReactionSize = 0.7;
class ReactionView final
: public Ui::RpWidget
, public SuggestedReactionView
, public HistoryView::DefaultElementDelegate {
public:
ReactionView(
QWidget *parent,
not_null<Main::Session*> session,
const Data::SuggestedReaction &reaction);
void setAreaGeometry(QRect geometry) override;
private:
using Element = HistoryView::Element;
not_null<HistoryView::ElementDelegate*> delegate();
HistoryView::Context elementContext() override;
bool elementAnimationsPaused() override;
bool elementShownUnread(not_null<const Element*> view) override;
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
void paintEvent(QPaintEvent *e) override;
void cacheBackground();
Data::SuggestedReaction _data;
std::unique_ptr<Ui::ChatStyle> _chatStyle;
std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
AdminLog::OwnedItem _fake;
QImage _background;
QRectF _bubbleGeometry;
int _size = 0;
int _mediaLeft = 0;
int _mediaTop = 0;
int _mediaWidth = 0;
int _mediaHeight = 0;
float64 _bubble = 0;
float64 _bigOffset = 0;
float64 _bigSize = 0;
float64 _smallOffset = 0;
float64 _smallSize = 0;
};
[[nodiscard]] AdminLog::OwnedItem GenerateFakeItem(
not_null<HistoryView::ElementDelegate*> delegate,
not_null<History*> history) {
Expects(history->peer->isUser());
const auto flags = MessageFlag::FakeHistoryItem
| MessageFlag::HasFromId;
const auto replyTo = FullReplyTo();
const auto viaBotId = UserId();
const auto groupedId = uint64();
const auto item = history->makeMessage(
history->nextNonHistoryEntryId(),
flags,
replyTo,
viaBotId,
base::unixtime::now(),
peerToUser(history->peer->id),
QString(),
TextWithEntities(),
MTP_messageMediaEmpty(),
HistoryMessageMarkupData(),
groupedId);
return AdminLog::OwnedItem(delegate, item);
}
ReactionView::ReactionView(
QWidget *parent,
not_null<Main::Session*> session,
const Data::SuggestedReaction &reaction)
: RpWidget(parent)
, _data(reaction)
, _chatStyle(std::make_unique<Ui::ChatStyle>())
, _pathGradient(
std::make_unique<Ui::PathShiftGradient>(
st::shadowFg,
st::shadowFg,
[=] { update(); }))
, _fake(
GenerateFakeItem(
delegate(),
session->data().history(PeerData::kServiceNotificationsId))) {
style::PaletteChanged() | rpl::start_with_next([=] {
_background = QImage();
}, lifetime());
const auto view = _fake.get();
const auto item = view->data();
const auto entityData = [&] {
const auto &id = _data.reaction;
const auto reactions = &session->data().reactions();
reactions->preloadAnimationsFor(id);
if (const auto customId = id.custom()) {
return Data::SerializeCustomEmojiId(customId);
}
const auto type = Data::Reactions::Type::All;
const auto &list = reactions->list(type);
const auto i = ranges::find(list, id, &Data::Reaction::id);
return (i != end(list))
? Data::SerializeCustomEmojiId(i->selectAnimation->id)
: QString();
}();
const auto emoji = Ui::Text::OnlyCustomEmoji{
{ { { entityData } } }
};
view->overrideMedia(std::make_unique<HistoryView::UnwrappedMedia>(
view,
std::make_unique<HistoryView::CustomEmoji>(view, emoji)));
view->initDimensions();
_mediaLeft = st::msgMargin.left();
_mediaTop = st::msgMargin.top();
_mediaWidth = _mediaHeight = view->resizeGetHeight(st::windowMinWidth)
- _mediaTop
- st::msgMargin.bottom();
session->data().viewRepaintRequest(
) | rpl::start_with_next([=](not_null<const Element*> element) {
if (element == view) {
update();
}
}, lifetime());
setAttribute(Qt::WA_TransparentForMouseEvents);
show();
}
void ReactionView::setAreaGeometry(QRect geometry) {
_size = std::min(geometry.width(), geometry.height());
const auto scaled = [&](float64 scale) {
return int(base::SafeRound(scale * _size));
};
_bubble = _size * kSuggestedBubbleSize;
_bigOffset = _bubble * kSuggestedTailBigOffset;
_bigSize = _bubble * kSuggestedTailBigSize;
_smallOffset = _bubble * kSuggestedTailSmallOffset;
_smallSize = _bubble * kSuggestedTailSmallSize;
const auto add = int(base::SafeRound(_smallOffset + _smallSize))
- (_size / 2);
setGeometry(geometry.marginsAdded({ add, add, add, add }));
}
not_null<HistoryView::ElementDelegate*> ReactionView::delegate() {
return static_cast<HistoryView::ElementDelegate*>(this);
}
void ReactionView::paintEvent(QPaintEvent *e) {
auto p = Painter(this);
if (!_size) {
return;
} else if (_background.size() != size() * style::DevicePixelRatio()) {
cacheBackground();
}
p.drawImage(0, 0, _background);
auto hq = PainterHighQualityEnabler(p);
p.translate(_bubbleGeometry.center());
p.scale(
kSuggestedReactionSize * _bubbleGeometry.width() / _mediaWidth,
kSuggestedReactionSize * _bubbleGeometry.height() / _mediaHeight);
p.rotate(_data.area.rotation);
p.translate(
-(_mediaLeft + (_mediaWidth / 2)),
-(_mediaTop + (_mediaHeight / 2)));
auto context = Ui::ChatPaintContext{
.st = _chatStyle.get(),
.viewport = rect(),
.clip = rect(),
.now = crl::now(),
};
_fake->draw(p, context);
}
void ReactionView::cacheBackground() {
const auto ratio = style::DevicePixelRatio();
_background = QImage(
size() * ratio,
QImage::Format_ARGB32_Premultiplied);
_background.setDevicePixelRatio(ratio);
_background.fill(Qt::transparent);
auto p = QPainter(&_background);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(_data.dark ? QColor(0, 0, 0, 128) : QColor(255, 255, 255));
p.setCompositionMode(QPainter::CompositionMode_Source);
_bubbleGeometry = QRectF(
(width() - _bubble) / 2.,
(height() - _bubble) / 2.,
_bubble,
_bubble);
p.drawEllipse(_bubbleGeometry);
const auto center = QPointF(width() / 2., height() / 2.);
p.translate(center);
auto previous = 0.;
const auto rotate = [&](float64 initial) {
if (_data.flipped) {
initial = 180 - initial;
}
auto rotation = _data.area.rotation - initial;
while (rotation < 0) {
rotation += 360;
}
while (rotation >= 360) {
rotation -= 360;
}
const auto delta = rotation - previous;
previous = rotation;
p.rotate(delta);
};
const auto paintTailPart = [&](float64 offset, float64 size) {
p.drawEllipse(QRectF(offset - size / 2., -size / 2., size, size));
};
rotate(kSuggestedTailBigRotation);
paintTailPart(_bigOffset, _bigSize);
rotate(kSuggestedTailSmallRotation);
paintTailPart(_smallOffset, _smallSize);
}
[[nodiscard]] Data::ReactionId HeartReactionId() { [[nodiscard]] Data::ReactionId HeartReactionId() {
return { QString() + QChar(10084) }; return { QString() + QChar(10084) };
@ -67,6 +315,24 @@ constexpr auto kMessageReactionScaleOutDuration = crl::time(400);
return result; return result;
} }
HistoryView::Context ReactionView::elementContext() {
return HistoryView::Context::ContactPreview;
}
bool ReactionView::elementAnimationsPaused() {
return false;
}
bool ReactionView::elementShownUnread(
not_null<const Element*> view) {
return false;
}
auto ReactionView::elementPathShiftGradient()
-> not_null<Ui::PathShiftGradient*> {
return _pathGradient.get();
}
} // namespace } // namespace
class Reactions::Panel final { class Reactions::Panel final {
@ -358,6 +624,15 @@ auto Reactions::chosen() const -> rpl::producer<Chosen> {
return _chosen.events(); return _chosen.events();
} }
auto Reactions::makeSuggestedReactionWidget(
const Data::SuggestedReaction &reaction)
-> std::unique_ptr<SuggestedReactionView> {
return std::make_unique<ReactionView>(
_controller->wrap(),
&_controller->uiShow()->session(),
reaction);
}
void Reactions::setReplyFieldState( void Reactions::setReplyFieldState(
rpl::producer<bool> focused, rpl::producer<bool> focused,
rpl::producer<bool> hasSendText) { rpl::producer<bool> hasSendText) {
@ -458,9 +733,12 @@ void Reactions::outsidePressed() {
void Reactions::toggleLiked() { void Reactions::toggleLiked() {
const auto liked = !_liked.current().empty(); const auto liked = !_liked.current().empty();
const auto now = liked ? Data::ReactionId() : HeartReactionId(); applyLike(liked ? Data::ReactionId() : HeartReactionId());
if (_liked.current() != now) { }
animateAndProcess({ { .id = now }, ReactionsMode::Reaction });
void Reactions::applyLike(Data::ReactionId id) {
if (_liked.current() != id) {
animateAndProcess({ { .id = id }, ReactionsMode::Reaction });
} }
} }

View file

@ -15,6 +15,7 @@ class DocumentMedia;
struct ReactionId; struct ReactionId;
class Session; class Session;
class Story; class Story;
struct SuggestedReaction;
} // namespace Data } // namespace Data
namespace HistoryView::Reactions { namespace HistoryView::Reactions {
@ -40,6 +41,13 @@ enum class ReactionsMode {
Reaction, Reaction,
}; };
class SuggestedReactionView {
public:
virtual ~SuggestedReactionView() = default;
virtual void setAreaGeometry(QRect geometry) = 0;
};
class Reactions final { class Reactions final {
public: public:
explicit Reactions(not_null<Controller*> controller); explicit Reactions(not_null<Controller*> controller);
@ -64,8 +72,13 @@ public:
void hide(); void hide();
void outsidePressed(); void outsidePressed();
void toggleLiked(); void toggleLiked();
void applyLike(Data::ReactionId id);
void ready(); void ready();
[[nodiscard]] auto makeSuggestedReactionWidget(
const Data::SuggestedReaction &reaction)
-> std::unique_ptr<SuggestedReactionView>;
void setReplyFieldState( void setReplyFieldState(
rpl::producer<bool> focused, rpl::producer<bool> focused,
rpl::producer<bool> hasSendText); rpl::producer<bool> hasSendText);

View file

@ -59,8 +59,8 @@ void View::updatePlayback(const Player::TrackState &state) {
_controller->updateVideoPlayback(state); _controller->updateVideoPlayback(state);
} }
ClickHandlerPtr View::lookupLocationHandler(QPoint point) const { ClickHandlerPtr View::lookupAreaHandler(QPoint point) const {
return _controller->lookupLocationHandler(point); return _controller->lookupAreaHandler(point);
} }
bool View::subjumpAvailable(int delta) const { bool View::subjumpAvailable(int delta) const {

View file

@ -81,7 +81,7 @@ public:
void showFullCaption(); void showFullCaption();
void updatePlayback(const Player::TrackState &state); void updatePlayback(const Player::TrackState &state);
[[nodiscard]] ClickHandlerPtr lookupLocationHandler(QPoint point) const; [[nodiscard]] ClickHandlerPtr lookupAreaHandler(QPoint point) const;
[[nodiscard]] bool subjumpAvailable(int delta) const; [[nodiscard]] bool subjumpAvailable(int delta) const;
[[nodiscard]] bool subjumpFor(int delta) const; [[nodiscard]] bool subjumpFor(int delta) const;

View file

@ -5685,7 +5685,7 @@ void OverlayWidget::updateOver(QPoint pos) {
lnk = _groupThumbs->getState(point); lnk = _groupThumbs->getState(point);
lnkhost = this; lnkhost = this;
} else if (_stories) { } else if (_stories) {
lnk = _stories->lookupLocationHandler(pos); lnk = _stories->lookupAreaHandler(pos);
lnkhost = this; lnkhost = this;
} }