diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 5d3c36df1..9ba02bf5a 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -1085,7 +1085,10 @@ void AddWhoReactedAction( strong->hideMenu(); } if (const auto item = controller->session().data().message(itemId)) { - controller->window().show(ReactionsListBox(controller, item)); + controller->window().show(ReactionsListBox( + controller, + item, + QString())); } }; if (!menu->empty()) { diff --git a/Telegram/SourceFiles/history/view/reactions/message_reactions_list.cpp b/Telegram/SourceFiles/history/view/reactions/message_reactions_list.cpp index 02019ce3a..d3c5e9f57 100644 --- a/Telegram/SourceFiles/history/view/reactions/message_reactions_list.cpp +++ b/Telegram/SourceFiles/history/view/reactions/message_reactions_list.cpp @@ -28,6 +28,7 @@ public: Controller( not_null window, not_null item, + const QString &selected, rpl::producer switches); Main::Session &session() const override; @@ -64,10 +65,12 @@ private: Controller::Controller( not_null window, not_null item, + const QString &selected, rpl::producer switches) : _window(window) , _item(item) -, _api(&window->session().mtp()) { +, _api(&window->session().mtp()) +, _shownReaction(selected) { std::move( switches ) | rpl::filter([=](const QString &reaction) { @@ -189,42 +192,113 @@ bool Controller::appendRow(not_null user, QString reaction) { return true; } +class Row final : public PeerListRow { +public: + Row(not_null peer, const QString &reaction); + + QSize rightActionSize() const override; + QMargins rightActionMargins() const override; + bool rightActionDisabled() const override; + void rightActionPaint( + Painter &p, + int x, + int y, + int outerWidth, + bool selected, + bool actionSelected) override; + +private: + EmojiPtr _emoji = nullptr; + +}; + +Row::Row(not_null peer, const QString &reaction) +: PeerListRow(peer) +, _emoji(Ui::Emoji::Find(reaction)) { +} + +QSize Row::rightActionSize() const { + const auto size = Ui::Emoji::GetSizeNormal() / style::DevicePixelRatio(); + return _emoji ? QSize(size, size) : QSize(); +} + +QMargins Row::rightActionMargins() const { + if (!_emoji) { + return QMargins(); + } + const auto size = Ui::Emoji::GetSizeNormal() / style::DevicePixelRatio(); + return QMargins( + size / 2, + (st::defaultPeerList.item.height - size) / 2, + (size * 3) / 2, + 0); +} + +bool Row::rightActionDisabled() const { + return true; +} + +void Row::rightActionPaint( + Painter &p, + int x, + int y, + int outerWidth, + bool selected, + bool actionSelected) { + if (!_emoji) { + return; + } + // #TODO reactions + Ui::Emoji::Draw(p, _emoji, Ui::Emoji::GetSizeNormal(), x, y); +} + std::unique_ptr Controller::createRow( not_null user, QString reaction) const { - auto result = std::make_unique(user); - if (!reaction.isEmpty()) { - result->setCustomStatus(reaction); - } - return result; + return std::make_unique(user, reaction); } } // namespace object_ptr ReactionsListBox( - not_null window, - not_null item) { + not_null window, + not_null item, + QString selected) { Expects(IsServerMsgId(item->id)); + if (!item->reactions().contains(selected)) { + selected = QString(); + } const auto tabRequests = std::make_shared>(); const auto initBox = [=](not_null box) { box->setNoContentMargin(true); - const auto selector = CreateReactionSelector(box, item->reactions()); + const auto selector = CreateReactionSelector( + box, + item->reactions(), + selected); + selector->changes( + ) | rpl::start_to_stream(*tabRequests, box->lifetime()); + box->widthValue( ) | rpl::start_with_next([=](int width) { selector->resizeToWidth(width); selector->move(0, 0); }, box->lifetime()); - selector->changes( - ) | rpl::start_to_stream(*tabRequests, box->lifetime()); - box->setAddedTopScrollSkip(selector->height()); + selector->heightValue( + ) | rpl::start_with_next([=](int height) { + box->setAddedTopScrollSkip(height); + }, box->lifetime()); box->addButton(tr::lng_close(), [=] { box->closeBox(); }); }; return Box( - std::make_unique(window, item, tabRequests->events()), + std::make_unique( + window, + item, + selected, + tabRequests->events()), initBox); } diff --git a/Telegram/SourceFiles/history/view/reactions/message_reactions_list.h b/Telegram/SourceFiles/history/view/reactions/message_reactions_list.h index ad61713ff..8782aff4b 100644 --- a/Telegram/SourceFiles/history/view/reactions/message_reactions_list.h +++ b/Telegram/SourceFiles/history/view/reactions/message_reactions_list.h @@ -23,6 +23,7 @@ namespace HistoryView { object_ptr ReactionsListBox( not_null window, - not_null item); + not_null item, + QString selected); } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/reactions/message_reactions_selector.cpp b/Telegram/SourceFiles/history/view/reactions/message_reactions_selector.cpp index 1c4bb6a63..a8c795038 100644 --- a/Telegram/SourceFiles/history/view/reactions/message_reactions_selector.cpp +++ b/Telegram/SourceFiles/history/view/reactions/message_reactions_selector.cpp @@ -7,15 +7,95 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/reactions/message_reactions_selector.h" -#include "ui/widgets/discrete_sliders.h" -#include "styles/style_layers.h" +#include "ui/rp_widget.h" +#include "ui/abstract_button.h" +#include "styles/style_widgets.h" +#include "styles/style_chat.h" namespace HistoryView { +namespace { + +not_null CreateTab( + not_null parent, + const style::MultiSelect &st, + const QString &reaction, + int count, + rpl::producer selected) { + struct State { + bool selected = false; + QImage cache; + }; + const auto stm = &st.item; + const auto text = QString("%L1").arg(count); + const auto font = st::semiboldFont; + const auto textWidth = font->width(text); + const auto result = Ui::CreateChild(parent.get()); + const auto width = stm->height + + stm->padding.left() + + textWidth + + stm->padding.right(); + result->resize(width, stm->height); + const auto state = result->lifetime().make_state(); + std::move( + selected + ) | rpl::start_with_next([=](bool selected) { + state->selected = selected; + state->cache = QImage(); + result->update(); + }, result->lifetime()); + + result->paintRequest( + ) | rpl::start_with_next([=] { + if (state->cache.isNull()) { + const auto factor = style::DevicePixelRatio(); + state->cache = QImage( + result->size() * factor, + QImage::Format_ARGB32_Premultiplied); + state->cache.setDevicePixelRatio(factor); + state->cache.fill(Qt::transparent); + auto p = QPainter(&state->cache); + + const auto height = stm->height; + const auto radius = height / 2; + p.setPen(Qt::NoPen); + p.setBrush(state->selected ? stm->textActiveBg : stm->textBg); + { + PainterHighQualityEnabler hq(p); + p.drawRoundedRect(result->rect(), radius, radius); + } + const auto skip = st::reactionsTabIconSkip; + const auto icon = QRect(skip, 0, height, height); + if (const auto emoji = Ui::Emoji::Find(reaction)) { + // #TODO reactions + const auto size = Ui::Emoji::GetSizeNormal(); + const auto shift = (height - (size / factor)) / 2; + Ui::Emoji::Draw(p, emoji, size, icon.x() + shift, shift); + } else { + (state->selected + ? st::reactionsTabAllSelected + : st::reactionsTabAll).paintInCenter(p, icon); + } + + const auto textLeft = height + stm->padding.left(); + p.setPen(state->selected ? stm->textActiveFg : stm->textFg); + p.setFont(font); + p.drawText(textLeft, stm->padding.top() + font->ascent, text); + } + QPainter(result).drawImage(0, 0, state->cache); + }, result->lifetime()); + return result; +} + +} // namespace not_null CreateReactionSelector( not_null parent, - const base::flat_map &items) { - const auto sectionsCount = int(items.size() + 1); + const base::flat_map &items, + const QString &selected) { + struct State { + rpl::variable selected; + std::vector> tabs; + }; const auto result = Ui::CreateChild(parent.get()); using Entry = std::pair; auto sorted = std::vector(); @@ -28,30 +108,57 @@ not_null CreateReactionSelector( 0, std::plus<>(), &Entry::first); - auto labels = QStringList() << ("ALL (" + QString::number(count) + ")"); + auto tabs = Ui::CreateChild(parent.get()); + const auto st = &st::reactionsTabs; + const auto state = tabs->lifetime().make_state(); + state->selected = selected; + const auto append = [&](const QString &reaction, int count) { + using namespace rpl::mappers; + const auto tab = CreateTab( + tabs, + *st, + reaction, + count, + state->selected.value() | rpl::map(_1 == reaction)); + tab->setClickedCallback([=] { + state->selected = reaction; + }); + state->tabs.push_back(tab); + }; + append(QString(), count); for (const auto &[count, reaction] : sorted) { - labels.append(reaction + " (" + QString::number(count) + ")"); + append(reaction, count); } - auto tabs = Ui::CreateChild( - parent.get(), - st::defaultTabsSlider); - tabs->setSections(labels | ranges::to_vector); - tabs->setRippleTopRoundRadius(st::boxRadius); result->move = [=](int x, int y) { tabs->moveToLeft(x, y); }; result->resizeToWidth = [=](int width) { - tabs->resizeToWidth(std::min( - width, - sectionsCount * st::defaultTabsSlider.height * 2)); + const auto available = width + - st->padding.left() + - st->padding.right(); + if (available <= 0) { + return; + } + auto left = available; + auto height = st->padding.top(); + for (const auto &tab : state->tabs) { + if (left > 0 && available - left < tab->width()) { + left = 0; + height += tab->height() + st->itemSkip; + } + tab->move( + st->padding.left() + left, + height - tab->height() - st->itemSkip); + left += tab->width() + st->itemSkip; + } + tabs->resize(width, height - st->itemSkip + st->padding.bottom()); }; - result->height = [=] { - return tabs->height() - st::lineWidth; + result->heightValue = [=] { + using namespace rpl::mappers; + return tabs->heightValue() | rpl::map(_1 - st::lineWidth); }; result->changes = [=] { - return tabs->sectionActivated() | rpl::map([=](int section) { - return (section > 0) ? sorted[section - 1].second : QString(); - }); + return state->selected.changes(); }; return result; } diff --git a/Telegram/SourceFiles/history/view/reactions/message_reactions_selector.h b/Telegram/SourceFiles/history/view/reactions/message_reactions_selector.h index 8906962d2..0961a6be1 100644 --- a/Telegram/SourceFiles/history/view/reactions/message_reactions_selector.h +++ b/Telegram/SourceFiles/history/view/reactions/message_reactions_selector.h @@ -13,11 +13,12 @@ struct Selector { Fn move; Fn resizeToWidth; Fn()> changes; - Fn height; + Fn()> heightValue; }; not_null CreateReactionSelector( not_null parent, - const base::flat_map &items); + const base::flat_map &items, + const QString &selected); } // namespace HistoryView diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 576469881..11782e7c0 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -921,6 +921,12 @@ whoReadPlayed: icon{{ "menu/read_audio", menuSubmenuArrowFg }}; whoReadPlayedOver: icon{{ "menu/read_audio", menuSubmenuArrowFg }}; whoReadPlayedDisabled: icon {{ "menu/read_audio", menuFgDisabled }}; +reactionsTabAll: icon {{ "menu/read_reactions", windowFg }}; +reactionsTabAllSelected: icon {{ "menu/read_reactions", activeButtonFg }}; +reactionsTabs: MultiSelect(defaultMultiSelect) { + padding: margins(12px, 10px, 12px, 10px); +} +reactionsTabIconSkip: 3px; historyRequestsUserpics: GroupCallUserpics { size: 22px; shift: 8px;