Improved style of widget for details of selected points on chart.

This commit is contained in:
23rd 2023-10-03 05:19:41 +03:00 committed by John Preston
parent 42fc4fbb31
commit 3425b40746
4 changed files with 157 additions and 68 deletions

View File

@ -1189,16 +1189,20 @@ void ChartWidget::setupDetails() {
currentXLimits.min,
currentXLimits.max,
_chartData.xPercentage[nearestXIndex]);
const auto xLeft = currentX
- _details.widget->width();
const auto widgetArea = _details.widget->width()
+ st::statisticsDetailsPopupPadding.left();
const auto xLeft = currentX - widgetArea;
const auto x = (xLeft >= 0)
? xLeft
: ((currentX
+ _details.widget->width()
- _chartArea->width()) > 0)
: ((currentX + widgetArea - _chartArea->width()) > 0)
? 0
: currentX;
_details.widget->moveToLeft(x, _chartArea->y());
_details.widget->moveToLeft(
std::clamp(
int(x),
_chartArea->x(),
rect::right(_chartArea) - widgetArea),
_chartArea->y());
_details.widget->setXIndex(nearestXIndex);
if (_details.widget->isHidden()) {
_details.hideOnAnimationEnd = false;

View File

@ -11,7 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/ripple_animation.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/widgets/shadow.h"
#include "styles/style_layers.h"
#include "styles/style_statistics.h"
@ -32,6 +31,37 @@ namespace {
}
}
void PaintShadow(QPainter &p, int radius, const QRect &r) {
constexpr auto kHorizontalOffset = 1;
constexpr auto kHorizontalOffset2 = 2;
constexpr auto kVerticalOffset = 2;
constexpr auto kVerticalOffset2 = 3;
constexpr auto kOpacityStep = 0.2;
constexpr auto kOpacityStep2 = 0.4;
const auto hOffset = style::ConvertScale(kHorizontalOffset);
const auto hOffset2 = style::ConvertScale(kHorizontalOffset2);
const auto vOffset = style::ConvertScale(kVerticalOffset);
const auto vOffset2 = style::ConvertScale(kVerticalOffset2);
const auto opacity = p.opacity();
auto hq = PainterHighQualityEnabler(p);
p.setOpacity(opacity);
p.drawRoundedRect(r + QMarginsF(0, hOffset, 0, hOffset), radius, radius);
p.setOpacity(opacity * kOpacityStep);
p.drawRoundedRect(r + QMarginsF(hOffset, 0, hOffset, 0), radius, radius);
p.setOpacity(opacity * kOpacityStep2);
p.drawRoundedRect(r
+ QMarginsF(hOffset2, 0, hOffset2, 0), radius, radius);
p.setOpacity(opacity * kOpacityStep);
p.drawRoundedRect(r + QMarginsF(0, 0, 0, vOffset), radius, radius);
p.setOpacity(opacity * kOpacityStep2);
p.drawRoundedRect(r + QMarginsF(0, 0, 0, vOffset2), radius, radius);
p.setOpacity(opacity);
}
} // namespace
void PaintDetails(
@ -67,7 +97,9 @@ void PaintDetails(
const auto innerRect = fullRect - st::statisticsDetailsPopupPadding;
const auto textRect = innerRect - st::statisticsDetailsPopupMargins;
Ui::Shadow::paint(p, innerRect, rect.width(), st::boxRoundShadow);
p.setBrush(st::shadowFg);
p.setPen(Qt::NoPen);
PaintShadow(p, st::boxRadius, innerRect);
Ui::FillRoundRect(p, innerRect, st::boxBg, Ui::BoxCorners);
const auto lineY = textRect.y();
@ -90,7 +122,7 @@ PointDetailsWidget::PointDetailsWidget(
const Data::StatisticalChart &chartData,
float64 maxAbsoluteValue,
bool zoomEnabled)
: Ui::RippleButton(parent, st::defaultRippleAnimation)
: Ui::AbstractButton(parent)
, _zoomEnabled(zoomEnabled)
, _chartData(chartData)
, _textStyle(st::statisticsDetailsPopupStyle)
@ -119,6 +151,7 @@ PointDetailsWidget::PointDetailsWidget(
p.drawLine(QLineF(s, s, w, w + s));
p.drawLine(QLineF(s, s + w * 2, w, w + s));
}
invalidateCache();
}, lifetime());
}
@ -162,6 +195,7 @@ PointDetailsWidget::PointDetailsWidget(
: Rect(s);
_innerRect = fullRect - st::statisticsDetailsPopupPadding;
_textRect = _innerRect - st::statisticsDetailsPopupMargins;
invalidateCache();
}, lifetime());
resize(calculatedWidth, height());
@ -171,11 +205,15 @@ PointDetailsWidget::PointDetailsWidget(
void PointDetailsWidget::setLineAlpha(int lineId, float64 alpha) {
for (auto &line : _lines) {
if (line.id == lineId) {
line.alpha = alpha;
if (line.alpha != alpha) {
line.alpha = alpha;
resizeHeight();
invalidateCache();
update();
}
return;
}
}
update();
resizeHeight();
}
void PointDetailsWidget::resizeHeight() {
@ -216,6 +254,7 @@ void PointDetailsWidget::setXIndex(int xIndex) {
setAttribute(
Qt::WA_TransparentForMouseEvents,
!clickable);
invalidateCache();
}
void PointDetailsWidget::setAlpha(float64 alpha) {
@ -242,61 +281,98 @@ int PointDetailsWidget::lineYAt(int index) const {
+ std::ceil(linesHeight);
}
void PointDetailsWidget::invalidateCache() {
_cache = QImage();
}
void PointDetailsWidget::mousePressEvent(QMouseEvent *e) {
AbstractButton::mousePressEvent(e);
const auto position = e->pos() - _innerRect.topLeft();
if (!_ripple) {
_ripple = std::make_unique<Ui::RippleAnimation>(
st::defaultRippleAnimation,
Ui::RippleAnimation::RoundRectMask(
_innerRect.size(),
st::boxRadius),
[=] { update(); });
}
_ripple->add(position);
}
void PointDetailsWidget::mouseReleaseEvent(QMouseEvent *e) {
AbstractButton::mouseReleaseEvent(e);
if (_ripple) {
_ripple->lastStop();
}
}
void PointDetailsWidget::paintEvent(QPaintEvent *e) {
auto p = QPainter(this);
auto painter = QPainter(this);
p.setOpacity(_alpha);
if (_cache.isNull()) {
_cache = QImage(
size() * style::DevicePixelRatio(),
QImage::Format_ARGB32_Premultiplied);
_cache.fill(Qt::transparent);
const auto fullRect = rect();
auto p = QPainter(&_cache);
Ui::Shadow::paint(p, _innerRect, width(), st::boxRoundShadow);
Ui::FillRoundRect(p, _innerRect, st::boxBg, Ui::BoxCorners);
Ui::RippleButton::paintRipple(p, _innerRect.topLeft());
p.setBrush(st::shadowFg);
p.setPen(Qt::NoPen);
PaintShadow(p, st::boxRadius, _innerRect);
Ui::FillRoundRect(p, _innerRect, st::boxBg, Ui::BoxCorners);
if (_ripple) {
_ripple->paint(p, _innerRect.left(), _innerRect.top(), width());
if (_ripple->empty()) {
_ripple.reset();
}
}
p.setPen(st::boxTextFg);
const auto headerContext = Ui::Text::PaintContext{
.position = _textRect.topLeft(),
.availableWidth = _textRect.width(),
};
_header.draw(p, headerContext);
for (auto i = 0; i < _lines.size(); i++) {
const auto &line = _lines[i];
const auto lineY = lineYAt(i);
const auto valueWidth = line.value.maxWidth();
const auto valueContext = Ui::Text::PaintContext{
.position = QPoint(rect::right(_textRect) - valueWidth, lineY),
.outerWidth = _textRect.width(),
.availableWidth = valueWidth,
};
const auto nameContext = Ui::Text::PaintContext{
.position = QPoint(_textRect.x(), lineY),
.outerWidth = _textRect.width(),
.availableWidth = _textRect.width() - valueWidth,
};
p.setOpacity(line.alpha * line.alpha * _alpha);
p.setPen(st::boxTextFg);
line.name.draw(p, nameContext);
p.setPen(line.valueColor);
line.value.draw(p, valueContext);
const auto headerContext = Ui::Text::PaintContext{
.position = _textRect.topLeft(),
.availableWidth = _textRect.width(),
};
_header.draw(p, headerContext);
for (auto i = 0; i < _lines.size(); i++) {
const auto &line = _lines[i];
const auto lineY = lineYAt(i);
const auto valueWidth = line.value.maxWidth();
const auto valueContext = Ui::Text::PaintContext{
.position = QPoint(
rect::right(_textRect) - valueWidth,
lineY),
.outerWidth = _textRect.width(),
.availableWidth = valueWidth,
};
const auto nameContext = Ui::Text::PaintContext{
.position = QPoint(_textRect.x(), lineY),
.outerWidth = _textRect.width(),
.availableWidth = _textRect.width() - valueWidth,
};
p.setOpacity(line.alpha * line.alpha);
p.setPen(st::boxTextFg);
line.name.draw(p, nameContext);
p.setPen(line.valueColor);
line.value.draw(p, valueContext);
}
if (_zoomEnabled) {
const auto s = _arrow.size() / style::DevicePixelRatio();
const auto x = rect::right(_textRect) - s.width();
const auto y = _textRect.y()
+ (_headerStyle.font->height - s.height()) / 2.;
p.drawImage(x, y, _arrow);
}
}
if (_zoomEnabled) {
const auto s = _arrow.size() / style::DevicePixelRatio();
const auto x = rect::right(_textRect) - s.width();
const auto y = _textRect.y()
+ (_headerStyle.font->height - s.height()) / 2.;
p.drawImage(x, y, _arrow);
if (_alpha < 1.) {
painter.setOpacity(_alpha);
}
painter.drawImage(0, 0, _cache);
if (_ripple) {
invalidateCache();
}
}
QPoint PointDetailsWidget::prepareRippleStartPosition() const {
return mapFromGlobal(QCursor::pos()) - _innerRect.topLeft();
}
QImage PointDetailsWidget::prepareRippleMask() const {
return Ui::RippleAnimation::RoundRectMask(
_innerRect.size(),
st::boxRadius);
}
} // namespace Statistic

View File

@ -8,7 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "data/data_statistics_chart.h"
#include "ui/widgets/buttons.h"
#include "ui/abstract_button.h"
namespace Ui {
class RippleAnimation;
} // namespace Ui
namespace Statistic {
@ -18,7 +22,7 @@ void PaintDetails(
int absoluteValue,
const QRect &rect);
class PointDetailsWidget : public Ui::RippleButton {
class PointDetailsWidget : public Ui::AbstractButton {
public:
PointDetailsWidget(
not_null<Ui::RpWidget*> parent,
@ -34,9 +38,8 @@ public:
protected:
void paintEvent(QPaintEvent *e) override;
QImage prepareRippleMask() const override;
QPoint prepareRippleStartPosition() const override;
void mousePressEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
private:
const bool _zoomEnabled;
@ -47,6 +50,8 @@ private:
const QString _shortFormat;
Ui::Text::String _header;
void invalidateCache();
[[nodiscard]] int lineYAt(int index) const;
void resizeHeight();
@ -63,11 +68,15 @@ private:
QRect _textRect;
QImage _arrow;
QImage _cache;
int _xIndex = -1;
float64 _alpha = 1.;
std::vector<Line> _lines;
std::unique_ptr<Ui::RippleAnimation> _ripple;
};
} // namespace Statistic

View File

@ -13,13 +13,13 @@ using "ui/widgets/widgets.style";
statisticsLayerOverviewMargins: margins(0px, 17px, 0px, 3px);
statisticsLayerMargins: margins(20px, 0px, 20px, 0px);
statisticsChartHeight: 150px;
statisticsChartHeight: 200px;
statisticsChartEntryPadding: margins(0px, 13px, 0px, 8px);
statisticsDetailsArrowShift: 2px;
statisticsDetailsArrowStroke: 1.5;
statisticsDetailsPopupMargins: margins(8px, 8px, 8px, 8px);
statisticsDetailsPopupMargins: margins(12px, 8px, 12px, 11px);
statisticsDetailsPopupPadding: margins(6px, 6px, 6px, 6px);
statisticsDetailsPopupMidLineSpace: 4px;
statisticsDetailsDotRadius: 5px;
@ -45,10 +45,10 @@ statisticsChartFlatCheckboxCheckWidth: 4px;
statisticsFilterButtonsPadding: margins(0px, 6px, 0px, 0px);
statisticsDetailsPopupHeaderStyle: TextStyle(defaultTextStyle) {
font: font(9px semibold);
font: font(12px semibold);
}
statisticsDetailsPopupStyle: TextStyle(defaultTextStyle) {
font: font(8px);
font: font(12px);
}
statisticsDetailsBottomCaptionStyle: TextStyle(defaultTextStyle) {
font: font(10px);