/* 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 "statistics/chart_widget.h" #include "statistics/linear_chart_view.h" #include "ui/abstract_button.h" #include "ui/rect.h" #include "styles/style_boxes.h" namespace Statistic { namespace { constexpr auto kHeightLimitsUpdateTimeout = crl::time(320); [[nodiscard]] int FindMaxValue( Data::StatisticalChart &chartData, int startXIndex, int endXIndex) { auto maxValue = 0; for (auto &l : chartData.lines) { const auto lineMax = l.segmentTree.rMaxQ(startXIndex, endXIndex); maxValue = std::max(lineMax, maxValue); } return maxValue; } [[nodiscard]] int FindMinValue( Data::StatisticalChart &chartData, int startXIndex, int endXIndex) { auto minValue = std::numeric_limits::max(); for (auto &l : chartData.lines) { const auto lineMin = l.segmentTree.rMinQ(startXIndex, endXIndex); minValue = std::min(lineMin, minValue); } return minValue; } void PaintHorizontalLines( QPainter &p, const ChartHorizontalLinesData &horizontalLine, const QRect &r) { for (const auto &line : horizontalLine.lines) { const auto lineRect = QRect( 0, r.y() + r.height() * line.relativeValue, r.x() + r.width(), st::lineWidth); p.fillRect(lineRect, st::boxTextFg); } } void PaintCaptionsToHorizontalLines( QPainter &p, const ChartHorizontalLinesData &horizontalLine, const QRect &r) { p.setFont(st::boxTextFont->f); p.setPen(st::boxTextFg); for (const auto &line : horizontalLine.lines) { p.drawText(10, r.y() + r.height() * line.relativeValue, line.caption); } } } // namespace class ChartWidget::Footer final : public Ui::AbstractButton { public: Footer(not_null parent); [[nodiscard]] rpl::producer xPercentageLimitsChange() const; private: not_null _left; not_null _right; rpl::event_stream _xPercentageLimitsChange; struct { int x = 0; int leftLimit = 0; int rightLimit = 0; } _start; }; ChartWidget::Footer::Footer(not_null parent) : Ui::AbstractButton(parent) , _left(Ui::CreateChild(this)) , _right(Ui::CreateChild(this)) { sizeValue( ) | rpl::start_with_next([=](const QSize &s) { _left->resize(st::colorSliderWidth, s.height()); _right->resize(st::colorSliderWidth, s.height()); }, _left->lifetime()); _left->paintRequest( ) | rpl::start_with_next([=] { auto p = QPainter(_left); p.setOpacity(0.3); p.fillRect(_left->rect(), st::boxTextFg); }, _left->lifetime()); _right->paintRequest( ) | rpl::start_with_next([=] { auto p = QPainter(_right); p.setOpacity(0.3); p.fillRect(_right->rect(), st::boxTextFg); }, _right->lifetime()); _left->move(10, 0); _right->move(50, 0); const auto handleDrag = [&]( not_null side, Fn leftLimit, Fn rightLimit) { side->events( ) | rpl::filter([=](not_null e) { return (e->type() == QEvent::MouseButtonPress) || (e->type() == QEvent::MouseButtonRelease) || ((e->type() == QEvent::MouseMove) && side->isDown()); }) | rpl::start_with_next([=](not_null e) { const auto pos = static_cast(e.get())->pos(); switch (e->type()) { case QEvent::MouseMove: { const auto nextX = std::clamp( side->x() + (pos.x() - _start.x), _start.leftLimit, _start.rightLimit); side->move(nextX, side->y()); } break; case QEvent::MouseButtonPress: { _start.x = pos.x(); _start.leftLimit = leftLimit(); _start.rightLimit = rightLimit(); } break; case QEvent::MouseButtonRelease: { _xPercentageLimitsChange.fire({ .min = _left->x() / float64(width()), .max = rect::right(_right) / float64(width()), }); _start = {}; } break; } }, side->lifetime()); }; handleDrag( _left, [=] { return 0; }, [=] { return _right->x() - _left->width(); }); handleDrag( _right, [=] { return rect::right(_left); }, [=] { return width() - _right->width(); }); } rpl::producer ChartWidget::Footer::xPercentageLimitsChange() const { return _xPercentageLimitsChange.events(); } ChartWidget::ChartWidget(not_null parent) : Ui::RpWidget(parent) , _footer(std::make_unique