tdesktop/Telegram/SourceFiles/history/view/reactions/history_view_reactions_tabs...

188 lines
5.4 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/view/reactions/history_view_reactions_tabs.h"
#include "ui/rp_widget.h"
#include "ui/abstract_button.h"
#include "ui/controls/who_reacted_context_action.h"
#include "data/data_message_reaction_id.h"
#include "styles/style_widgets.h"
#include "styles/style_chat.h"
namespace HistoryView::Reactions {
namespace {
using ::Data::ReactionId;
not_null<Ui::AbstractButton*> CreateTab(
not_null<QWidget*> parent,
const style::MultiSelect &st,
const ReactionId &reaction,
Ui::WhoReadType whoReadType,
int count,
rpl::producer<bool> 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<Ui::AbstractButton>(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<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);
// #TODO reactions
if (const auto emoji = Ui::Emoji::Find(reaction.emoji())) {
const auto size = Ui::Emoji::GetSizeNormal();
const auto shift = (height - (size / factor)) / 2;
Ui::Emoji::Draw(p, emoji, size, icon.x() + shift, shift);
} else {
using Type = Ui::WhoReadType;
(reaction.emoji().isEmpty()
? (state->selected
? st::reactionsTabAllSelected
: st::reactionsTabAll)
: (whoReadType == Type::Watched
|| whoReadType == Type::Listened)
? (state->selected
? st::reactionsTabPlayedSelected
: st::reactionsTabPlayed)
: (state->selected
? st::reactionsTabChecksSelected
: st::reactionsTabChecks)).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<Tabs*> CreateTabs(
not_null<QWidget*> parent,
const std::vector<Data::MessageReaction> &items,
const ReactionId &selected,
Ui::WhoReadType whoReadType) {
struct State {
rpl::variable<ReactionId> selected;
std::vector<not_null<Ui::AbstractButton*>> tabs;
};
const auto result = Ui::CreateChild<Tabs>(parent.get());
using Entry = std::pair<int, ReactionId>;
auto tabs = Ui::CreateChild<Ui::RpWidget>(parent.get());
const auto st = &st::reactionsTabs;
const auto state = tabs->lifetime().make_state<State>();
state->selected = selected;
const auto append = [&](const ReactionId &reaction, int count) {
using namespace rpl::mappers;
const auto tab = CreateTab(
tabs,
*st,
reaction,
whoReadType,
count,
state->selected.value() | rpl::map(_1 == reaction));
tab->setClickedCallback([=] {
state->selected = reaction;
});
state->tabs.push_back(tab);
};
auto sorted = std::vector<Entry>();
for (const auto &reaction : items) {
if (reaction.id.emoji() == u"read"_q) {
append(reaction.id, reaction.count);
} else {
sorted.emplace_back(reaction.count, reaction.id);
}
}
ranges::sort(sorted, std::greater<>(), &Entry::first);
const auto count = ranges::accumulate(
sorted,
0,
std::plus<>(),
&Entry::first);
append(ReactionId(), count);
for (const auto &[count, reaction] : sorted) {
append(reaction, count);
}
result->move = [=](int x, int y) {
tabs->moveToLeft(x, y);
};
result->resizeToWidth = [=](int width) {
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->heightValue = [=] {
using namespace rpl::mappers;
return tabs->heightValue() | rpl::map(_1 - st::lineWidth);
};
result->changes = [=] {
return state->selected.changes();
};
return result;
}
} // namespace HistoryView::Reactions