From 03a59b04be04648f38439940b973ca5eb4109bac Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 4 Apr 2017 16:19:49 +0300 Subject: [PATCH] Add progress animation to GIFs search. Also display "no results" phrase. --- Telegram/SourceFiles/boxes/boxes.style | 1 + Telegram/SourceFiles/dialogs/dialogs.style | 1 + .../SourceFiles/stickers/gifs_list_widget.cpp | 19 ++- .../ui/effects/cross_animation.cpp | 123 +++++++++++++++++- .../SourceFiles/ui/effects/cross_animation.h | 2 +- Telegram/SourceFiles/ui/widgets/buttons.cpp | 50 ++++++- Telegram/SourceFiles/ui/widgets/buttons.h | 7 + Telegram/SourceFiles/ui/widgets/widgets.style | 1 + 8 files changed, 191 insertions(+), 13 deletions(-) diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index babfc3b43..08c133a9e 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -270,6 +270,7 @@ contactsSearchCancel: CrossButton { crossPosition: point(4px, 4px); duration: 150; + loadingPeriod: 1000; ripple: RippleAnimation(defaultRippleAnimation) { color: windowBgOver; } diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index bc2597fac..057429371 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -137,6 +137,7 @@ dialogsCancelSearch: CrossButton { crossPosition: point(0px, 0px); duration: 150; + loadingPeriod: 1000; ripple: emptyRippleAnimation; } diff --git a/Telegram/SourceFiles/stickers/gifs_list_widget.cpp b/Telegram/SourceFiles/stickers/gifs_list_widget.cpp index d35fabb41..7caa186f7 100644 --- a/Telegram/SourceFiles/stickers/gifs_list_widget.cpp +++ b/Telegram/SourceFiles/stickers/gifs_list_widget.cpp @@ -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(); } diff --git a/Telegram/SourceFiles/ui/effects/cross_animation.cpp b/Telegram/SourceFiles/ui/effects/cross_animation.cpp index bc58d4249..4e5b384bd 100644 --- a/Telegram/SourceFiles/ui/effects/cross_animation.cpp +++ b/Telegram/SourceFiles/ui/effects/cross_animation.cpp @@ -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 &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 &&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 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 diff --git a/Telegram/SourceFiles/ui/effects/cross_animation.h b/Telegram/SourceFiles/ui/effects/cross_animation.h index ef5d185da..85a9919a5 100644 --- a/Telegram/SourceFiles/ui/effects/cross_animation.h +++ b/Telegram/SourceFiles/ui/effects/cross_animation.h @@ -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.); }; diff --git a/Telegram/SourceFiles/ui/widgets/buttons.cpp b/Telegram/SourceFiles/ui/widgets/buttons.cpp index 7d4856574..d882e6160 100644 --- a/Telegram/SourceFiles/ui/widgets/buttons.cpp +++ b/Telegram/SourceFiles/ui/widgets/buttons.cpp @@ -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) { diff --git a/Telegram/SourceFiles/ui/widgets/buttons.h b/Telegram/SourceFiles/ui/widgets/buttons.h index 1cd82b3c0..3fd0bc721 100644 --- a/Telegram/SourceFiles/ui/widgets/buttons.h +++ b/Telegram/SourceFiles/ui/widgets/buttons.h @@ -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 diff --git a/Telegram/SourceFiles/ui/widgets/widgets.style b/Telegram/SourceFiles/ui/widgets/widgets.style index 5606f1dbc..5eb52f850 100644 --- a/Telegram/SourceFiles/ui/widgets/widgets.style +++ b/Telegram/SourceFiles/ui/widgets/widgets.style @@ -315,6 +315,7 @@ CrossButton { crossFgOver:color; crossPosition: point; duration: int; + loadingPeriod: int; ripple: RippleAnimation; }