From 4c02d19a5171dbf235c67dd9a950aca7bf3d0a36 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 14 Jul 2023 06:50:26 +0300 Subject: [PATCH] Added implementation of buttons container to filter chart lines. --- .../statistics/chart_lines_filter_widget.cpp | 241 ++++++++++++++++++ .../statistics/chart_lines_filter_widget.h | 39 +++ .../SourceFiles/statistics/statistics.style | 3 + Telegram/cmake/td_ui.cmake | 3 + 4 files changed, 286 insertions(+) create mode 100644 Telegram/SourceFiles/statistics/chart_lines_filter_widget.cpp create mode 100644 Telegram/SourceFiles/statistics/chart_lines_filter_widget.h diff --git a/Telegram/SourceFiles/statistics/chart_lines_filter_widget.cpp b/Telegram/SourceFiles/statistics/chart_lines_filter_widget.cpp new file mode 100644 index 000000000..7e087e88d --- /dev/null +++ b/Telegram/SourceFiles/statistics/chart_lines_filter_widget.cpp @@ -0,0 +1,241 @@ +/* +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_lines_filter_widget.h" + +#include "ui/abstract_button.h" +#include "ui/effects/animations.h" +#include "ui/painter.h" +#include "ui/rect.h" +#include "styles/style_basic.h" +#include "styles/style_statistics.h" +#include "styles/style_widgets.h" + +namespace Statistic { +namespace { +constexpr auto kShiftDuration = crl::time(300); +} // namespace + +class ChartLinesFilterWidget::FlatCheckbox final : public Ui::AbstractButton { +public: + FlatCheckbox( + not_null parent, + const QString &text, + QColor activeColor); + + void shake(); + void setChecked(bool value, bool animated); + [[nodiscard]] bool checked() const; + +protected: + void paintEvent(QPaintEvent *e) override; + +private: + const QColor _activeColor; + const QColor _inactiveColor; + Ui::Text::String _text; + + Ui::Animations::Simple _animation; + + struct { + Ui::Animations::Simple animation; + int shift = 0; + } _shake; + + bool _checked = true; + +}; + +ChartLinesFilterWidget::FlatCheckbox::FlatCheckbox( + not_null parent, + const QString &text, + QColor activeColor) +: Ui::AbstractButton(parent) +, _activeColor(activeColor) +, _inactiveColor(st::boxBg->c) +, _text(st::defaultTextStyle, text) { + const auto &margins = st::statisticsChartFlatCheckboxMargins; + const auto h = _text.minHeight() + rect::m::sum::v(margins) * 2; + resize( + _text.maxWidth() + + rect::m::sum::h(margins) + + h + + st::statisticsChartFlatCheckboxCheckWidth * 3, + h); +} + +void ChartLinesFilterWidget::FlatCheckbox::setChecked( + bool value, + bool animated) { + if (_checked == value) { + return; + } + _checked = value; + if (!animated) { + _animation.stop(); + } else { + const auto from = value ? 0. : 1.; + const auto to = value ? 1. : 0.; + _animation.start([=] { update(); }, from, to, kShiftDuration); + } +} + +bool ChartLinesFilterWidget::FlatCheckbox::checked() const { + return _checked; +} + +void ChartLinesFilterWidget::FlatCheckbox::shake() { + if (_shake.animation.animating()) { + return; + } + constexpr auto kShiftProgress = 6; + constexpr auto kSegmentsCount = 5; + const auto refresh = [=] { + const auto fullProgress = _shake.animation.value(1.) * kShiftProgress; + const auto segment = std::clamp( + int(std::floor(fullProgress)), + 0, + kSegmentsCount); + const auto part = fullProgress - segment; + const auto from = (segment == 0) + ? 0. + : (segment == 1 || segment == 3 || segment == 5) + ? 1. + : -1.; + const auto to = (segment == 0 || segment == 2 || segment == 4) + ? 1. + : (segment == 1 || segment == 3) + ? -1. + : 0.; + const auto shift = from * (1. - part) + to * part; + _shake.shift = int(base::SafeRound(shift * st::shakeShift)); + update(); + }; + _shake.animation.start(refresh, 0., 1., kShiftDuration); +} + +void ChartLinesFilterWidget::FlatCheckbox::paintEvent(QPaintEvent *e) { + auto p = QPainter(this); + + const auto progress = _animation.value(_checked ? 1. : 0.); + + p.translate(_shake.shift, 0); + + const auto checkWidth = st::statisticsChartFlatCheckboxCheckWidth; + const auto r = rect() - st::statisticsChartFlatCheckboxMargins; + const auto heightHalf = r.height() / 2.; + const auto textX = anim::interpolate( + r.center().x() - _text.maxWidth() / 2., + r.x() + heightHalf + checkWidth * 5, + progress); + const auto textY = (r - st::statisticsChartFlatCheckboxMargins).y(); + p.fillRect(r, Qt::transparent); + + constexpr auto kCheckPartProgress = 0.5; + const auto checkProgress = progress / kCheckPartProgress; + const auto textColor = (progress <= kCheckPartProgress) + ? anim::color(_activeColor, _inactiveColor, checkProgress) + : _inactiveColor; + const auto fillColor = (progress <= kCheckPartProgress) + ? anim::color(_inactiveColor, _activeColor, checkProgress) + : _activeColor; + + p.setPen(QPen(_activeColor, st::statisticsChartLineWidth)); + p.setBrush(fillColor); + const auto radius = r.height() / 2.; + { + auto hq = PainterHighQualityEnabler(p); + p.drawRoundedRect(r, radius, radius); + } + + p.setPen(textColor); + const auto textContext = Ui::Text::PaintContext{ + .position = QPoint(textX, textY), + .availableWidth = width(), + }; + _text.draw(p, textContext); + + if (progress > kCheckPartProgress) { + p.setPen(QPen(textColor, st::statisticsChartLineWidth)); + const auto bounceProgress = checkProgress - 1.; + const auto start = QPoint( + r.x() + heightHalf + checkWidth, + textY + _text.style()->font->ascent); + p.translate(start); + p.drawLine({}, -QPoint(checkWidth, checkWidth) * bounceProgress); + p.drawLine({}, QPoint(checkWidth, -checkWidth) * bounceProgress * 2); + } +} + +ChartLinesFilterWidget::ChartLinesFilterWidget( + not_null parent) +: Ui::RpWidget(parent) { +} + +void ChartLinesFilterWidget::fillButtons( + const std::vector &texts, + const std::vector &colors, + const std::vector &ids, + int outerWidth) { + Expects(texts.size() == colors.size()); + _buttons.clear(); + + _buttons.reserve(texts.size()); + auto maxRight = 0; + for (auto i = 0; i < texts.size(); i++) { + auto button = base::make_unique_q( + this, + texts[i], + colors[i]); + button->show(); + if (!i) { + button->move(0, 0); + } else { + const auto lastRaw = _buttons.back().get(); + const auto lastLeft = rect::right(lastRaw); + const auto isOut = (lastLeft + button->width() > outerWidth); + const auto left = isOut ? 0 : lastLeft; + const auto top = isOut ? rect::bottom(lastRaw) : lastRaw->y(); + button->move(left, top); + } + const auto id = ids[i]; + button->setClickedCallback([=, raw = button.get()] { + const auto checked = !raw->checked(); + if (!checked) { + const auto cancel = [&] { + for (const auto &b : _buttons) { + if (b.get() == raw) { + continue; + } + if (b->checked()) { + return false; + } + } + return true; + }(); + if (cancel) { + raw->shake(); + return; + } + } + raw->setChecked(checked, true); + _buttonEnabledChanges.fire({ .id = id, .enabled = checked }); + }); + maxRight = std::max(maxRight, rect::right(button.get())); + + _buttons.push_back(std::move(button)); + } + + resize(maxRight, rect::bottom(_buttons.back().get())); +} + +auto ChartLinesFilterWidget::buttonEnabledChanges() const +-> rpl::producer { + return _buttonEnabledChanges.events(); +} + +} // namespace Statistic diff --git a/Telegram/SourceFiles/statistics/chart_lines_filter_widget.h b/Telegram/SourceFiles/statistics/chart_lines_filter_widget.h new file mode 100644 index 000000000..0743f48d8 --- /dev/null +++ b/Telegram/SourceFiles/statistics/chart_lines_filter_widget.h @@ -0,0 +1,39 @@ +/* +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 +*/ +#pragma once + +#include "ui/rp_widget.h" + +namespace Statistic { + +class ChartLinesFilterWidget final : public Ui::RpWidget { +public: + ChartLinesFilterWidget(not_null parent); + + void fillButtons( + const std::vector &texts, + const std::vector &colors, + const std::vector &ids, + int outerWidth); + + struct Entry final { + int id = 0; + bool enabled = 0; + }; + [[nodiscard]] rpl::producer buttonEnabledChanges() const; + +private: + class FlatCheckbox; + + std::vector> _buttons; + + rpl::event_stream _buttonEnabledChanges; + +}; + +} // namespace Statistic diff --git a/Telegram/SourceFiles/statistics/statistics.style b/Telegram/SourceFiles/statistics/statistics.style index 0683f49f4..bbd06ea8e 100644 --- a/Telegram/SourceFiles/statistics/statistics.style +++ b/Telegram/SourceFiles/statistics/statistics.style @@ -31,6 +31,9 @@ statisticsChartBottomCaptionMaxWidth: 44px; statisticsChartFooterSkip: 10px; statisticsChartFooterHeight: 52px; +statisticsChartFlatCheckboxMargins: margins(6px, 4px, 6px, 4px); +statisticsChartFlatCheckboxCheckWidth: 4px; + statisticsDetailsPopupStyle: TextStyle(defaultTextStyle) { font: font(11px); } diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 87436fffa..d9455b63c 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -159,6 +159,9 @@ PRIVATE platform/mac/file_bookmark_mac.mm platform/platform_file_bookmark.h + statistics/chart_lines_filter_widget.cpp + statistics/chart_lines_filter_widget.h + ui/boxes/auto_delete_settings.cpp ui/boxes/auto_delete_settings.h ui/boxes/boost_box.cpp