2016-10-05 18:37:48 +00:00
|
|
|
/*
|
|
|
|
This file is part of Telegram Desktop,
|
|
|
|
the official desktop version of Telegram messaging app, see https://telegram.org
|
|
|
|
|
|
|
|
Telegram Desktop is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
It is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
In addition, as a special exception, the copyright holders give permission
|
|
|
|
to link the code of portions of this program with the OpenSSL library.
|
|
|
|
|
|
|
|
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
2017-01-11 18:31:31 +00:00
|
|
|
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
2016-10-05 18:37:48 +00:00
|
|
|
*/
|
2016-11-22 09:48:13 +00:00
|
|
|
#include "ui/widgets/discrete_sliders.h"
|
2016-10-05 18:37:48 +00:00
|
|
|
|
2016-12-02 19:16:35 +00:00
|
|
|
#include "ui/effects/ripple_animation.h"
|
2016-10-05 18:37:48 +00:00
|
|
|
#include "styles/style_widgets.h"
|
|
|
|
|
|
|
|
namespace Ui {
|
|
|
|
|
2017-09-13 16:57:44 +00:00
|
|
|
DiscreteSlider::DiscreteSlider(QWidget *parent) : RpWidget(parent) {
|
2016-10-05 18:37:48 +00:00
|
|
|
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);
|
2017-03-19 08:29:05 +00:00
|
|
|
_timerId = -1;
|
2016-12-02 19:16:35 +00:00
|
|
|
}
|
|
|
|
auto ms = getms();
|
|
|
|
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-10-05 18:37:48 +00:00
|
|
|
}
|
2016-12-02 19:16:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DiscreteSlider::timerEvent(QTimerEvent *e) {
|
|
|
|
activateCallback();
|
2016-10-05 18:37:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DiscreteSlider::setActiveSectionFast(int index) {
|
|
|
|
setActiveSection(index);
|
2017-09-13 16:57:44 +00:00
|
|
|
finishAnimations();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DiscreteSlider::finishAnimations() {
|
2016-11-22 09:48:13 +00:00
|
|
|
_a_left.finish();
|
2016-10-05 18:37:48 +00:00
|
|
|
update();
|
|
|
|
}
|
|
|
|
|
2016-11-22 09:48:13 +00:00
|
|
|
void DiscreteSlider::setSelectOnPress(bool selectOnPress) {
|
|
|
|
_selectOnPress = selectOnPress;
|
|
|
|
}
|
|
|
|
|
2016-10-05 18:37:48 +00:00
|
|
|
void DiscreteSlider::addSection(const QString &label) {
|
2016-11-22 09:48:13 +00:00
|
|
|
_sections.push_back(Section(label, getLabelFont()));
|
|
|
|
resizeToWidth(width());
|
2016-10-05 18:37:48 +00:00
|
|
|
}
|
|
|
|
|
2016-11-22 09:48:13 +00:00
|
|
|
void DiscreteSlider::setSections(const QStringList &labels) {
|
2017-08-17 09:06:26 +00:00
|
|
|
Assert(!labels.isEmpty());
|
2016-10-05 18:37:48 +00:00
|
|
|
|
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());
|
|
|
|
}
|
|
|
|
|
2016-12-01 19:20:33 +00:00
|
|
|
int DiscreteSlider::getCurrentActiveLeft(TimeMs ms) {
|
2016-11-22 09:48:13 +00:00
|
|
|
return _a_left.current(ms, _sections.isEmpty() ? 0 : _sections[_selected].left);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename Lambda>
|
|
|
|
void DiscreteSlider::enumerateSections(Lambda callback) {
|
|
|
|
for (auto §ion : _sections) {
|
2016-12-02 19:16:35 +00:00
|
|
|
if (!callback(section)) {
|
|
|
|
return;
|
|
|
|
}
|
2016-10-05 18:37:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2016-12-05 08:45:56 +00:00
|
|
|
_pressed = index;
|
2016-10-05 18:37:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DiscreteSlider::mouseMoveEvent(QMouseEvent *e) {
|
2016-12-05 08:45:56 +00:00
|
|
|
if (_pressed < 0) return;
|
2016-11-22 09:48:13 +00:00
|
|
|
if (_selectOnPress) {
|
|
|
|
setSelectedSection(getIndexFromPosition(e->pos()));
|
|
|
|
}
|
2016-10-05 18:37:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DiscreteSlider::mouseReleaseEvent(QMouseEvent *e) {
|
2017-02-25 16:44:02 +00:00
|
|
|
auto pressed = std::exchange(_pressed, -1);
|
2016-12-05 08:45:56 +00:00
|
|
|
if (pressed < 0) return;
|
|
|
|
|
2016-12-02 19:16:35 +00:00
|
|
|
auto index = getIndexFromPosition(e->pos());
|
2016-12-05 08:45:56 +00:00
|
|
|
if (pressed < _sections.size()) {
|
|
|
|
if (_sections[pressed].ripple) {
|
|
|
|
_sections[pressed].ripple->lastStop();
|
2016-12-02 19:16:35 +00:00
|
|
|
}
|
|
|
|
}
|
2016-12-05 08:45:56 +00:00
|
|
|
if (_selectOnPress || index == pressed) {
|
|
|
|
setActiveSection(index);
|
|
|
|
}
|
2016-10-05 18:37:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DiscreteSlider::setSelectedSection(int index) {
|
2016-11-22 09:48:13 +00:00
|
|
|
if (index < 0 || index >= _sections.size()) return;
|
2016-10-05 18:37:48 +00:00
|
|
|
|
|
|
|
if (_selected != index) {
|
2016-11-22 09:48:13 +00:00
|
|
|
auto from = _sections[_selected].left;
|
2016-10-05 18:37:48 +00:00
|
|
|
_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 = getms() + 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-10-05 18:37:48 +00:00
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
2017-04-08 15:20:12 +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;
|
|
|
|
|
|
|
|
auto sectionsWidth = newWidth - (count - 1) * _st.barSkip;
|
|
|
|
auto sectionWidth = sectionsWidth / float64(count);
|
|
|
|
auto skip = 0;
|
|
|
|
auto x = 0.;
|
|
|
|
enumerateSections([this, &x, &skip, sectionWidth](Section §ion) {
|
|
|
|
section.left = qFloor(x) + skip;
|
|
|
|
x += sectionWidth;
|
|
|
|
section.width = qRound(x) - (section.left - skip);
|
|
|
|
skip += _st.barSkip;
|
2016-12-02 19:16:35 +00:00
|
|
|
return true;
|
2016-11-22 09:48:13 +00:00
|
|
|
});
|
|
|
|
stopAnimation();
|
2016-10-05 18:37:48 +00:00
|
|
|
}
|
|
|
|
|
2016-11-22 09:48:13 +00:00
|
|
|
int SettingsSlider::resizeGetHeight(int newWidth) {
|
|
|
|
resizeSections(newWidth);
|
|
|
|
return _st.height;
|
|
|
|
}
|
|
|
|
|
2016-12-13 17:07:56 +00:00
|
|
|
void SettingsSlider::startRipple(int sectionIndex) {
|
2016-12-02 19:16:35 +00:00
|
|
|
if (!_st.ripple.showDuration) return;
|
2016-12-13 17:07:56 +00:00
|
|
|
auto index = 0;
|
|
|
|
enumerateSections([this, &index, sectionIndex](Section §ion) {
|
|
|
|
if (index++ == sectionIndex) {
|
2016-12-02 19:16:35 +00:00
|
|
|
if (!section.ripple) {
|
2016-12-13 17:07:56 +00:00
|
|
|
auto mask = prepareRippleMask(sectionIndex, section);
|
2017-02-21 13:45:56 +00:00
|
|
|
section.ripple = MakeShared<RippleAnimation>(_st.ripple, std::move(mask), [this] { update(); });
|
2016-12-02 19:16:35 +00:00
|
|
|
}
|
|
|
|
section.ripple->add(mapFromGlobal(QCursor::pos()) - QPoint(section.left, 0));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-12-13 17:07:56 +00:00
|
|
|
QImage SettingsSlider::prepareRippleMask(int sectionIndex, const Section §ion) {
|
|
|
|
auto size = QSize(section.width, height() - _st.rippleBottomSkip);
|
2017-04-08 15:20:12 +00:00
|
|
|
if (!_rippleTopRoundRadius || (sectionIndex > 0 && sectionIndex + 1 < getSectionsCount())) {
|
2016-12-13 17:07:56 +00:00
|
|
|
return RippleAnimation::rectMask(size);
|
|
|
|
}
|
|
|
|
return RippleAnimation::maskByDrawer(size, false, [this, sectionIndex, width = section.width](QPainter &p) {
|
2017-04-08 15:20:12 +00:00
|
|
|
auto plusRadius = _rippleTopRoundRadius + 1;
|
|
|
|
p.drawRoundedRect(0, 0, width, height() + plusRadius, _rippleTopRoundRadius, _rippleTopRoundRadius);
|
2016-12-13 17:07:56 +00:00
|
|
|
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) {
|
2016-10-05 18:37:48 +00:00
|
|
|
Painter p(this);
|
|
|
|
|
2017-03-28 12:30:38 +00:00
|
|
|
auto clip = e->rect();
|
2016-12-02 19:16:35 +00:00
|
|
|
auto ms = getms();
|
|
|
|
auto activeLeft = getCurrentActiveLeft(ms);
|
2016-10-05 18:37:48 +00:00
|
|
|
|
2016-11-22 09:48:13 +00:00
|
|
|
p.setFont(_st.labelFont);
|
2017-03-28 12:30:38 +00:00
|
|
|
enumerateSections([this, &p, activeLeft, ms, clip](Section §ion) {
|
2016-12-05 08:45:56 +00:00
|
|
|
auto active = 1. - snap(qAbs(activeLeft - section.left) / float64(section.width), 0., 1.);
|
2016-12-02 19:16:35 +00:00
|
|
|
if (section.ripple) {
|
2016-12-05 08:45:56 +00:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
2016-10-05 18:37:48 +00:00
|
|
|
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);
|
2016-10-05 18:37:48 +00:00
|
|
|
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);
|
2016-10-05 18:37:48 +00:00
|
|
|
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);
|
2016-10-05 18:37:48 +00:00
|
|
|
}
|
2017-03-28 12:30:38 +00:00
|
|
|
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
|
|
|
});
|
2016-10-05 18:37:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace Ui
|