mirror of
https://github.com/vale981/tdesktop
synced 2025-03-05 09:41:41 -05:00
Always display nice percent values.
Sum of percent values should never exceed 100%. If any two answers received same amount of votes, they should show same percent values. This way sum could be less than 100% (three answers, one vote each), but this looks better than giving extra vote to some random answer.
This commit is contained in:
parent
6fc4facddf
commit
8708a001c7
6 changed files with 152 additions and 60 deletions
|
@ -26,7 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
namespace {
|
||||
|
||||
constexpr auto kQuestionLimit = 255;
|
||||
constexpr auto kMaxOptionsCount = 10;
|
||||
constexpr auto kMaxOptionsCount = PollData::kMaxOptions;
|
||||
constexpr auto kOptionLimit = 100;
|
||||
constexpr auto kWarnQuestionLimit = 80;
|
||||
constexpr auto kWarnOptionLimit = 30;
|
||||
|
|
|
@ -51,7 +51,9 @@ bool PollData::applyChanges(const MTPDpoll &poll) {
|
|||
result.text = qs(answer.vtext);
|
||||
return result;
|
||||
});
|
||||
}) | ranges::to_vector;
|
||||
}) | ranges::view::take(
|
||||
kMaxOptions
|
||||
) | ranges::to_vector;
|
||||
|
||||
const auto changed1 = (question != newQuestion)
|
||||
|| (closed != newClosed);
|
||||
|
@ -78,6 +80,8 @@ bool PollData::applyChanges(const MTPDpoll &poll) {
|
|||
|
||||
bool PollData::applyResults(const MTPPollResults &results) {
|
||||
return results.match([&](const MTPDpollResults &results) {
|
||||
lastResultsUpdate = getms();
|
||||
|
||||
const auto newTotalVoters = results.has_total_voters()
|
||||
? results.vtotal_voters.v
|
||||
: totalVoters;
|
||||
|
@ -89,8 +93,11 @@ bool PollData::applyResults(const MTPPollResults &results) {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (!changed) {
|
||||
return false;
|
||||
}
|
||||
totalVoters = newTotalVoters;
|
||||
lastResultsUpdate = getms();
|
||||
++version;
|
||||
return changed;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -45,6 +45,8 @@ struct PollData {
|
|||
|
||||
int version = 0;
|
||||
|
||||
static constexpr auto kMaxOptions = 10;
|
||||
|
||||
private:
|
||||
bool applyResultToAnswers(
|
||||
const MTPPollAnswerVoters &result,
|
||||
|
|
|
@ -1523,7 +1523,7 @@ not_null<PollData*> Session::poll(const MTPDmessageMediaPoll &data) {
|
|||
const auto result = poll(data.vpoll);
|
||||
const auto changed = result->applyResults(data.vresults);
|
||||
if (changed) {
|
||||
requestPollViewRepaint(result);
|
||||
notifyPollUpdateDelayed(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -1538,7 +1538,7 @@ void Session::applyPollUpdate(const MTPDupdateMessagePoll &update) {
|
|||
: i->second.get();
|
||||
}();
|
||||
if (updated && updated->applyResults(update.vresults)) {
|
||||
requestPollViewRepaint(updated);
|
||||
notifyPollUpdateDelayed(updated);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -61,6 +61,73 @@ FormattedLargeNumber FormatLargeNumber(int64 number) {
|
|||
return result;
|
||||
}
|
||||
|
||||
struct PercentCounterItem {
|
||||
int index = 0;
|
||||
int percent = 0;
|
||||
int remainder = 0;
|
||||
|
||||
inline bool operator<(const PercentCounterItem &other) const {
|
||||
if (remainder > other.remainder) {
|
||||
return true;
|
||||
} else if (remainder < other.remainder) {
|
||||
return false;
|
||||
}
|
||||
return percent < other.percent;
|
||||
}
|
||||
};
|
||||
|
||||
void AdjustPercentCount(gsl::span<PercentCounterItem> items, int left) {
|
||||
ranges::sort(items, std::less<>());
|
||||
for (auto i = 0, count = int(items.size()); i != count;) {
|
||||
const auto &item = items[i];
|
||||
auto j = i + 1;
|
||||
for (; j != count; ++j) {
|
||||
if (items[j].percent != item.percent
|
||||
|| items[j].remainder != item.remainder) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const auto equal = j - i;
|
||||
if (equal <= left) {
|
||||
left -= equal;
|
||||
for (; i != j; ++i) {
|
||||
++items[i].percent;
|
||||
}
|
||||
} else {
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CountNicePercent(
|
||||
gsl::span<const int> votes,
|
||||
int total,
|
||||
gsl::span<int> result) {
|
||||
Expects(result.size() >= votes.size());
|
||||
Expects(votes.size() <= PollData::kMaxOptions);
|
||||
|
||||
const auto count = size_type(votes.size());
|
||||
PercentCounterItem ItemsStorage[PollData::kMaxOptions];
|
||||
const auto items = gsl::make_span(ItemsStorage).subspan(0, count);
|
||||
auto left = 100;
|
||||
auto &&zipped = ranges::view::zip(
|
||||
votes,
|
||||
items,
|
||||
ranges::view::ints(0));
|
||||
for (auto &&[votes, item, index] : zipped) {
|
||||
item.index = index;
|
||||
item.percent = (votes * 100) / total;
|
||||
item.remainder = (votes * 100) - (item.percent * total);
|
||||
left -= item.percent;
|
||||
}
|
||||
if (left > 0 && left <= count) {
|
||||
AdjustPercentCount(items, left);
|
||||
}
|
||||
for (const auto &item : items) {
|
||||
result[item.index] = item.percent;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct HistoryPoll::AnswerAnimation {
|
||||
|
@ -90,11 +157,12 @@ struct HistoryPoll::Answer {
|
|||
|
||||
Text text;
|
||||
QByteArray option;
|
||||
mutable int votes = 0;
|
||||
mutable int votesPercentWidth = 0;
|
||||
mutable float64 filling = 0.;
|
||||
mutable QString votesPercent;
|
||||
mutable bool chosen = false;
|
||||
int votes = 0;
|
||||
int votesPercent = 0;
|
||||
int votesPercentWidth = 0;
|
||||
float64 filling = 0.;
|
||||
QString votesPercentString;
|
||||
bool chosen = false;
|
||||
ClickHandlerPtr handler;
|
||||
mutable std::unique_ptr<Ui::RippleAnimation> ripple;
|
||||
};
|
||||
|
@ -247,14 +315,18 @@ void HistoryPoll::updateTexts() {
|
|||
|
||||
const auto willStartAnimation = checkAnimationStart();
|
||||
|
||||
_closed = _poll->closed;
|
||||
_question.setText(
|
||||
st::historyPollQuestionStyle,
|
||||
_poll->question,
|
||||
Ui::WebpageTextTitleOptions());
|
||||
_subtitle.setText(
|
||||
st::msgDateTextStyle,
|
||||
lang(_closed ? lng_polls_closed : lng_polls_anonymous));
|
||||
if (_question.originalText() != _poll->question) {
|
||||
_question.setText(
|
||||
st::historyPollQuestionStyle,
|
||||
_poll->question,
|
||||
Ui::WebpageTextTitleOptions());
|
||||
}
|
||||
if (_closed != _poll->closed || _subtitle.isEmpty()) {
|
||||
_closed = _poll->closed;
|
||||
_subtitle.setText(
|
||||
st::msgDateTextStyle,
|
||||
lang(_closed ? lng_polls_closed : lng_polls_anonymous));
|
||||
}
|
||||
|
||||
updateAnswers();
|
||||
updateVotes();
|
||||
|
@ -303,19 +375,13 @@ ClickHandlerPtr HistoryPoll::createAnswerClickHandler(
|
|||
});
|
||||
}
|
||||
|
||||
void HistoryPoll::updateVotes() const {
|
||||
void HistoryPoll::updateVotes() {
|
||||
_voted = _poll->voted();
|
||||
updateAnswerVotes();
|
||||
updateTotalVotes();
|
||||
}
|
||||
|
||||
void HistoryPoll::updateVotesCheckAnimations() const {
|
||||
const auto willStartAnimation = checkAnimationStart();
|
||||
updateVotes();
|
||||
if (willStartAnimation) {
|
||||
startAnswersAnimation();
|
||||
}
|
||||
|
||||
void HistoryPoll::checkSendingAnimation() const {
|
||||
const auto &sending = _poll->sendingVote;
|
||||
if (sending.isEmpty() == !_sendingAnimation) {
|
||||
if (_sendingAnimation) {
|
||||
|
@ -337,7 +403,7 @@ void HistoryPoll::updateVotesCheckAnimations() const {
|
|||
_sendingAnimation->animation.start();
|
||||
}
|
||||
|
||||
void HistoryPoll::updateTotalVotes() const {
|
||||
void HistoryPoll::updateTotalVotes() {
|
||||
if (_totalVotes == _poll->totalVoters && !_totalVotesLabel.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
@ -357,26 +423,26 @@ void HistoryPoll::updateTotalVotes() const {
|
|||
}
|
||||
|
||||
void HistoryPoll::updateAnswerVotesFromOriginal(
|
||||
const Answer &answer,
|
||||
Answer &answer,
|
||||
const PollAnswer &original,
|
||||
int totalVotes,
|
||||
int maxVotes) const {
|
||||
int percent,
|
||||
int maxVotes) {
|
||||
if (canVote()) {
|
||||
answer.votesPercent.clear();
|
||||
} else if (answer.votes != original.votes
|
||||
|| answer.votesPercent.isEmpty()
|
||||
|| std::max(_totalVotes, 1) != totalVotes) {
|
||||
const auto percent = int(std::round(
|
||||
original.votes * 100. / totalVotes));
|
||||
answer.votesPercent = QString::number(percent) + '%';
|
||||
answer.votesPercent = 0;
|
||||
answer.votesPercentString.clear();
|
||||
answer.votesPercentWidth = 0;
|
||||
} else if (answer.votesPercentString.isEmpty()
|
||||
|| answer.votesPercent != percent) {
|
||||
answer.votesPercent = percent;
|
||||
answer.votesPercentString = QString::number(percent) + '%';
|
||||
answer.votesPercentWidth = st::historyPollPercentFont->width(
|
||||
answer.votesPercent);
|
||||
answer.votesPercentString);
|
||||
}
|
||||
answer.votes = original.votes;
|
||||
answer.filling = answer.votes / float64(maxVotes);
|
||||
}
|
||||
|
||||
void HistoryPoll::updateAnswerVotes() const {
|
||||
void HistoryPoll::updateAnswerVotes() {
|
||||
if (_poll->answers.size() != _answers.size()
|
||||
|| _poll->answers.empty()) {
|
||||
return;
|
||||
|
@ -386,12 +452,33 @@ void HistoryPoll::updateAnswerVotes() const {
|
|||
_poll->answers,
|
||||
ranges::less(),
|
||||
&PollAnswer::votes)->votes);
|
||||
auto &&answers = ranges::view::zip(_answers, _poll->answers);
|
||||
for (auto &&[answer, original] : answers) {
|
||||
|
||||
constexpr auto kMaxCount = PollData::kMaxOptions;
|
||||
const auto count = size_type(_poll->answers.size());
|
||||
Assert(count <= kMaxCount);
|
||||
int PercentsStorage[kMaxCount] = { 0 };
|
||||
int VotesStorage[kMaxCount] = { 0 };
|
||||
|
||||
ranges::copy(
|
||||
ranges::view::all(
|
||||
_poll->answers
|
||||
) | ranges::view::transform(&PollAnswer::votes),
|
||||
ranges::begin(VotesStorage));
|
||||
|
||||
CountNicePercent(
|
||||
gsl::make_span(VotesStorage).subspan(0, count),
|
||||
totalVotes,
|
||||
gsl::make_span(PercentsStorage).subspan(0, count));
|
||||
|
||||
auto &&answers = ranges::view::zip(
|
||||
_answers,
|
||||
_poll->answers,
|
||||
PercentsStorage);
|
||||
for (auto &&[answer, original, percent] : answers) {
|
||||
updateAnswerVotesFromOriginal(
|
||||
answer,
|
||||
original,
|
||||
totalVotes,
|
||||
percent,
|
||||
maxVotes);
|
||||
}
|
||||
}
|
||||
|
@ -400,7 +487,7 @@ void HistoryPoll::draw(Painter &p, const QRect &r, TextSelection selection, Time
|
|||
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
|
||||
auto paintx = 0, painty = 0, paintw = width(), painth = height();
|
||||
|
||||
updateVotesCheckAnimations();
|
||||
checkSendingAnimation();
|
||||
_poll->checkResultsReload(_parent->data(), ms);
|
||||
|
||||
const auto outbg = _parent->hasOutLayout();
|
||||
|
@ -542,7 +629,7 @@ int HistoryPoll::paintAnswer(
|
|||
} else {
|
||||
paintPercent(
|
||||
p,
|
||||
answer.votesPercent,
|
||||
answer.votesPercentString,
|
||||
answer.votesPercentWidth,
|
||||
left,
|
||||
top,
|
||||
|
@ -682,9 +769,7 @@ void HistoryPoll::saveStateInAnimation() const {
|
|||
_answersAnimation->data.reserve(_answers.size());
|
||||
const auto convert = [&](const Answer &answer) {
|
||||
auto result = AnswerAnimation();
|
||||
result.percent = can
|
||||
? 0.
|
||||
: (answer.votes * 100. / std::max(_totalVotes, 1));
|
||||
result.percent = can ? 0. : float64(answer.votesPercent);
|
||||
result.filling = can ? 0. : answer.filling;
|
||||
result.opacity = can ? 0. : 1.;
|
||||
return result;
|
||||
|
@ -716,9 +801,7 @@ void HistoryPoll::startAnswersAnimation() const {
|
|||
const auto can = canVote();
|
||||
auto &&both = ranges::view::zip(_answers, _answersAnimation->data);
|
||||
for (auto &&[answer, data] : both) {
|
||||
data.percent.start(can
|
||||
? 0.
|
||||
: answer.votes * 100. / std::max(_totalVotes, 1));
|
||||
data.percent.start(can ? 0. : float64(answer.votesPercent));
|
||||
data.filling.start(can ? 0. : answer.filling);
|
||||
data.opacity.start(can ? 0. : 1.);
|
||||
}
|
||||
|
|
|
@ -61,15 +61,15 @@ private:
|
|||
const Answer &answer) const;
|
||||
void updateTexts();
|
||||
void updateAnswers();
|
||||
void updateVotes() const;
|
||||
void updateTotalVotes() const;
|
||||
void updateAnswerVotes() const;
|
||||
void updateVotes();
|
||||
void updateTotalVotes();
|
||||
void updateAnswerVotes();
|
||||
void updateAnswerVotesFromOriginal(
|
||||
const Answer &answer,
|
||||
Answer &answer,
|
||||
const PollAnswer &original,
|
||||
int totalVotes,
|
||||
int maxVotes) const;
|
||||
void updateVotesCheckAnimations() const;
|
||||
int percent,
|
||||
int maxVotes);
|
||||
void checkSendingAnimation() const;
|
||||
|
||||
int paintAnswer(
|
||||
Painter &p,
|
||||
|
@ -115,14 +115,14 @@ private:
|
|||
|
||||
not_null<PollData*> _poll;
|
||||
int _pollVersion = 0;
|
||||
mutable int _totalVotes = 0;
|
||||
mutable bool _voted = false;
|
||||
int _totalVotes = 0;
|
||||
bool _voted = false;
|
||||
bool _closed = false;
|
||||
|
||||
Text _question;
|
||||
Text _subtitle;
|
||||
std::vector<Answer> _answers;
|
||||
mutable Text _totalVotesLabel;
|
||||
Text _totalVotesLabel;
|
||||
|
||||
mutable std::unique_ptr<AnswersAnimation> _answersAnimation;
|
||||
mutable std::unique_ptr<SendingAnimation> _sendingAnimation;
|
||||
|
|
Loading…
Add table
Reference in a new issue