mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +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());
|
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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue