tdesktop/Telegram/SourceFiles/ui/widgets/discrete_sliders.cpp

317 lines
8.4 KiB
C++
Raw Normal View History

/*
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
*/
2016-11-22 09:48:13 +00:00
#include "ui/widgets/discrete_sliders.h"
2016-12-02 19:16:35 +00:00
#include "ui/effects/ripple_animation.h"
#include "styles/style_widgets.h"
namespace Ui {
2017-09-13 16:57:44 +00:00
DiscreteSlider::DiscreteSlider(QWidget *parent) : RpWidget(parent) {
setCursor(style::cur_pointer);
}
void DiscreteSlider::setActiveSection(int index) {
if (_activeIndex != index) {
_activeIndex = index;
2016-12-02 19:16:35 +00:00
activateCallback();
}
setSelectedSection(index);
}
void DiscreteSlider::activateCallback() {
if (_timerId >= 0) {
killTimer(_timerId);
_timerId = -1;
2016-12-02 19:16:35 +00:00
}
auto ms = crl::now();
2016-12-02 19:16:35 +00:00
if (ms >= _callbackAfterMs) {
2017-09-13 16:57:44 +00:00
_sectionActivated.fire_copy(_activeIndex);
2016-12-02 19:16:35 +00:00
} else {
_timerId = startTimer(_callbackAfterMs - ms, Qt::PreciseTimer);
}
2016-12-02 19:16:35 +00:00
}
void DiscreteSlider::timerEvent(QTimerEvent *e) {
activateCallback();
}
void DiscreteSlider::setActiveSectionFast(int index) {
setActiveSection(index);
finishAnimating();
2017-09-13 16:57:44 +00:00
}
void DiscreteSlider::finishAnimating() {
2016-11-22 09:48:13 +00:00
_a_left.finish();
update();
}
2016-11-22 09:48:13 +00:00
void DiscreteSlider::setSelectOnPress(bool selectOnPress) {
_selectOnPress = selectOnPress;
}
void DiscreteSlider::addSection(const QString &label) {
2016-11-22 09:48:13 +00:00
_sections.push_back(Section(label, getLabelFont()));
resizeToWidth(width());
}
2016-11-22 09:48:13 +00:00
void DiscreteSlider::setSections(const QStringList &labels) {
Assert(!labels.isEmpty());
2016-11-22 09:48:13 +00:00
_sections.clear();
for_const (auto &label, labels) {
_sections.push_back(Section(label, getLabelFont()));
}
stopAnimation();
if (_activeIndex >= _sections.size()) {
_activeIndex = 0;
}
if (_selected >= _sections.size()) {
_selected = 0;
}
resizeToWidth(width());
}
int DiscreteSlider::getCurrentActiveLeft(crl::time ms) {
const auto left = _sections.empty() ? 0 : _sections[_selected].left;
return _a_left.current(ms, left);
2016-11-22 09:48:13 +00:00
}
template <typename Lambda>
void DiscreteSlider::enumerateSections(Lambda callback) {
for (auto &section : _sections) {
2016-12-02 19:16:35 +00:00
if (!callback(section)) {
return;
}
}
}
2017-11-12 13:47:11 +00:00
template <typename Lambda>
void DiscreteSlider::enumerateSections(Lambda callback) const {
for (auto &section : _sections) {
if (!callback(section)) {
return;
}
}
}
void DiscreteSlider::mousePressEvent(QMouseEvent *e) {
2016-12-02 19:16:35 +00:00
auto index = getIndexFromPosition(e->pos());
2016-11-22 09:48:13 +00:00
if (_selectOnPress) {
2016-12-02 19:16:35 +00:00
setSelectedSection(index);
2016-11-22 09:48:13 +00:00
}
2016-12-02 19:16:35 +00:00
startRipple(index);
_pressed = index;
}
void DiscreteSlider::mouseMoveEvent(QMouseEvent *e) {
if (_pressed < 0) return;
2016-11-22 09:48:13 +00:00
if (_selectOnPress) {
setSelectedSection(getIndexFromPosition(e->pos()));
}
}
void DiscreteSlider::mouseReleaseEvent(QMouseEvent *e) {
auto pressed = std::exchange(_pressed, -1);
if (pressed < 0) return;
2016-12-02 19:16:35 +00:00
auto index = getIndexFromPosition(e->pos());
if (pressed < _sections.size()) {
if (_sections[pressed].ripple) {
_sections[pressed].ripple->lastStop();
2016-12-02 19:16:35 +00:00
}
}
if (_selectOnPress || index == pressed) {
setActiveSection(index);
}
}
void DiscreteSlider::setSelectedSection(int index) {
2016-11-22 09:48:13 +00:00
if (index < 0 || index >= _sections.size()) return;
if (_selected != index) {
2016-11-22 09:48:13 +00:00
auto from = _sections[_selected].left;
_selected = index;
2016-11-22 09:48:13 +00:00
auto to = _sections[_selected].left;
2016-12-02 19:16:35 +00:00
auto duration = getAnimationDuration();
_a_left.start([this] { update(); }, from, to, duration);
_callbackAfterMs = crl::now() + duration;
2016-11-22 09:48:13 +00:00
}
}
int DiscreteSlider::getIndexFromPosition(QPoint pos) {
int count = _sections.size();
for (int i = 0; i != count; ++i) {
if (_sections[i].left + _sections[i].width > pos.x()) {
return i;
}
}
2016-11-22 09:48:13 +00:00
return count - 1;
}
DiscreteSlider::Section::Section(const QString &label, const style::font &font)
: label(label)
, labelWidth(font->width(label)) {
}
SettingsSlider::SettingsSlider(QWidget *parent, const style::SettingsSlider &st) : DiscreteSlider(parent)
, _st(st) {
2016-12-02 19:16:35 +00:00
setSelectOnPress(_st.ripple.showDuration == 0);
2016-11-22 09:48:13 +00:00
}
void SettingsSlider::setRippleTopRoundRadius(int radius) {
_rippleTopRoundRadius = radius;
}
2016-11-22 09:48:13 +00:00
const style::font &SettingsSlider::getLabelFont() const {
return _st.labelFont;
}
int SettingsSlider::getAnimationDuration() const {
return _st.duration;
}
void SettingsSlider::resizeSections(int newWidth) {
auto count = getSectionsCount();
if (!count) return;
2017-11-12 13:47:11 +00:00
auto sectionWidths = countSectionsWidths(newWidth);
2016-11-22 09:48:13 +00:00
auto skip = 0;
auto x = 0.;
2017-11-12 13:47:11 +00:00
auto sectionWidth = sectionWidths.begin();
enumerateSections([&](Section &section) {
Expects(sectionWidth != sectionWidths.end());
2016-11-22 09:48:13 +00:00
section.left = qFloor(x) + skip;
2017-11-12 13:47:11 +00:00
x += *sectionWidth;
2016-11-22 09:48:13 +00:00
section.width = qRound(x) - (section.left - skip);
skip += _st.barSkip;
2017-11-12 13:47:11 +00:00
++sectionWidth;
2016-12-02 19:16:35 +00:00
return true;
2016-11-22 09:48:13 +00:00
});
stopAnimation();
}
2017-11-12 13:47:11 +00:00
std::vector<float64> SettingsSlider::countSectionsWidths(
int newWidth) const {
auto count = getSectionsCount();
auto sectionsWidth = newWidth - (count - 1) * _st.barSkip;
auto sectionWidth = sectionsWidth / float64(count);
auto result = std::vector<float64>(count, sectionWidth);
auto labelsWidth = 0;
auto commonWidth = true;
enumerateSections([&](const Section &section) {
labelsWidth += section.labelWidth;
if (section.labelWidth >= sectionWidth) {
commonWidth = false;
}
return true;
});
// If labelsWidth > sectionsWidth we're screwed anyway.
if (!commonWidth && labelsWidth <= sectionsWidth) {
auto padding = (sectionsWidth - labelsWidth) / (2. * count);
auto currentWidth = result.begin();
enumerateSections([&](const Section &section) {
Expects(currentWidth != result.end());
*currentWidth = padding + section.labelWidth + padding;
++currentWidth;
return true;
});
}
return result;
}
2016-11-22 09:48:13 +00:00
int SettingsSlider::resizeGetHeight(int newWidth) {
resizeSections(newWidth);
return _st.height;
}
void SettingsSlider::startRipple(int sectionIndex) {
2016-12-02 19:16:35 +00:00
if (!_st.ripple.showDuration) return;
auto index = 0;
enumerateSections([this, &index, sectionIndex](Section &section) {
if (index++ == sectionIndex) {
2016-12-02 19:16:35 +00:00
if (!section.ripple) {
auto mask = prepareRippleMask(sectionIndex, section);
section.ripple = std::make_unique<RippleAnimation>(
_st.ripple,
std::move(mask),
[this] { update(); });
2016-12-02 19:16:35 +00:00
}
const auto point = mapFromGlobal(QCursor::pos());
section.ripple->add(point - QPoint(section.left, 0));
2016-12-02 19:16:35 +00:00
return false;
}
return true;
});
}
QImage SettingsSlider::prepareRippleMask(int sectionIndex, const Section &section) {
auto size = QSize(section.width, height() - _st.rippleBottomSkip);
if (!_rippleTopRoundRadius || (sectionIndex > 0 && sectionIndex + 1 < getSectionsCount())) {
return RippleAnimation::rectMask(size);
}
return RippleAnimation::maskByDrawer(size, false, [this, sectionIndex, width = section.width](QPainter &p) {
auto plusRadius = _rippleTopRoundRadius + 1;
p.drawRoundedRect(0, 0, width, height() + plusRadius, _rippleTopRoundRadius, _rippleTopRoundRadius);
if (sectionIndex > 0) {
p.fillRect(0, 0, plusRadius, plusRadius, p.brush());
}
if (sectionIndex + 1 < getSectionsCount()) {
p.fillRect(width - plusRadius, 0, plusRadius, plusRadius, p.brush());
}
});
}
2016-11-22 09:48:13 +00:00
void SettingsSlider::paintEvent(QPaintEvent *e) {
Painter p(this);
auto clip = e->rect();
auto ms = crl::now();
2016-12-02 19:16:35 +00:00
auto activeLeft = getCurrentActiveLeft(ms);
2016-11-22 09:48:13 +00:00
p.setFont(_st.labelFont);
enumerateSections([this, &p, activeLeft, ms, clip](Section &section) {
auto active = 1. - snap(qAbs(activeLeft - section.left) / float64(section.width), 0., 1.);
2016-12-02 19:16:35 +00:00
if (section.ripple) {
auto color = anim::color(_st.rippleBg, _st.rippleBgActive, active);
section.ripple->paint(p, section.left, 0, width(), ms, &color);
2016-12-02 19:16:35 +00:00
if (section.ripple->empty()) {
section.ripple.reset();
}
}
auto from = section.left, tofill = section.width;
if (activeLeft > from) {
auto fill = qMin(tofill, activeLeft - from);
2016-11-22 09:48:13 +00:00
p.fillRect(myrtlrect(from, _st.barTop, fill, _st.barStroke), _st.barFg);
from += fill;
tofill -= fill;
}
if (activeLeft + section.width > from) {
if (auto fill = qMin(tofill, activeLeft + section.width - from)) {
2016-11-22 09:48:13 +00:00
p.fillRect(myrtlrect(from, _st.barTop, fill, _st.barStroke), _st.barFgActive);
from += fill;
tofill -= fill;
}
}
if (tofill) {
2016-11-22 09:48:13 +00:00
p.fillRect(myrtlrect(from, _st.barTop, tofill, _st.barStroke), _st.barFg);
}
if (myrtlrect(section.left, _st.labelTop, section.width, _st.labelFont->height).intersects(clip)) {
p.setPen(anim::pen(_st.labelFg, _st.labelFgActive, active));
p.drawTextLeft(section.left + (section.width - section.labelWidth) / 2, _st.labelTop, width(), section.label, section.labelWidth);
}
2016-12-02 19:16:35 +00:00
return true;
2016-11-22 09:48:13 +00:00
});
}
} // namespace Ui