mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Proof-of-concept reactions dropdown.
This commit is contained in:
parent
9c18f7b0e3
commit
e148b5ff08
19 changed files with 585 additions and 9 deletions
|
@ -66,9 +66,7 @@ void AddReactionIcon(
|
||||||
document->session().downloaderTaskFinished(
|
document->session().downloaderTaskFinished(
|
||||||
) | rpl::map([=] {
|
) | rpl::map([=] {
|
||||||
return state->media->getStickerLarge();
|
return state->media->getStickerLarge();
|
||||||
}) | rpl::filter([=](Image *image) {
|
}) | rpl::filter_nullptr() | rpl::take(
|
||||||
return (image != nullptr);
|
|
||||||
}) | rpl::take(
|
|
||||||
1
|
1
|
||||||
) | rpl::start_with_next([=](not_null<Image*> image) {
|
) | rpl::start_with_next([=](not_null<Image*> image) {
|
||||||
setImage(image);
|
setImage(image);
|
||||||
|
|
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/basic_click_handlers.h"
|
#include "ui/basic_click_handlers.h"
|
||||||
|
|
||||||
constexpr auto kPeerLinkPeerIdProperty = 0x01;
|
constexpr auto kPeerLinkPeerIdProperty = 0x01;
|
||||||
|
constexpr auto kReactionIdProperty = 0x02;
|
||||||
|
|
||||||
namespace Main {
|
namespace Main {
|
||||||
class Session;
|
class Session;
|
||||||
|
|
|
@ -673,6 +673,11 @@ void InnerWidget::elementStartInteraction(not_null<const Element*> view) {
|
||||||
void InnerWidget::elementShowReactions(not_null<const Element*> view) {
|
void InnerWidget::elementShowReactions(not_null<const Element*> view) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Data::Reaction *InnerWidget::elementCornerReaction(
|
||||||
|
not_null<const Element*> view) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
void InnerWidget::saveState(not_null<SectionMemento*> memento) {
|
void InnerWidget::saveState(not_null<SectionMemento*> memento) {
|
||||||
memento->setFilter(std::move(_filter));
|
memento->setFilter(std::move(_filter));
|
||||||
memento->setAdmins(std::move(_admins));
|
memento->setAdmins(std::move(_admins));
|
||||||
|
|
|
@ -141,6 +141,8 @@ public:
|
||||||
not_null<const HistoryView::Element*> view) override;
|
not_null<const HistoryView::Element*> view) override;
|
||||||
void elementShowReactions(
|
void elementShowReactions(
|
||||||
not_null<const HistoryView::Element*> view) override;
|
not_null<const HistoryView::Element*> view) override;
|
||||||
|
const Data::Reaction *elementCornerReaction(
|
||||||
|
not_null<const HistoryView::Element*> view) override;
|
||||||
|
|
||||||
~InnerWidget();
|
~InnerWidget();
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/view/history_view_service_message.h"
|
#include "history/view/history_view_service_message.h"
|
||||||
#include "history/view/history_view_cursor_state.h"
|
#include "history/view/history_view_cursor_state.h"
|
||||||
#include "history/view/history_view_context_menu.h"
|
#include "history/view/history_view_context_menu.h"
|
||||||
|
#include "history/view/history_view_reactions.h"
|
||||||
#include "history/view/history_view_emoji_interactions.h"
|
#include "history/view/history_view_emoji_interactions.h"
|
||||||
#include "history/history_item_components.h"
|
#include "history/history_item_components.h"
|
||||||
#include "history/history_item_text.h"
|
#include "history/history_item_text.h"
|
||||||
|
@ -176,6 +177,8 @@ HistoryInner::HistoryInner(
|
||||||
HistoryView::MakePathShiftGradient(
|
HistoryView::MakePathShiftGradient(
|
||||||
controller->chatStyle(),
|
controller->chatStyle(),
|
||||||
[=] { update(); }))
|
[=] { update(); }))
|
||||||
|
, _reactionsMenus(
|
||||||
|
std::make_unique<HistoryView::ReactionsMenuManager>(historyWidget))
|
||||||
, _touchSelectTimer([=] { onTouchSelect(); })
|
, _touchSelectTimer([=] { onTouchSelect(); })
|
||||||
, _touchScrollTimer([=] { onTouchScrollTimer(); })
|
, _touchScrollTimer([=] { onTouchScrollTimer(); })
|
||||||
, _scrollDateCheck([this] { scrollDateCheck(); })
|
, _scrollDateCheck([this] { scrollDateCheck(); })
|
||||||
|
@ -222,6 +225,14 @@ HistoryInner::HistoryInner(
|
||||||
_controller->emojiInteractions().playStarted(_peer, std::move(emoji));
|
_controller->emojiInteractions().playStarted(_peer, std::move(emoji));
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
|
using ChosenReaction = HistoryView::ReactionsMenuManager::Chosen;
|
||||||
|
_reactionsMenus->chosen(
|
||||||
|
) | rpl::start_with_next([=](ChosenReaction reaction) {
|
||||||
|
if (const auto item = session().data().message(reaction.context)) {
|
||||||
|
item->addReaction(reaction.emoji);
|
||||||
|
}
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
session().data().itemRemoved(
|
session().data().itemRemoved(
|
||||||
) | rpl::start_with_next(
|
) | rpl::start_with_next(
|
||||||
[this](auto item) { itemRemoved(item); },
|
[this](auto item) { itemRemoved(item); },
|
||||||
|
@ -263,6 +274,18 @@ HistoryInner::HistoryInner(
|
||||||
update();
|
update();
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
|
rpl::combine(
|
||||||
|
rpl::single(
|
||||||
|
rpl::empty_value()
|
||||||
|
) | rpl::then(session().data().reactions().updates()),
|
||||||
|
session().changes().peerFlagsValue(
|
||||||
|
_peer,
|
||||||
|
Data::PeerUpdate::Flag::Reactions)
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
_reactions = session().data().reactions().list(_peer);
|
||||||
|
repaintItem(App::mousedItem());
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
controller->adaptive().chatWideValue(
|
controller->adaptive().chatWideValue(
|
||||||
) | rpl::start_with_next([=](bool wide) {
|
) | rpl::start_with_next([=](bool wide) {
|
||||||
_isChatWide = wide;
|
_isChatWide = wide;
|
||||||
|
@ -1510,6 +1533,7 @@ void HistoryInner::mouseActionFinish(
|
||||||
.sessionWindow = base::make_weak(_controller.get()),
|
.sessionWindow = base::make_weak(_controller.get()),
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
_reactionsMenus->hideAll(anim::type::normal);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ((_mouseAction == MouseAction::PrepareSelect)
|
if ((_mouseAction == MouseAction::PrepareSelect)
|
||||||
|
@ -2125,6 +2149,19 @@ void HistoryInner::copySelectedText() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HistoryInner::showReactionsMenu(FullMsgId itemId, QRect area) {
|
||||||
|
const auto top = itemTop(session().data().message(itemId));
|
||||||
|
if (top < 0) {
|
||||||
|
area = QRect(); // Just hide.
|
||||||
|
}
|
||||||
|
const auto skip = st::reactionCornerOut.y();
|
||||||
|
area = area.marginsRemoved({ 0, skip, 0, skip });
|
||||||
|
_reactionsMenus->showReactionsMenu(
|
||||||
|
itemId,
|
||||||
|
{ mapToGlobal(area.translated(0, top).topLeft()), area.size() },
|
||||||
|
_reactions);
|
||||||
|
}
|
||||||
|
|
||||||
void HistoryInner::savePhotoToFile(not_null<PhotoData*> photo) {
|
void HistoryInner::savePhotoToFile(not_null<PhotoData*> photo) {
|
||||||
const auto media = photo->activeMediaView();
|
const auto media = photo->activeMediaView();
|
||||||
if (photo->isNull() || !media || !media->loaded()) {
|
if (photo->isNull() || !media || !media->loaded()) {
|
||||||
|
@ -2884,6 +2921,13 @@ void HistoryInner::elementShowReactions(not_null<const Element*> view) {
|
||||||
view->data()));
|
view->data()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Data::Reaction *HistoryInner::elementCornerReaction(
|
||||||
|
not_null<const Element*> view) {
|
||||||
|
return (view == App::mousedItem() && !_reactions.empty())
|
||||||
|
? &_reactions.front()
|
||||||
|
: nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
auto HistoryInner::getSelectionState() const
|
auto HistoryInner::getSelectionState() const
|
||||||
-> HistoryView::TopBarWidget::SelectedState {
|
-> HistoryView::TopBarWidget::SelectedState {
|
||||||
auto result = HistoryView::TopBarWidget::SelectedState {};
|
auto result = HistoryView::TopBarWidget::SelectedState {};
|
||||||
|
@ -2960,7 +3004,16 @@ void HistoryInner::mouseActionUpdate() {
|
||||||
view = block->messages[_curItem].get();
|
view = block->messages[_curItem].get();
|
||||||
item = view->data();
|
item = view->data();
|
||||||
|
|
||||||
App::mousedItem(view);
|
const auto was = App::mousedItem();
|
||||||
|
if (was != view) {
|
||||||
|
if (!_reactions.empty()) {
|
||||||
|
repaintItem(was);
|
||||||
|
}
|
||||||
|
App::mousedItem(view);
|
||||||
|
if (!_reactions.empty()) {
|
||||||
|
repaintItem(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
m = mapPointToItem(point, view);
|
m = mapPointToItem(point, view);
|
||||||
if (view->pointState(m) != PointState::Outside) {
|
if (view->pointState(m) != PointState::Outside) {
|
||||||
if (App::hoveredItem() != view) {
|
if (App::hoveredItem() != view) {
|
||||||
|
@ -3095,6 +3148,7 @@ void HistoryInner::mouseActionUpdate() {
|
||||||
|| dragState.customTooltip) {
|
|| dragState.customTooltip) {
|
||||||
Ui::Tooltip::Show(1000, this);
|
Ui::Tooltip::Show(1000, this);
|
||||||
}
|
}
|
||||||
|
showReactionsMenu(dragState.itemId, dragState.reactionArea);
|
||||||
|
|
||||||
Qt::CursorShape cur = style::cur_default;
|
Qt::CursorShape cur = style::cur_default;
|
||||||
if (_mouseAction == MouseAction::None) {
|
if (_mouseAction == MouseAction::None) {
|
||||||
|
@ -3798,6 +3852,12 @@ not_null<HistoryView::ElementDelegate*> HistoryInner::ElementDelegate() {
|
||||||
Instance->elementShowReactions(view);
|
Instance->elementShowReactions(view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const Data::Reaction *elementCornerReaction(
|
||||||
|
not_null<const Element*> view) override {
|
||||||
|
Expects(Instance != nullptr);
|
||||||
|
|
||||||
|
return Instance->elementCornerReaction(view);
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
namespace Data {
|
namespace Data {
|
||||||
struct Group;
|
struct Group;
|
||||||
class CloudImageView;
|
class CloudImageView;
|
||||||
|
struct Reaction;
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
@ -28,6 +29,7 @@ enum class CursorState : char;
|
||||||
enum class PointState : char;
|
enum class PointState : char;
|
||||||
class EmptyPainter;
|
class EmptyPainter;
|
||||||
class Element;
|
class Element;
|
||||||
|
class ReactionsMenuManager;
|
||||||
} // namespace HistoryView
|
} // namespace HistoryView
|
||||||
|
|
||||||
namespace Window {
|
namespace Window {
|
||||||
|
@ -57,6 +59,7 @@ public:
|
||||||
not_null<Ui::ScrollArea*> scroll,
|
not_null<Ui::ScrollArea*> scroll,
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionController*> controller,
|
||||||
not_null<History*> history);
|
not_null<History*> history);
|
||||||
|
~HistoryInner();
|
||||||
|
|
||||||
[[nodiscard]] Main::Session &session() const;
|
[[nodiscard]] Main::Session &session() const;
|
||||||
[[nodiscard]] not_null<Ui::ChatTheme*> theme() const {
|
[[nodiscard]] not_null<Ui::ChatTheme*> theme() const {
|
||||||
|
@ -117,6 +120,7 @@ public:
|
||||||
void elementReplyTo(const FullMsgId &to);
|
void elementReplyTo(const FullMsgId &to);
|
||||||
void elementStartInteraction(not_null<const Element*> view);
|
void elementStartInteraction(not_null<const Element*> view);
|
||||||
void elementShowReactions(not_null<const Element*> view);
|
void elementShowReactions(not_null<const Element*> view);
|
||||||
|
const Data::Reaction *elementCornerReaction(not_null<const Element*> view);
|
||||||
|
|
||||||
void updateBotInfo(bool recount = true);
|
void updateBotInfo(bool recount = true);
|
||||||
|
|
||||||
|
@ -155,8 +159,6 @@ public:
|
||||||
// HistoryView::ElementDelegate interface.
|
// HistoryView::ElementDelegate interface.
|
||||||
static not_null<HistoryView::ElementDelegate*> ElementDelegate();
|
static not_null<HistoryView::ElementDelegate*> ElementDelegate();
|
||||||
|
|
||||||
~HistoryInner();
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool focusNextPrevChild(bool next) override;
|
bool focusNextPrevChild(bool next) override;
|
||||||
|
|
||||||
|
@ -338,10 +340,10 @@ private:
|
||||||
void deleteAsGroup(FullMsgId itemId);
|
void deleteAsGroup(FullMsgId itemId);
|
||||||
void reportItem(FullMsgId itemId);
|
void reportItem(FullMsgId itemId);
|
||||||
void reportAsGroup(FullMsgId itemId);
|
void reportAsGroup(FullMsgId itemId);
|
||||||
void reportItems(MessageIdsList ids);
|
|
||||||
void blockSenderItem(FullMsgId itemId);
|
void blockSenderItem(FullMsgId itemId);
|
||||||
void blockSenderAsGroup(FullMsgId itemId);
|
void blockSenderAsGroup(FullMsgId itemId);
|
||||||
void copySelectedText();
|
void copySelectedText();
|
||||||
|
void showReactionsMenu(FullMsgId itemId, QRect area);
|
||||||
|
|
||||||
void setupSharingDisallowed();
|
void setupSharingDisallowed();
|
||||||
[[nodiscard]] bool hasCopyRestriction(HistoryItem *item = nullptr) const;
|
[[nodiscard]] bool hasCopyRestriction(HistoryItem *item = nullptr) const;
|
||||||
|
@ -395,6 +397,9 @@ private:
|
||||||
not_null<PeerData*>,
|
not_null<PeerData*>,
|
||||||
std::shared_ptr<Data::CloudImageView>> _userpics, _userpicsCache;
|
std::shared_ptr<Data::CloudImageView>> _userpics, _userpicsCache;
|
||||||
|
|
||||||
|
std::vector<Data::Reaction> _reactions;
|
||||||
|
std::unique_ptr<HistoryView::ReactionsMenuManager> _reactionsMenus;
|
||||||
|
|
||||||
MouseAction _mouseAction = MouseAction::None;
|
MouseAction _mouseAction = MouseAction::None;
|
||||||
TextSelectType _mouseSelectType = TextSelectType::Letters;
|
TextSelectType _mouseSelectType = TextSelectType::Letters;
|
||||||
QPoint _dragStartPosition;
|
QPoint _dragStartPosition;
|
||||||
|
|
|
@ -53,6 +53,7 @@ struct TextState {
|
||||||
bool customTooltip = false;
|
bool customTooltip = false;
|
||||||
uint16 symbol = 0;
|
uint16 symbol = 0;
|
||||||
QString customTooltipText;
|
QString customTooltipText;
|
||||||
|
QRect reactionArea;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -188,6 +188,11 @@ void SimpleElementDelegate::elementShowReactions(
|
||||||
not_null<const Element*> view) {
|
not_null<const Element*> view) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Data::Reaction *SimpleElementDelegate::elementCornerReaction(
|
||||||
|
not_null<const Element*> view) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
TextSelection UnshiftItemSelection(
|
TextSelection UnshiftItemSelection(
|
||||||
TextSelection selection,
|
TextSelection selection,
|
||||||
uint16 byLength) {
|
uint16 byLength) {
|
||||||
|
|
|
@ -18,6 +18,10 @@ class HistoryMessage;
|
||||||
class HistoryService;
|
class HistoryService;
|
||||||
struct HistoryMessageReply;
|
struct HistoryMessageReply;
|
||||||
|
|
||||||
|
namespace Data {
|
||||||
|
struct Reaction;
|
||||||
|
} // namespace Data
|
||||||
|
|
||||||
namespace Window {
|
namespace Window {
|
||||||
class SessionController;
|
class SessionController;
|
||||||
} // namespace Window
|
} // namespace Window
|
||||||
|
@ -92,6 +96,8 @@ public:
|
||||||
virtual void elementReplyTo(const FullMsgId &to) = 0;
|
virtual void elementReplyTo(const FullMsgId &to) = 0;
|
||||||
virtual void elementStartInteraction(not_null<const Element*> view) = 0;
|
virtual void elementStartInteraction(not_null<const Element*> view) = 0;
|
||||||
virtual void elementShowReactions(not_null<const Element*> view) = 0;
|
virtual void elementShowReactions(not_null<const Element*> view) = 0;
|
||||||
|
virtual const Data::Reaction *elementCornerReaction(
|
||||||
|
not_null<const Element*> view) = 0;
|
||||||
|
|
||||||
virtual ~ElementDelegate() {
|
virtual ~ElementDelegate() {
|
||||||
}
|
}
|
||||||
|
@ -150,6 +156,8 @@ public:
|
||||||
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 elementShowReactions(not_null<const Element*> view) override;
|
void elementShowReactions(not_null<const Element*> view) override;
|
||||||
|
const Data::Reaction *elementCornerReaction(
|
||||||
|
not_null<const Element*> view) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
[[nodiscard]] not_null<Window::SessionController*> controller() const {
|
[[nodiscard]] not_null<Window::SessionController*> controller() const {
|
||||||
|
|
|
@ -1461,6 +1461,11 @@ void ListWidget::elementStartInteraction(not_null<const Element*> view) {
|
||||||
void ListWidget::elementShowReactions(not_null<const Element*> view) {
|
void ListWidget::elementShowReactions(not_null<const Element*> view) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Data::Reaction *ListWidget::elementCornerReaction(
|
||||||
|
not_null<const Element*> view) {
|
||||||
|
return nullptr; // #TODO reactions
|
||||||
|
}
|
||||||
|
|
||||||
void ListWidget::saveState(not_null<ListMemento*> memento) {
|
void ListWidget::saveState(not_null<ListMemento*> memento) {
|
||||||
memento->setAroundPosition(_aroundPosition);
|
memento->setAroundPosition(_aroundPosition);
|
||||||
auto state = countScrollState();
|
auto state = countScrollState();
|
||||||
|
|
|
@ -279,6 +279,8 @@ public:
|
||||||
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 elementShowReactions(not_null<const Element*> view) override;
|
void elementShowReactions(not_null<const Element*> view) override;
|
||||||
|
const Data::Reaction *elementCornerReaction(
|
||||||
|
not_null<const Element*> view) override;
|
||||||
|
|
||||||
void setEmptyInfoWidget(base::unique_qptr<Ui::RpWidget> &&w);
|
void setEmptyInfoWidget(base::unique_qptr<Ui::RpWidget> &&w);
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
|
#include "data/data_message_reactions.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
|
@ -604,6 +605,28 @@ void Message::draw(Painter &p, const PaintContext &context) const {
|
||||||
p.translate(-reactionsPosition);
|
p.translate(-reactionsPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (const auto reaction = delegate()->elementCornerReaction(this)) {
|
||||||
|
if (!_react) {
|
||||||
|
_react = std::make_unique<ReactButton>([=] {
|
||||||
|
history()->owner().requestViewRepaint(this);
|
||||||
|
}, [=] {
|
||||||
|
if (const auto reaction
|
||||||
|
= delegate()->elementCornerReaction(this)) {
|
||||||
|
data()->addReaction(reaction->emoji);
|
||||||
|
}
|
||||||
|
}, g);
|
||||||
|
_react->toggle(true);
|
||||||
|
} else {
|
||||||
|
_react->updateGeometry(g);
|
||||||
|
}
|
||||||
|
_react->show(reaction);
|
||||||
|
} else if (_react) {
|
||||||
|
_react->toggle(false);
|
||||||
|
if (_react->isHidden()) {
|
||||||
|
_react = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (bubble) {
|
if (bubble) {
|
||||||
if (displayFromName()
|
if (displayFromName()
|
||||||
&& item->displayFrom()
|
&& item->displayFrom()
|
||||||
|
@ -744,6 +767,10 @@ void Message::draw(Painter &p, const PaintContext &context) const {
|
||||||
drawRightAction(p, context, fastShareLeft, fastShareTop, width());
|
drawRightAction(p, context, fastShareLeft, fastShareTop, width());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_react) {
|
||||||
|
_react->paint(p, context);
|
||||||
|
}
|
||||||
|
|
||||||
if (media) {
|
if (media) {
|
||||||
media->paintBubbleFireworks(p, g, context.now);
|
media->paintBubbleFireworks(p, g, context.now);
|
||||||
}
|
}
|
||||||
|
@ -1070,6 +1097,12 @@ PointState Message::pointState(QPoint point) const {
|
||||||
return PointState::Outside;
|
return PointState::Outside;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_react) {
|
||||||
|
if (const auto state = _react->pointState(point)) {
|
||||||
|
return *state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const auto media = this->media();
|
const auto media = this->media();
|
||||||
const auto item = message();
|
const auto item = message();
|
||||||
const auto reactionsInBubble = _reactions && needInfoDisplay();
|
const auto reactionsInBubble = _reactions && needInfoDisplay();
|
||||||
|
@ -1246,6 +1279,14 @@ TextState Message::textState(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_react) {
|
||||||
|
if (const auto state = _react->textState(point, request)) {
|
||||||
|
result.link = state->link;
|
||||||
|
result.reactionArea = state->reactionArea;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const auto reactionsInBubble = _reactions && needInfoDisplay();
|
const auto reactionsInBubble = _reactions && needInfoDisplay();
|
||||||
auto keyboard = item->inlineReplyKeyboard();
|
auto keyboard = item->inlineReplyKeyboard();
|
||||||
auto keyboardHeight = 0;
|
auto keyboardHeight = 0;
|
||||||
|
@ -1923,9 +1964,11 @@ void Message::itemDataChanged() {
|
||||||
auto Message::verticalRepaintRange() const -> VerticalRepaintRange {
|
auto Message::verticalRepaintRange() const -> VerticalRepaintRange {
|
||||||
const auto media = this->media();
|
const auto media = this->media();
|
||||||
const auto add = media ? media->bubbleRollRepaintMargins() : QMargins();
|
const auto add = media ? media->bubbleRollRepaintMargins() : QMargins();
|
||||||
|
const auto addBottom = add.bottom()
|
||||||
|
+ (_react ? std::max(_react->bottomOutsideMargin(height()), 0) : 0);
|
||||||
return {
|
return {
|
||||||
.top = -add.top(),
|
.top = -add.top(),
|
||||||
.height = height() + add.top() + add.bottom()
|
.height = height() + add.top() + addBottom
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2669,6 +2712,7 @@ int Message::resizeContentGetHeight(int newWidth) {
|
||||||
if (_reactions && !reactionsInBubble) {
|
if (_reactions && !reactionsInBubble) {
|
||||||
newHeight += st::mediaInBubbleSkip + _reactions->resizeGetHeight(contentWidth);
|
newHeight += st::mediaInBubbleSkip + _reactions->resizeGetHeight(contentWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (const auto keyboard = item->inlineReplyKeyboard()) {
|
if (const auto keyboard = item->inlineReplyKeyboard()) {
|
||||||
const auto keyboardHeight = st::msgBotKbButton.margin + keyboard->naturalHeight();
|
const auto keyboardHeight = st::msgBotKbButton.margin + keyboard->naturalHeight();
|
||||||
newHeight += keyboardHeight;
|
newHeight += keyboardHeight;
|
||||||
|
|
|
@ -19,6 +19,7 @@ struct HistoryMessageForwarded;
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
|
||||||
class ViewButton;
|
class ViewButton;
|
||||||
|
class ReactButton;
|
||||||
class Reactions;
|
class Reactions;
|
||||||
class WebPage;
|
class WebPage;
|
||||||
|
|
||||||
|
@ -233,6 +234,7 @@ private:
|
||||||
mutable ClickHandlerPtr _rightActionLink;
|
mutable ClickHandlerPtr _rightActionLink;
|
||||||
mutable ClickHandlerPtr _fastReplyLink;
|
mutable ClickHandlerPtr _fastReplyLink;
|
||||||
mutable std::unique_ptr<ViewButton> _viewButton;
|
mutable std::unique_ptr<ViewButton> _viewButton;
|
||||||
|
mutable std::unique_ptr<ReactButton> _react;
|
||||||
std::unique_ptr<Reactions> _reactions;
|
std::unique_ptr<Reactions> _reactions;
|
||||||
mutable std::unique_ptr<CommentsButton> _comments;
|
mutable std::unique_ptr<CommentsButton> _comments;
|
||||||
|
|
||||||
|
|
|
@ -8,11 +8,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/view/history_view_reactions.h"
|
#include "history/view/history_view_reactions.h"
|
||||||
|
|
||||||
#include "history/view/history_view_message.h"
|
#include "history/view/history_view_message.h"
|
||||||
|
#include "history/view/history_view_cursor_state.h"
|
||||||
#include "history/history_message.h"
|
#include "history/history_message.h"
|
||||||
|
#include "ui/chat/chat_style.h"
|
||||||
#include "ui/text/text_options.h"
|
#include "ui/text/text_options.h"
|
||||||
#include "ui/text/text_utilities.h"
|
#include "ui/text/text_utilities.h"
|
||||||
|
#include "data/data_message_reactions.h"
|
||||||
|
#include "data/data_document.h"
|
||||||
|
#include "data/data_document_media.h"
|
||||||
|
#include "core/click_handler_types.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/palette.h"
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kItemsPerRow = 5;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
Reactions::Reactions(Data &&data)
|
Reactions::Reactions(Data &&data)
|
||||||
: _data(std::move(data))
|
: _data(std::move(data))
|
||||||
|
@ -101,4 +115,313 @@ Reactions::Data ReactionsDataFromMessage(not_null<Message*> message) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ReactButton::ReactButton(
|
||||||
|
Fn<void()> update,
|
||||||
|
Fn<void()> react,
|
||||||
|
QRect bubble)
|
||||||
|
: _update(std::move(update))
|
||||||
|
, _handler(std::make_shared<LambdaClickHandler>(react)) {
|
||||||
|
updateGeometry(bubble);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReactButton::updateGeometry(QRect bubble) {
|
||||||
|
const auto topLeft = bubble.topLeft()
|
||||||
|
+ QPoint(bubble.width(), bubble.height())
|
||||||
|
+ QPoint(st::reactionCornerOut.x(), st::reactionCornerOut.y())
|
||||||
|
- QPoint(
|
||||||
|
st::reactionCornerSize.width(),
|
||||||
|
st::reactionCornerSize.height());
|
||||||
|
_geometry = QRect(topLeft, st::reactionCornerSize);
|
||||||
|
_imagePosition = _geometry.topLeft() + QPoint(
|
||||||
|
(_geometry.width() - st::reactionCornerImage) / 2,
|
||||||
|
(_geometry.height() - st::reactionCornerImage) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ReactButton::bottomOutsideMargin(int fullHeight) const {
|
||||||
|
return _geometry.y() + _geometry.height() - fullHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<PointState> ReactButton::pointState(QPoint point) const {
|
||||||
|
if (!_geometry.contains(point)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return PointState::Inside;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<TextState> ReactButton::textState(
|
||||||
|
QPoint point,
|
||||||
|
const StateRequest &request) const {
|
||||||
|
if (!_geometry.contains(point)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
auto result = TextState(nullptr, _handler);
|
||||||
|
result.reactionArea = _geometry;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReactButton::paint(Painter &p, const PaintContext &context) {
|
||||||
|
const auto shown = _shownAnimation.value(_shown ? 1. : 0.);
|
||||||
|
if (shown == 0.) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
p.setOpacity(shown);
|
||||||
|
p.setBrush(context.messageStyle()->msgBg);
|
||||||
|
p.setPen(st::shadowFg);
|
||||||
|
const auto radius = _geometry.height() / 2;
|
||||||
|
p.drawRoundedRect(_geometry, radius, radius);
|
||||||
|
if (!_image.isNull()) {
|
||||||
|
p.drawImage(_imagePosition, _image);
|
||||||
|
}
|
||||||
|
p.setOpacity(1.);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReactButton::toggle(bool shown) {
|
||||||
|
if (_shown == shown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_shown = shown;
|
||||||
|
_shownAnimation.start(_update, _shown ? 0. : 1., _shown ? 1. : 0., 120);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ReactButton::isHidden() const {
|
||||||
|
return !_shown && !_shownAnimation.animating();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReactButton::show(not_null<const Data::Reaction*> reaction) {
|
||||||
|
if (_media && _media->owner() == reaction->staticIcon) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_handler->setProperty(kReactionIdProperty, reaction->emoji);
|
||||||
|
_media = reaction->staticIcon->createMediaView();
|
||||||
|
const auto setImage = [=](not_null<Image*> image) {
|
||||||
|
const auto size = st::reactionCornerImage;
|
||||||
|
_image = Images::prepare(
|
||||||
|
image->original(),
|
||||||
|
size * style::DevicePixelRatio(),
|
||||||
|
size * style::DevicePixelRatio(),
|
||||||
|
Images::Option::Smooth | Images::Option::TransparentBackground,
|
||||||
|
size,
|
||||||
|
size);
|
||||||
|
_image.setDevicePixelRatio(style::DevicePixelRatio());
|
||||||
|
};
|
||||||
|
if (const auto image = _media->getStickerLarge()) {
|
||||||
|
setImage(image);
|
||||||
|
} else {
|
||||||
|
reaction->staticIcon->session().downloaderTaskFinished(
|
||||||
|
) | rpl::map([=] {
|
||||||
|
return _media->getStickerLarge();
|
||||||
|
}) | rpl::filter_nullptr() | rpl::take(
|
||||||
|
1
|
||||||
|
) | rpl::start_with_next([=](not_null<Image*> image) {
|
||||||
|
setImage(image);
|
||||||
|
_update();
|
||||||
|
}, _downloadTaskLifetime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactionsMenu::ReactionsMenu(
|
||||||
|
QWidget *parent,
|
||||||
|
const std::vector<Data::Reaction> &list)
|
||||||
|
: _dropdown(parent) {
|
||||||
|
_dropdown.setAutoHiding(false);
|
||||||
|
|
||||||
|
const auto content = _dropdown.setOwnedWidget(
|
||||||
|
object_ptr<Ui::RpWidget>(&_dropdown));
|
||||||
|
|
||||||
|
const auto count = int(list.size());
|
||||||
|
const auto single = st::reactionPopupImage;
|
||||||
|
const auto padding = st::reactionPopupPadding;
|
||||||
|
const auto width = padding.left() + single + padding.right();
|
||||||
|
const auto height = padding.top() + single + padding.bottom();
|
||||||
|
const auto rows = (count + kItemsPerRow - 1) / kItemsPerRow;
|
||||||
|
const auto columns = (int(list.size()) + rows - 1) / rows;
|
||||||
|
const auto inner = QRect(0, 0, columns * width, rows * height);
|
||||||
|
const auto outer = inner.marginsAdded(padding);
|
||||||
|
content->resize(outer.size());
|
||||||
|
|
||||||
|
_elements.reserve(list.size());
|
||||||
|
auto x = padding.left();
|
||||||
|
auto y = padding.top();
|
||||||
|
auto row = -1;
|
||||||
|
auto perrow = 0;
|
||||||
|
while (_elements.size() != list.size()) {
|
||||||
|
if (!perrow) {
|
||||||
|
++row;
|
||||||
|
perrow = (list.size() - _elements.size()) / (rows - row);
|
||||||
|
x = (outer.width() - perrow * width) / 2;
|
||||||
|
}
|
||||||
|
auto &reaction = list[_elements.size()];
|
||||||
|
_elements.push_back({
|
||||||
|
.emoji = reaction.emoji,
|
||||||
|
.geometry = QRect(x, y + row * height, width, height),
|
||||||
|
});
|
||||||
|
x += width;
|
||||||
|
--perrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
int selected = -1;
|
||||||
|
int pressed = -1;
|
||||||
|
};
|
||||||
|
const auto state = content->lifetime().make_state<State>();
|
||||||
|
content->setMouseTracking(true);
|
||||||
|
content->events(
|
||||||
|
) | rpl::start_with_next([=](not_null<QEvent*> e) {
|
||||||
|
const auto type = e->type();
|
||||||
|
if (type == QEvent::MouseMove) {
|
||||||
|
const auto position = static_cast<QMouseEvent*>(e.get())->pos();
|
||||||
|
const auto i = ranges::find_if(_elements, [&](const Element &e) {
|
||||||
|
return e.geometry.contains(position);
|
||||||
|
});
|
||||||
|
const auto selected = (i != end(_elements))
|
||||||
|
? int(i - begin(_elements))
|
||||||
|
: -1;
|
||||||
|
if (state->selected != selected) {
|
||||||
|
state->selected = selected;
|
||||||
|
content->update();
|
||||||
|
}
|
||||||
|
} else if (type == QEvent::MouseButtonPress) {
|
||||||
|
state->pressed = state->selected;
|
||||||
|
content->update();
|
||||||
|
} else if (type == QEvent::MouseButtonRelease) {
|
||||||
|
const auto pressed = std::exchange(state->pressed, -1);
|
||||||
|
if (pressed >= 0) {
|
||||||
|
content->update();
|
||||||
|
if (pressed == state->selected) {
|
||||||
|
_chosen.fire_copy(_elements[pressed].emoji);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, content->lifetime());
|
||||||
|
|
||||||
|
content->paintRequest(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
auto p = QPainter(content);
|
||||||
|
const auto radius = st::roundRadiusSmall;
|
||||||
|
{
|
||||||
|
auto hq = PainterHighQualityEnabler(p);
|
||||||
|
p.setBrush(st::emojiPanBg);
|
||||||
|
p.setPen(Qt::NoPen);
|
||||||
|
p.drawRoundedRect(content->rect(), radius, radius);
|
||||||
|
}
|
||||||
|
auto index = 0;
|
||||||
|
const auto activeIndex = (state->pressed >= 0)
|
||||||
|
? state->pressed
|
||||||
|
: state->selected;
|
||||||
|
const auto size = Ui::Emoji::GetSizeNormal();
|
||||||
|
for (const auto &element : _elements) {
|
||||||
|
const auto active = (index++ == activeIndex);
|
||||||
|
if (active) {
|
||||||
|
auto hq = PainterHighQualityEnabler(p);
|
||||||
|
p.setBrush(st::windowBgOver);
|
||||||
|
p.setPen(Qt::NoPen);
|
||||||
|
p.drawRoundedRect(element.geometry, radius, radius);
|
||||||
|
}
|
||||||
|
if (const auto emoji = Ui::Emoji::Find(element.emoji)) {
|
||||||
|
Ui::Emoji::Draw(
|
||||||
|
p,
|
||||||
|
emoji,
|
||||||
|
size,
|
||||||
|
element.geometry.x() + (width - size) / 2,
|
||||||
|
element.geometry.y() + (height - size) / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, content->lifetime());
|
||||||
|
|
||||||
|
_dropdown.resizeToContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReactionsMenu::showAround(QRect area) {
|
||||||
|
const auto parent = _dropdown.parentWidget();
|
||||||
|
const auto left = std::min(
|
||||||
|
std::max(area.x() + (area.width() - _dropdown.width()) / 2, 0),
|
||||||
|
parent->width() - _dropdown.width());
|
||||||
|
_fromTop = (area.y() >= _dropdown.height());
|
||||||
|
_fromLeft = (area.center().x() - left
|
||||||
|
<= left + _dropdown.width() - area.center().x());
|
||||||
|
const auto top = _fromTop
|
||||||
|
? (area.y() - _dropdown.height())
|
||||||
|
: (area.y() + area.height());
|
||||||
|
_dropdown.move(left, top);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReactionsMenu::toggle(bool shown, anim::type animated) {
|
||||||
|
if (animated == anim::type::normal) {
|
||||||
|
if (shown) {
|
||||||
|
using Origin = Ui::PanelAnimation::Origin;
|
||||||
|
_dropdown.showAnimated(_fromTop
|
||||||
|
? (_fromLeft ? Origin::BottomLeft : Origin::BottomRight)
|
||||||
|
: (_fromLeft ? Origin::TopLeft : Origin::TopRight));
|
||||||
|
} else {
|
||||||
|
_dropdown.hideAnimated();
|
||||||
|
}
|
||||||
|
} else if (shown) {
|
||||||
|
_dropdown.showFast();
|
||||||
|
} else {
|
||||||
|
_dropdown.hideFast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<QString> ReactionsMenu::chosen() const {
|
||||||
|
return _chosen.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::lifetime &ReactionsMenu::lifetime() {
|
||||||
|
return _dropdown.lifetime();
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactionsMenuManager::ReactionsMenuManager(QWidget *parent)
|
||||||
|
: _parent(parent) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactionsMenuManager::~ReactionsMenuManager() = default;
|
||||||
|
|
||||||
|
void ReactionsMenuManager::showReactionsMenu(
|
||||||
|
FullMsgId context,
|
||||||
|
QRect globalReactionArea,
|
||||||
|
const std::vector<Data::Reaction> &list) {
|
||||||
|
if (globalReactionArea.isEmpty()) {
|
||||||
|
context = FullMsgId();
|
||||||
|
}
|
||||||
|
const auto listsEqual = ranges::equal(
|
||||||
|
_list,
|
||||||
|
list,
|
||||||
|
ranges::equal_to(),
|
||||||
|
&Data::Reaction::emoji,
|
||||||
|
&Data::Reaction::emoji);
|
||||||
|
const auto changed = (_context != context || !listsEqual);
|
||||||
|
if (_menu && changed) {
|
||||||
|
_menu->toggle(false, anim::type::normal);
|
||||||
|
_hiding.push_back(std::move(_menu));
|
||||||
|
}
|
||||||
|
_context = context;
|
||||||
|
_list = list;
|
||||||
|
if (list.size() < 2 || !context || (!changed && !_menu)) {
|
||||||
|
return;
|
||||||
|
} else if (!_menu) {
|
||||||
|
_menu = std::make_unique<ReactionsMenu>(_parent, list);
|
||||||
|
_menu->chosen(
|
||||||
|
) | rpl::start_with_next([=](QString emoji) {
|
||||||
|
_menu->toggle(false, anim::type::normal);
|
||||||
|
_hiding.push_back(std::move(_menu));
|
||||||
|
_chosen.fire({ context, std::move(emoji) });
|
||||||
|
}, _menu->lifetime());
|
||||||
|
}
|
||||||
|
const auto area = QRect(
|
||||||
|
_parent->mapFromGlobal(globalReactionArea.topLeft()),
|
||||||
|
globalReactionArea.size());
|
||||||
|
_menu->showAround(area);
|
||||||
|
_menu->toggle(true, anim::type::normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReactionsMenuManager::hideAll(anim::type animated) {
|
||||||
|
if (animated == anim::type::instant) {
|
||||||
|
_hiding.clear();
|
||||||
|
_menu = nullptr;
|
||||||
|
} else if (_menu) {
|
||||||
|
_menu->toggle(false, anim::type::normal);
|
||||||
|
_hiding.push_back(std::move(_menu));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace HistoryView
|
} // namespace HistoryView
|
||||||
|
|
|
@ -8,13 +8,27 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "history/view/history_view_object.h"
|
#include "history/view/history_view_object.h"
|
||||||
|
#include "ui/effects/animations.h"
|
||||||
|
#include "ui/widgets/inner_dropdown.h"
|
||||||
|
|
||||||
|
class Image;
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class ChatStyle;
|
class ChatStyle;
|
||||||
|
struct ChatPaintContext;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
|
namespace Data {
|
||||||
|
struct Reaction;
|
||||||
|
class DocumentMedia;
|
||||||
|
} // namespace Data
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
|
||||||
|
using PaintContext = Ui::ChatPaintContext;
|
||||||
|
enum class PointState : char;
|
||||||
|
struct TextState;
|
||||||
|
struct StateRequest;
|
||||||
class Message;
|
class Message;
|
||||||
|
|
||||||
class Reactions final : public Object {
|
class Reactions final : public Object {
|
||||||
|
@ -52,4 +66,92 @@ private:
|
||||||
[[nodiscard]] Reactions::Data ReactionsDataFromMessage(
|
[[nodiscard]] Reactions::Data ReactionsDataFromMessage(
|
||||||
not_null<Message*> message);
|
not_null<Message*> message);
|
||||||
|
|
||||||
|
class ReactButton final {
|
||||||
|
public:
|
||||||
|
ReactButton(Fn<void()> update, Fn<void()> react, QRect bubble);
|
||||||
|
|
||||||
|
void updateGeometry(QRect bubble);
|
||||||
|
[[nodiscard]] int bottomOutsideMargin(int fullHeight) const;
|
||||||
|
[[nodiscard]] std::optional<PointState> pointState(QPoint point) const;
|
||||||
|
[[nodiscard]] std::optional<TextState> textState(
|
||||||
|
QPoint point,
|
||||||
|
const StateRequest &request) const;
|
||||||
|
|
||||||
|
void paint(Painter &p, const PaintContext &context);
|
||||||
|
|
||||||
|
void toggle(bool shown);
|
||||||
|
[[nodiscard]] bool isHidden() const;
|
||||||
|
void show(not_null<const Data::Reaction*> reaction);
|
||||||
|
|
||||||
|
private:
|
||||||
|
const Fn<void()> _update;
|
||||||
|
const ClickHandlerPtr _handler;
|
||||||
|
QRect _geometry;
|
||||||
|
bool _shown = false;
|
||||||
|
Ui::Animations::Simple _shownAnimation;
|
||||||
|
|
||||||
|
QImage _image;
|
||||||
|
QPoint _imagePosition;
|
||||||
|
std::shared_ptr<Data::DocumentMedia> _media;
|
||||||
|
rpl::lifetime _downloadTaskLifetime;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class ReactionsMenu final {
|
||||||
|
public:
|
||||||
|
ReactionsMenu(
|
||||||
|
QWidget *parent,
|
||||||
|
const std::vector<Data::Reaction> &list);
|
||||||
|
|
||||||
|
void showAround(QRect area);
|
||||||
|
void toggle(bool shown, anim::type animated);
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<QString> chosen() const;
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::lifetime &lifetime();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Element {
|
||||||
|
QString emoji;
|
||||||
|
QRect geometry;
|
||||||
|
};
|
||||||
|
Ui::InnerDropdown _dropdown;
|
||||||
|
rpl::event_stream<QString> _chosen;
|
||||||
|
std::vector<Element> _elements;
|
||||||
|
bool _fromTop = true;
|
||||||
|
bool _fromLeft = true;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class ReactionsMenuManager final {
|
||||||
|
public:
|
||||||
|
explicit ReactionsMenuManager(QWidget *parent);
|
||||||
|
~ReactionsMenuManager();
|
||||||
|
|
||||||
|
struct Chosen {
|
||||||
|
FullMsgId context;
|
||||||
|
QString emoji;
|
||||||
|
};
|
||||||
|
|
||||||
|
void showReactionsMenu(
|
||||||
|
FullMsgId context,
|
||||||
|
QRect globalReactionArea,
|
||||||
|
const std::vector<Data::Reaction> &list);
|
||||||
|
void hideAll(anim::type animated);
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<Chosen> chosen() const {
|
||||||
|
return _chosen.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
QWidget *_parent = nullptr;
|
||||||
|
rpl::event_stream<Chosen> _chosen;
|
||||||
|
|
||||||
|
std::unique_ptr<ReactionsMenu> _menu;
|
||||||
|
FullMsgId _context;
|
||||||
|
std::vector<Data::Reaction> _list;
|
||||||
|
std::vector<std::unique_ptr<ReactionsMenu>> _hiding;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace HistoryView
|
} // namespace HistoryView
|
||||||
|
|
|
@ -962,7 +962,7 @@ void ListWidget::repaintItem(QRect itemGeometry) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ListWidget::isMyItem(not_null<const HistoryItem*> item) const {
|
bool ListWidget::isMyItem(not_null<const HistoryItem*> item) const {
|
||||||
auto peer = item->history()->peer;
|
const auto peer = item->history()->peer;
|
||||||
return (_peer == peer) || (_migrated == peer);
|
return (_peer == peer) || (_migrated == peer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -954,3 +954,10 @@ sendAsButton: SendAsButton {
|
||||||
}
|
}
|
||||||
duration: 150;
|
duration: 150;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reactionCornerSize: size(23px, 18px);
|
||||||
|
reactionCornerOut: point(7px, 5px);
|
||||||
|
reactionCornerImage: 14px;
|
||||||
|
|
||||||
|
reactionPopupImage: 25px;
|
||||||
|
reactionPopupPadding: margins(5px, 5px, 5px, 5px);
|
||||||
|
|
|
@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "history/view/history_view_replies_section.h"
|
#include "history/view/history_view_replies_section.h"
|
||||||
|
#include "history/view/history_view_reactions.h"
|
||||||
#include "media/player/media_player_instance.h"
|
#include "media/player/media_player_instance.h"
|
||||||
#include "media/view/media_view_open_common.h"
|
#include "media/view/media_view_open_common.h"
|
||||||
#include "data/data_document_resolver.h"
|
#include "data/data_document_resolver.h"
|
||||||
|
|
|
@ -55,8 +55,13 @@ struct ChatThemeBackgroundData;
|
||||||
namespace Data {
|
namespace Data {
|
||||||
struct CloudTheme;
|
struct CloudTheme;
|
||||||
enum class CloudThemeType;
|
enum class CloudThemeType;
|
||||||
|
struct Reaction;
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
|
namespace HistoryView {
|
||||||
|
class ReactionsMenu;
|
||||||
|
} // namespace HistoryView
|
||||||
|
|
||||||
namespace Window {
|
namespace Window {
|
||||||
|
|
||||||
class MainWindow;
|
class MainWindow;
|
||||||
|
|
Loading…
Add table
Reference in a new issue