Add progress animation to GIFs search.

Also display "no results" phrase.
This commit is contained in:
John Preston 2017-04-04 16:19:49 +03:00
parent 2028116e22
commit 03a59b04be
8 changed files with 191 additions and 13 deletions

View File

@ -270,6 +270,7 @@ contactsSearchCancel: CrossButton {
crossPosition: point(4px, 4px);
duration: 150;
loadingPeriod: 1000;
ripple: RippleAnimation(defaultRippleAnimation) {
color: windowBgOver;
}

View File

@ -137,6 +137,7 @@ dialogsCancelSearch: CrossButton {
crossPosition: point(0px, 0px);
duration: 150;
loadingPeriod: 1000;
ripple: emptyRippleAnimation;
}

View File

@ -48,6 +48,9 @@ public:
void stealFocus();
void returnFocus();
void setLoading(bool loading) {
_cancel->setLoadingAnimation(loading);
}
protected:
void paintEvent(QPaintEvent *e) override;
@ -181,6 +184,7 @@ GifsListWidget::~GifsListWidget() {
}
void GifsListWidget::cancelGifsSearch() {
_footer->setLoading(false);
if (_inlineRequestId) {
request(_inlineRequestId).cancel();
_inlineRequestId = 0;
@ -192,6 +196,7 @@ void GifsListWidget::cancelGifsSearch() {
}
void GifsListWidget::inlineResultsDone(const MTPmessages_BotResults &result) {
_footer->setLoading(false);
_inlineRequestId = 0;
auto it = _inlineCache.find(_inlineQuery);
@ -754,9 +759,7 @@ bool GifsListWidget::refreshInlineRows(int32 *added) {
auto it = _inlineCache.find(_inlineQuery);
const InlineCacheEntry *entry = nullptr;
if (it != _inlineCache.cend()) {
if (!it->second->results.empty()) {
entry = it->second.get();
}
entry = it->second.get();
_inlineNextOffset = it->second->nextOffset;
}
auto result = refreshInlineRows(entry, false);
@ -780,6 +783,7 @@ void GifsListWidget::searchForGifs(const QString &query) {
}
if (_inlineQuery != query) {
_footer->setLoading(false);
if (_inlineRequestId) {
request(_inlineRequestId).cancel();
_inlineRequestId = 0;
@ -813,8 +817,10 @@ void GifsListWidget::sendInlineRequest() {
if (_inlineRequestId || !_inlineQueryPeer || _inlineNextQuery.isEmpty()) {
return;
}
if (!_searchBot) {
// Wait for the bot being resolved.
_footer->setLoading(true);
_inlineRequestTimer.start(kSearchRequestDelay);
return;
}
@ -825,13 +831,18 @@ void GifsListWidget::sendInlineRequest() {
auto it = _inlineCache.find(_inlineQuery);
if (it != _inlineCache.cend()) {
nextOffset = it->second->nextOffset;
if (nextOffset.isEmpty()) return;
if (nextOffset.isEmpty()) {
_footer->setLoading(false);
return;
}
}
_footer->setLoading(true);
_inlineRequestId = request(MTPmessages_GetInlineBotResults(MTP_flags(0), _searchBot->inputUser, _inlineQueryPeer->input, MTPInputGeoPoint(), MTP_string(_inlineQuery), MTP_string(nextOffset))).done([this](const MTPmessages_BotResults &result, mtpRequestId requestId) {
inlineResultsDone(result);
}).fail([this](const RPCError &error) {
// show error?
_footer->setLoading(false);
_inlineRequestId = 0;
}).handleAllErrors().send();
}

View File

@ -21,19 +21,100 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/effects/cross_animation.h"
namespace Ui {
namespace {
void CrossAnimation::paint(Painter &p, const style::CrossAnimation &st, style::color color, int x, int y, int outerWidth, float64 shown) {
constexpr auto kPointCount = 12;
//
// 1 3
// X X X X
// X X X X
// 0 X X 4
// X X X X
// X 2 X
// X X
// X X
// 11 5
// X X
// X X
// X 8 X
// X X X X
// 10 X X 6
// X X X X
// X X X X
// 9 7
//
void transformLoadingCross(float64 loading, std::array<QPointF, kPointCount> &points, int &paintPointsCount) {
auto moveTo = [](QPointF &point, QPointF &to, float64 ratio) {
point = point * (1. - ratio) + to * ratio;
};
auto moveFrom = [](QPointF &point, QPointF &from, float64 ratio) {
point = from * (1. - ratio) + point * ratio;
};
auto paintPoints = [&points, &paintPointsCount](std::initializer_list<int> &&indices) {
auto index = 0;
for (auto paintIndex : indices) {
points[index++] = points[paintIndex];
}
paintPointsCount = indices.size();
};
if (loading < 0.125) {
auto ratio = loading / 0.125;
moveTo(points[6], points[5], ratio);
moveTo(points[7], points[8], ratio);
} else if (loading < 0.25) {
auto ratio = (loading - 0.125) / 0.125;
moveTo(points[9], points[8], ratio);
moveTo(points[10], points[11], ratio);
paintPoints({ 0, 1, 2, 3, 4, 9, 10, 11 });
} else if (loading < 0.375) {
auto ratio = (loading - 0.25) / 0.125;
moveTo(points[0], points[11], ratio);
moveTo(points[1], points[2], ratio);
paintPoints({ 0, 1, 2, 3, 4, 8 });
} else if (loading < 0.5) {
auto ratio = (loading - 0.375) / 0.125;
moveTo(points[8], points[4], ratio);
moveTo(points[11], points[3], ratio);
paintPoints({ 3, 4, 8, 11 });
} else if (loading < 0.625) {
auto ratio = (loading - 0.5) / 0.125;
moveFrom(points[8], points[4], ratio);
moveFrom(points[11], points[3], ratio);
paintPoints({ 3, 4, 8, 11 });
} else if (loading < 0.75) {
auto ratio = (loading - 0.625) / 0.125;
moveFrom(points[6], points[5], ratio);
moveFrom(points[7], points[8], ratio);
paintPoints({ 3, 4, 5, 6, 7, 11 });
} else if (loading < 0.875) {
auto ratio = (loading - 0.75) / 0.125;
moveFrom(points[9], points[8], ratio);
moveFrom(points[10], points[11], ratio);
paintPoints({ 3, 4, 5, 6, 7, 8, 9, 10 });
} else {
auto ratio = (loading - 0.875) / 0.125;
moveFrom(points[0], points[11], ratio);
moveFrom(points[1], points[2], ratio);
}
}
} // namespace
void CrossAnimation::paint(Painter &p, const style::CrossAnimation &st, style::color color, int x, int y, int outerWidth, float64 shown, float64 loading) {
PainterHighQualityEnabler hq(p);
auto deleteScale = shown + st.minScale * (1. - shown);
auto deleteSkip = deleteScale * st.skip + (1. - deleteScale) * (st.size / 2);
auto sqrt2 = sqrt(2.);
auto deleteScale = shown + st.minScale * (1. - shown);
auto deleteSkip = (deleteScale * st.skip) + (1. - deleteScale) * (st.size / 2);
auto deleteLeft = rtlpoint(x + deleteSkip, 0, outerWidth).x() + 0.;
auto deleteTop = y + deleteSkip + 0.;
auto deleteWidth = st.size - 2 * deleteSkip;
auto deleteHeight = st.size - 2 * deleteSkip;
auto deleteStroke = st.stroke / sqrt2;
QPointF pathDelete[] = {
std::array<QPointF, kPointCount> pathDelete = { {
{ deleteLeft, deleteTop + deleteStroke },
{ deleteLeft + deleteStroke, deleteTop },
{ deleteLeft + (deleteWidth / 2.), deleteTop + (deleteHeight / 2.) - deleteStroke },
@ -46,7 +127,17 @@ void CrossAnimation::paint(Painter &p, const style::CrossAnimation &st, style::c
{ deleteLeft + deleteStroke, deleteTop + deleteHeight },
{ deleteLeft, deleteTop + deleteHeight - deleteStroke },
{ deleteLeft + (deleteWidth / 2.) - deleteStroke, deleteTop + (deleteHeight / 2.) },
};
} };
auto pathDeleteSize = kPointCount;
auto loadingArcLength = 0;
if (loading > 0.) {
transformLoadingCross(loading, pathDelete, pathDeleteSize);
auto loadingArc = (loading >= 0.5) ? (loading - 1.) : loading;
loadingArcLength = qRound(-loadingArc * 2 * FullArcLength);
}
if (shown < 1.) {
auto alpha = -(shown - 1.) * M_PI_2;
auto cosalpha = cos(alpha);
@ -62,10 +153,30 @@ void CrossAnimation::paint(Painter &p, const style::CrossAnimation &st, style::c
}
QPainterPath path;
path.moveTo(pathDelete[0]);
for (int i = 1; i != base::array_size(pathDelete); ++i) {
for (int i = 1; i != pathDeleteSize; ++i) {
path.lineTo(pathDelete[i]);
}
path.lineTo(pathDelete[0]);
p.fillPath(path, color);
if (loadingArcLength != 0) {
auto loadingArcStart = FullArcLength / 8;
auto roundSkip = (st.size * (1 - sqrt2) + 2 * sqrt2 * deleteSkip + st.stroke) / 2;
auto roundPart = QRectF(x + roundSkip, y + roundSkip, st.size - 2 * roundSkip, st.size - 2 * roundSkip);
if (shown < 1.) {
loadingArcStart -= qRound(-(shown - 1.) * FullArcLength / 4.);
}
p.setBrush(Qt::NoBrush);
auto pen = color->p;
pen.setWidthF(st.stroke);
pen.setCapStyle(Qt::RoundCap);
p.setPen(pen);
if (loadingArcLength < 0) {
loadingArcStart += loadingArcLength;
loadingArcLength = -loadingArcLength;
}
p.drawArc(roundPart, loadingArcStart, loadingArcLength);
}
}
} // namespace Ui

View File

@ -26,7 +26,7 @@ namespace Ui {
class CrossAnimation {
public:
static void paint(Painter &p, const style::CrossAnimation &st, style::color color, int x, int y, int outerWidth, float64 shown);
static void paint(Painter &p, const style::CrossAnimation &st, style::color color, int x, int y, int outerWidth, float64 shown, float64 loading = 0.);
};

View File

@ -625,12 +625,22 @@ void LeftOutlineButton::paintEvent(QPaintEvent *e) {
}
CrossButton::CrossButton(QWidget *parent, const style::CrossButton &st) : RippleButton(parent, st.ripple)
, _st(st) {
, _st(st)
, _a_loading(animation(this, &CrossButton::step_loading)) {
resize(_st.width, _st.height);
setCursor(style::cur_pointer);
hide();
}
void CrossButton::step_loading(TimeMs ms, bool timer) {
if (stopLoadingAnimation(ms)) {
_a_loading.stop();
}
if (timer) {
update();
}
}
void CrossButton::toggleAnimated(bool visible) {
if (_shown == visible) {
return;
@ -659,7 +669,43 @@ void CrossButton::paintEvent(QPaintEvent *e) {
paintRipple(p, _st.crossPosition.x(), _st.crossPosition.y(), ms);
CrossAnimation::paint(p, _st.cross, over ? _st.crossFgOver : _st.crossFg, _st.crossPosition.x(), _st.crossPosition.y(), width(), shown);
auto loading = 0.;
if (_a_loading.animating()) {
if (stopLoadingAnimation(ms)) {
_a_loading.stop();
} else {
loading = ((ms - _loadingStartMs) % _st.loadingPeriod) / float64(_st.loadingPeriod);
}
}
CrossAnimation::paint(p, _st.cross, over ? _st.crossFgOver : _st.crossFg, _st.crossPosition.x(), _st.crossPosition.y(), width(), shown, loading);
}
bool CrossButton::stopLoadingAnimation(TimeMs ms) {
if (!_loadingStopMs) {
return false;
}
auto stopPeriod = (_loadingStopMs - _loadingStartMs) / _st.loadingPeriod;
auto currentPeriod = (ms - _loadingStartMs) / _st.loadingPeriod;
if (currentPeriod != stopPeriod) {
t_assert(currentPeriod > stopPeriod);
return true;
}
return false;
}
void CrossButton::setLoadingAnimation(bool enabled) {
if (enabled) {
_loadingStopMs = 0;
if (!_a_loading.animating()) {
_loadingStartMs = getms();
_a_loading.start();
}
} else if (_a_loading.animating()) {
_loadingStopMs = getms();
if (!((_loadingStopMs - _loadingStartMs) % _st.loadingPeriod)) {
_a_loading.stop();
}
}
}
void CrossButton::onStateChanged(State was, StateChangeSource source) {

View File

@ -233,6 +233,7 @@ public:
bool isShown() const {
return _shown;
}
void setLoadingAnimation(bool enabled);
protected:
void paintEvent(QPaintEvent *e) override;
@ -243,6 +244,8 @@ protected:
QPoint prepareRippleStartPosition() const override;
private:
void step_loading(TimeMs ms, bool timer);
bool stopLoadingAnimation(TimeMs ms);
void animationCallback();
const style::CrossButton &_st;
@ -250,6 +253,10 @@ private:
bool _shown = false;
Animation _a_show;
TimeMs _loadingStartMs = 0;
TimeMs _loadingStopMs = 0;
BasicAnimation _a_loading;
};
} // namespace Ui

View File

@ -315,6 +315,7 @@ CrossButton {
crossFgOver:color;
crossPosition: point;
duration: int;
loadingPeriod: int;
ripple: RippleAnimation;
}