From c8d5a60c749f41d6f12bb233018cf6c84af79e84 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 1 Jul 2023 10:50:53 +0300 Subject: [PATCH] Moved out processing of chart animation to separate class. --- .../SourceFiles/statistics/chart_widget.cpp | 517 ++++++++---------- .../SourceFiles/statistics/chart_widget.h | 72 ++- 2 files changed, 288 insertions(+), 301 deletions(-) diff --git a/Telegram/SourceFiles/statistics/chart_widget.cpp b/Telegram/SourceFiles/statistics/chart_widget.cpp index 986fad0bbb..298c3396f9 100644 --- a/Telegram/SourceFiles/statistics/chart_widget.cpp +++ b/Telegram/SourceFiles/statistics/chart_widget.cpp @@ -20,10 +20,6 @@ namespace { constexpr auto kHeightLimitsUpdateTimeout = crl::time(320); -[[nodiscard]] bool AnimFinished(const anim::value &anim) { - return anim.current() == anim.to(); -} - [[nodiscard]] int FindMaxValue( Data::StatisticalChart &chartData, int startXIndex, @@ -106,6 +102,216 @@ private: }; +ChartWidget::ChartAnimationController::ChartAnimationController( + Fn &&updateCallback) +: _animation(std::move(updateCallback)) { +} + +void ChartWidget::ChartAnimationController::setXPercentageLimits( + Data::StatisticalChart &chartData, + Limits xPercentageLimits, + crl::time now) { + if ((_animValueXMin.to() == xPercentageLimits.min) + && (_animValueXMax.to() == xPercentageLimits.max)) { + return; + } + start(); + _animValueXMin.start(xPercentageLimits.min); + _animValueXMax.start(xPercentageLimits.max); + _lastUserInteracted = now; + + { + auto minY = std::numeric_limits::max(); + auto maxY = 0.; + auto minYIndex = 0; + auto maxYIndex = 0; + const auto tempXPercentage = Limits{ + .min = *ranges::lower_bound( + chartData.xPercentage, + xPercentageLimits.min), + .max = *ranges::lower_bound( + chartData.xPercentage, + xPercentageLimits.max), + }; + for (auto i = 0; i < chartData.xPercentage.size(); i++) { + if (chartData.xPercentage[i] == tempXPercentage.min) { + minYIndex = i; + } + if (chartData.xPercentage[i] == tempXPercentage.max) { + maxYIndex = i; + } + } + for (const auto &line : chartData.lines) { + for (auto i = minYIndex; i < maxYIndex; i++) { + if (line.y[i] > maxY) { + maxY = line.y[i]; + } + if (line.y[i] < minY) { + minY = line.y[i]; + } + } + } + _animValueYMin = anim::value( + _animValueYMin.current(), + minY); + _animValueYMax = anim::value( + _animValueYMax.current(), + maxY); + + { + auto k = (_animValueYMax.current() - _animValueYMin.current()) + / float64(maxY - minY); + if (k > 1.) { + k = 1. / k; + } + constexpr auto kDtHeightSpeed1 = 0.03 / 2; + constexpr auto kDtHeightSpeed2 = 0.03 / 2; + constexpr auto kDtHeightSpeed3 = 0.045 / 2; + constexpr auto kDtHeightSpeedThreshold1 = 0.7; + constexpr auto kDtHeightSpeedThreshold2 = 0.1; + constexpr auto kDtHeightInstantThreshold = 0.97; + _dtYSpeed = (k > kDtHeightSpeedThreshold1) + ? kDtHeightSpeed1 + : (k < kDtHeightSpeedThreshold2) + ? kDtHeightSpeed2 + : kDtHeightSpeed3; + if (k < kDtHeightInstantThreshold) { + _dtCurrent = { 0., 0. }; + } + } + + } + { + const auto startXIndex = chartData.findStartIndex( + _animValueXMin.to()); + const auto endXIndex = chartData.findEndIndex( + startXIndex, + _animValueXMax.to()); + _finalHeightLimits = { + float64(FindMinValue(chartData, startXIndex, endXIndex)), + float64(FindMaxValue(chartData, startXIndex, endXIndex)), + }; + } +} + +void ChartWidget::ChartAnimationController::start() { + if (!_animation.animating()) { + _animation.start(); + } +} + +void ChartWidget::ChartAnimationController::resetAlpha() { + _alphaAnimationStartedAt = 0; + _animValueYAlpha = anim::value(0., 1.); +} + +void ChartWidget::ChartAnimationController::tick( + crl::time now, + std::vector &horizontalLines) { + if (!_animation.animating()) { + return; + } + constexpr auto kExpandingDelay = crl::time(100); + constexpr auto kXExpandingDuration = 200.; + constexpr auto kAlphaExpandingDuration = 200.; + + if (!_yAnimationStartedAt + && ((now - _lastUserInteracted) >= kExpandingDelay)) { + _heightAnimationStarts.fire({}); + _yAnimationStartedAt = _lastUserInteracted + kExpandingDelay; + } + if (!_alphaAnimationStartedAt) { + _alphaAnimationStartedAt = now; + } + + _dtCurrent.min = std::min(_dtCurrent.min + _dtYSpeed, 1.); + _dtCurrent.max = std::min(_dtCurrent.max + _dtYSpeed, 1.); + + const auto dtX = std::min( + (now - _animation.started()) / kXExpandingDuration, + 1.); + const auto dtAlpha = std::min( + (now - _alphaAnimationStartedAt) / kAlphaExpandingDuration, + 1.); + + const auto isFinished = [](const anim::value &anim) { + return anim.current() == anim.to(); + }; + + const auto xFinished = isFinished(_animValueXMin) + && isFinished(_animValueXMax); + const auto yFinished = isFinished(_animValueYMin) + && isFinished(_animValueYMax); + const auto alphaFinished = isFinished(_animValueYAlpha); + + if (xFinished && yFinished && alphaFinished) { + const auto &lines = horizontalLines.back().lines; + if ((lines.front().absoluteValue == _animValueYMin.to()) + && (lines.back().absoluteValue == _animValueYMax.to())) { + _animation.stop(); + } + } + if (xFinished) { + _animValueXMin.finish(); + _animValueXMax.finish(); + } else { + _animValueXMin.update(dtX, anim::linear); + _animValueXMax.update(dtX, anim::linear); + } + if (_yAnimationStartedAt) { + _animValueYMin.update(_dtCurrent.min, anim::easeInCubic); + _animValueYMax.update(_dtCurrent.max, anim::easeInCubic); + _animValueYAlpha.update(dtAlpha, anim::easeInCubic); + + for (auto &horizontalLine : horizontalLines) { + horizontalLine.computeRelative( + _animValueYMax.current(), + _animValueYMin.current()); + } + } + + if (dtAlpha >= 0. && dtAlpha <= 1.) { + const auto value = _animValueYAlpha.current(); + + for (auto &horizontalLine : horizontalLines) { + horizontalLine.alpha = horizontalLine.fixedAlpha * (1. - value); + } + horizontalLines.back().alpha = value; + if (value == 1.) { + while (horizontalLines.size() > 1) { + const auto startIt = begin(horizontalLines); + if (!startIt->alpha) { + horizontalLines.erase(startIt); + } else { + break; + } + } + } + } + + if (yFinished && alphaFinished) { + _alphaAnimationStartedAt = 0; + _yAnimationStartedAt = 0; + } +} + +Limits ChartWidget::ChartAnimationController::currentXLimits() const { + return { _animValueXMin.current(), _animValueXMax.current() }; +} + +Limits ChartWidget::ChartAnimationController::currentHeightLimits() const { + return { _animValueYMin.current(), _animValueYMax.current() }; +} + +Limits ChartWidget::ChartAnimationController::finalHeightLimits() const { + return _finalHeightLimits; +} + +auto ChartWidget::ChartAnimationController::heightAnimationStarts() const +-> rpl::producer<> { + return _heightAnimationStarts.events(); +} + ChartWidget::Footer::Footer(not_null parent) : Ui::AbstractButton(parent) , _left(Ui::CreateChild(this)) @@ -206,7 +412,8 @@ rpl::producer<> ChartWidget::Footer::directionChanges() const { ChartWidget::ChartWidget(not_null parent) : Ui::RpWidget(parent) -, _footer(std::make_unique