mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 06:07:06 +02:00
Initial suggested reaction implementation.
This commit is contained in:
parent
d5b429e910
commit
f3e65181cd
9 changed files with 479 additions and 130 deletions
|
@ -106,6 +106,99 @@ std::unique_ptr<Ui::PathShiftGradient> MakePathShiftGradient(
|
|||
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(
|
||||
not_null<Window::SessionController*> controller,
|
||||
Fn<void()> update)
|
||||
|
@ -118,106 +211,15 @@ SimpleElementDelegate::SimpleElementDelegate(
|
|||
|
||||
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() {
|
||||
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()
|
||||
-> not_null<Ui::PathShiftGradient*> {
|
||||
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 selection,
|
||||
uint16 byLength) {
|
||||
|
@ -611,7 +613,24 @@ bool Element::isHidden() const {
|
|||
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) {
|
||||
if (_flags & Flag::MediaOverriden) {
|
||||
return;
|
||||
}
|
||||
_flags &= ~Flag::HiddenByGroup;
|
||||
|
||||
const auto item = data();
|
||||
|
|
|
@ -117,13 +117,8 @@ public:
|
|||
not_null<const Ui::ChatStyle*> st,
|
||||
Fn<void()> update);
|
||||
|
||||
class SimpleElementDelegate : public ElementDelegate {
|
||||
class DefaultElementDelegate : public ElementDelegate {
|
||||
public:
|
||||
SimpleElementDelegate(
|
||||
not_null<Window::SessionController*> controller,
|
||||
Fn<void()> update);
|
||||
~SimpleElementDelegate();
|
||||
|
||||
bool elementUnderCursor(not_null<const Element*> view) override;
|
||||
[[nodiscard]] float64 elementHighlightOpacity(
|
||||
not_null<const HistoryItem*> item) const override;
|
||||
|
@ -147,7 +142,6 @@ public:
|
|||
void elementShowTooltip(
|
||||
const TextWithEntities &text,
|
||||
Fn<void()> hiddenCallback) override;
|
||||
bool elementAnimationsPaused() override;
|
||||
bool elementHideReply(not_null<const Element*> view) override;
|
||||
bool elementShownUnread(not_null<const Element*> view) override;
|
||||
void elementSendBotCommand(
|
||||
|
@ -155,7 +149,6 @@ public:
|
|||
const FullMsgId &context) override;
|
||||
void elementHandleViaClick(not_null<UserData*> bot) override;
|
||||
bool elementIsChatWide() override;
|
||||
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
|
||||
void elementReplyTo(const FullMsgId &to) override;
|
||||
void elementStartInteraction(not_null<const Element*> view) override;
|
||||
void elementStartPremium(
|
||||
|
@ -164,6 +157,17 @@ public:
|
|||
void elementCancelPremium(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:
|
||||
[[nodiscard]] not_null<Window::SessionController*> controller() const {
|
||||
|
@ -264,6 +268,7 @@ public:
|
|||
CustomEmojiRepainting = 0x0100,
|
||||
ScheduledUntilOnline = 0x0200,
|
||||
TopicRootReply = 0x0400,
|
||||
MediaOverriden = 0x0800,
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||
|
@ -480,6 +485,8 @@ public:
|
|||
Data::ReactionId,
|
||||
std::unique_ptr<Ui::ReactionFlyAnimation>>;
|
||||
|
||||
void overrideMedia(std::unique_ptr<Media> media);
|
||||
|
||||
virtual ~Element();
|
||||
|
||||
static void Hovered(Element *view);
|
||||
|
|
|
@ -509,27 +509,27 @@ void Controller::initLayout() {
|
|||
.nameBoundingRect = nameBoundingRect(right, false),
|
||||
.nameFontSize = nameFontSize,
|
||||
};
|
||||
if (!_locationAreas.empty()) {
|
||||
rebuildLocationAreas(layout);
|
||||
if (!_areas.empty()) {
|
||||
rebuildActiveAreas(layout);
|
||||
}
|
||||
return layout;
|
||||
});
|
||||
}
|
||||
|
||||
void Controller::rebuildLocationAreas(const Layout &layout) const {
|
||||
Expects(_locations.size() == _locationAreas.size());
|
||||
|
||||
void Controller::rebuildActiveAreas(const Layout &layout) const {
|
||||
const auto origin = layout.content.topLeft();
|
||||
const auto scale = layout.content.size();
|
||||
for (auto i = 0, count = int(_locations.size()); i != count; ++i) {
|
||||
auto &area = _locationAreas[i];
|
||||
const auto &general = _locations[i].area.geometry;
|
||||
for (auto &area : _areas) {
|
||||
const auto &general = area.original;
|
||||
area.geometry = QRect(
|
||||
int(base::SafeRound(general.x() * scale.width())),
|
||||
int(base::SafeRound(general.y() * scale.height())),
|
||||
int(base::SafeRound(general.width() * scale.width())),
|
||||
int(base::SafeRound(general.height() * scale.height()))
|
||||
).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
|
||||
? story->locations()
|
||||
: std::vector<Data::StoryLocation>();
|
||||
const auto &suggestedReactions = story
|
||||
? story->suggestedReactions()
|
||||
: std::vector<Data::SuggestedReaction>();
|
||||
if (_locations != locations) {
|
||||
_locations = locations;
|
||||
_locationAreas.clear();
|
||||
_areas.clear();
|
||||
}
|
||||
if (_suggestedReactions != suggestedReactions) {
|
||||
_suggestedReactions = suggestedReactions;
|
||||
_areas.clear();
|
||||
}
|
||||
|
||||
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();
|
||||
if (_locations.empty() || !layout) {
|
||||
if ((_locations.empty() && _suggestedReactions.empty()) || !layout) {
|
||||
return nullptr;
|
||||
} else if (_locationAreas.empty()) {
|
||||
_locationAreas = _locations | ranges::views::transform([](
|
||||
const Data::StoryLocation &location) {
|
||||
return LocationArea{
|
||||
} else if (_areas.empty()) {
|
||||
_areas.reserve(_locations.size() + _suggestedReactions.size());
|
||||
for (const auto &location : _locations) {
|
||||
_areas.push_back({
|
||||
.original = location.area.geometry,
|
||||
.rotation = location.area.rotation,
|
||||
.handler = std::make_shared<LocationClickHandler>(
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,6 +68,7 @@ enum class SiblingType;
|
|||
struct ContentLayout;
|
||||
class CaptionFullView;
|
||||
enum class ReactionsMode;
|
||||
class SuggestedReactionView;
|
||||
|
||||
enum class HeaderLayout {
|
||||
Normal,
|
||||
|
@ -135,7 +136,7 @@ public:
|
|||
void ready();
|
||||
|
||||
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 subjumpFor(int delta);
|
||||
|
@ -197,10 +198,12 @@ private:
|
|||
return peerId != 0;
|
||||
}
|
||||
};
|
||||
struct LocationArea {
|
||||
struct ActiveArea {
|
||||
QRectF original;
|
||||
QRect geometry;
|
||||
float64 rotation = 0.;
|
||||
ClickHandlerPtr handler;
|
||||
std::unique_ptr<SuggestedReactionView> reaction;
|
||||
};
|
||||
|
||||
void initLayout();
|
||||
|
@ -215,7 +218,7 @@ private:
|
|||
void updateContentFaded();
|
||||
void updatePlayingAllowed();
|
||||
void setPlayingAllowed(bool allowed);
|
||||
void rebuildLocationAreas(const Layout &layout) const;
|
||||
void rebuildActiveAreas(const Layout &layout) const;
|
||||
|
||||
void hideSiblings();
|
||||
void showSiblings(not_null<Main::Session*> session);
|
||||
|
@ -284,7 +287,8 @@ private:
|
|||
bool _viewed = false;
|
||||
|
||||
std::vector<Data::StoryLocation> _locations;
|
||||
mutable std::vector<LocationArea> _locationAreas;
|
||||
std::vector<Data::SuggestedReaction> _suggestedReactions;
|
||||
mutable std::vector<ActiveArea> _areas;
|
||||
|
||||
std::vector<CachedSource> _cachedSourcesList;
|
||||
int _cachedSourceIndex = -1;
|
||||
|
|
|
@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "media/stories/media_stories_reactions.h"
|
||||
|
||||
#include "base/event_filter.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "chat_helpers/compose/compose_show.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_peer.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/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 "media/stories/media_stories_controller.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/effects/emoji_fly_animation.h"
|
||||
#include "ui/effects/path_shift_gradient.h"
|
||||
#include "ui/effects/reaction_fly_animation.h"
|
||||
#include "ui/text/text_isolated_emoji.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/animated_icon.h"
|
||||
#include "ui/painter.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_media_view.h"
|
||||
#include "styles/style_widgets.h"
|
||||
#include "styles/style_window.h"
|
||||
|
||||
namespace Media::Stories {
|
||||
namespace {
|
||||
|
@ -34,6 +47,241 @@ namespace {
|
|||
constexpr auto kReactionScaleOutTarget = 0.7;
|
||||
constexpr auto kReactionScaleOutDuration = crl::time(1000);
|
||||
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() {
|
||||
return { QString() + QChar(10084) };
|
||||
|
@ -67,6 +315,24 @@ constexpr auto kMessageReactionScaleOutDuration = crl::time(400);
|
|||
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
|
||||
|
||||
class Reactions::Panel final {
|
||||
|
@ -358,6 +624,15 @@ auto Reactions::chosen() const -> rpl::producer<Chosen> {
|
|||
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(
|
||||
rpl::producer<bool> focused,
|
||||
rpl::producer<bool> hasSendText) {
|
||||
|
@ -458,9 +733,12 @@ void Reactions::outsidePressed() {
|
|||
|
||||
void Reactions::toggleLiked() {
|
||||
const auto liked = !_liked.current().empty();
|
||||
const auto now = liked ? Data::ReactionId() : HeartReactionId();
|
||||
if (_liked.current() != now) {
|
||||
animateAndProcess({ { .id = now }, ReactionsMode::Reaction });
|
||||
applyLike(liked ? Data::ReactionId() : HeartReactionId());
|
||||
}
|
||||
|
||||
void Reactions::applyLike(Data::ReactionId id) {
|
||||
if (_liked.current() != id) {
|
||||
animateAndProcess({ { .id = id }, ReactionsMode::Reaction });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ class DocumentMedia;
|
|||
struct ReactionId;
|
||||
class Session;
|
||||
class Story;
|
||||
struct SuggestedReaction;
|
||||
} // namespace Data
|
||||
|
||||
namespace HistoryView::Reactions {
|
||||
|
@ -40,6 +41,13 @@ enum class ReactionsMode {
|
|||
Reaction,
|
||||
};
|
||||
|
||||
class SuggestedReactionView {
|
||||
public:
|
||||
virtual ~SuggestedReactionView() = default;
|
||||
|
||||
virtual void setAreaGeometry(QRect geometry) = 0;
|
||||
};
|
||||
|
||||
class Reactions final {
|
||||
public:
|
||||
explicit Reactions(not_null<Controller*> controller);
|
||||
|
@ -64,8 +72,13 @@ public:
|
|||
void hide();
|
||||
void outsidePressed();
|
||||
void toggleLiked();
|
||||
void applyLike(Data::ReactionId id);
|
||||
void ready();
|
||||
|
||||
[[nodiscard]] auto makeSuggestedReactionWidget(
|
||||
const Data::SuggestedReaction &reaction)
|
||||
-> std::unique_ptr<SuggestedReactionView>;
|
||||
|
||||
void setReplyFieldState(
|
||||
rpl::producer<bool> focused,
|
||||
rpl::producer<bool> hasSendText);
|
||||
|
|
|
@ -59,8 +59,8 @@ void View::updatePlayback(const Player::TrackState &state) {
|
|||
_controller->updateVideoPlayback(state);
|
||||
}
|
||||
|
||||
ClickHandlerPtr View::lookupLocationHandler(QPoint point) const {
|
||||
return _controller->lookupLocationHandler(point);
|
||||
ClickHandlerPtr View::lookupAreaHandler(QPoint point) const {
|
||||
return _controller->lookupAreaHandler(point);
|
||||
}
|
||||
|
||||
bool View::subjumpAvailable(int delta) const {
|
||||
|
|
|
@ -81,7 +81,7 @@ public:
|
|||
void showFullCaption();
|
||||
|
||||
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 subjumpFor(int delta) const;
|
||||
|
|
|
@ -5685,7 +5685,7 @@ void OverlayWidget::updateOver(QPoint pos) {
|
|||
lnk = _groupThumbs->getState(point);
|
||||
lnkhost = this;
|
||||
} else if (_stories) {
|
||||
lnk = _stories->lookupLocationHandler(pos);
|
||||
lnk = _stories->lookupAreaHandler(pos);
|
||||
lnkhost = this;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue