init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
507
Telegram/SourceFiles/ui/widgets/discrete_sliders.cpp
Normal file
507
Telegram/SourceFiles/ui/widgets/discrete_sliders.cpp
Normal file
@@ -0,0 +1,507 @@
|
||||
/*
|
||||
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 "ui/widgets/discrete_sliders.h"
|
||||
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
DiscreteSlider::DiscreteSlider(QWidget *parent, bool snapToLabel)
|
||||
: RpWidget(parent)
|
||||
, _snapToLabel(snapToLabel) {
|
||||
setCursor(style::cur_pointer);
|
||||
}
|
||||
|
||||
DiscreteSlider::~DiscreteSlider() = default;
|
||||
|
||||
void DiscreteSlider::setActiveSection(int index) {
|
||||
_activeIndex = index;
|
||||
activateCallback();
|
||||
setSelectedSection(index);
|
||||
}
|
||||
|
||||
void DiscreteSlider::activateCallback() {
|
||||
if (_timerId >= 0) {
|
||||
killTimer(_timerId);
|
||||
_timerId = -1;
|
||||
}
|
||||
auto ms = crl::now();
|
||||
if (ms >= _callbackAfterMs) {
|
||||
_sectionActivated.fire_copy(_activeIndex);
|
||||
} else {
|
||||
_timerId = startTimer(_callbackAfterMs - ms, Qt::PreciseTimer);
|
||||
}
|
||||
}
|
||||
|
||||
void DiscreteSlider::timerEvent(QTimerEvent *e) {
|
||||
activateCallback();
|
||||
}
|
||||
|
||||
void DiscreteSlider::setActiveSectionFast(int index) {
|
||||
setActiveSection(index);
|
||||
finishAnimating();
|
||||
}
|
||||
|
||||
void DiscreteSlider::finishAnimating() {
|
||||
_a_left.stop();
|
||||
_a_width.stop();
|
||||
update();
|
||||
_callbackAfterMs = 0;
|
||||
if (_timerId >= 0) {
|
||||
activateCallback();
|
||||
}
|
||||
}
|
||||
|
||||
void DiscreteSlider::setAdditionalContentWidthToSection(int index, int w) {
|
||||
if (index >= 0 && index < _sections.size()) {
|
||||
auto §ion = _sections[index];
|
||||
section.contentWidth = section.label.maxWidth() + w;
|
||||
}
|
||||
}
|
||||
|
||||
int DiscreteSlider::sectionsCount() const {
|
||||
return int(_sections.size());
|
||||
}
|
||||
|
||||
int DiscreteSlider::lookupSectionLeft(int index) const {
|
||||
Expects(index >= 0 && index < _sections.size());
|
||||
|
||||
return _sections[index].left;
|
||||
}
|
||||
|
||||
void DiscreteSlider::setSelectOnPress(bool selectOnPress) {
|
||||
_selectOnPress = selectOnPress;
|
||||
}
|
||||
|
||||
bool DiscreteSlider::paused() const {
|
||||
return _paused && _paused();
|
||||
}
|
||||
|
||||
std::vector<DiscreteSlider::Section> &DiscreteSlider::sectionsRef() {
|
||||
return _sections;
|
||||
}
|
||||
|
||||
void DiscreteSlider::addSection(const QString &label) {
|
||||
_sections.push_back(Section(label, getLabelStyle()));
|
||||
resizeToWidth(width());
|
||||
}
|
||||
|
||||
void DiscreteSlider::addSection(
|
||||
const TextWithEntities &label,
|
||||
Text::MarkedContext context) {
|
||||
context.repaint = [this] { update(); };
|
||||
_sections.push_back(Section(label, getLabelStyle(), context));
|
||||
resizeToWidth(width());
|
||||
}
|
||||
|
||||
void DiscreteSlider::setSections(const std::vector<QString> &labels) {
|
||||
Assert(!labels.empty());
|
||||
|
||||
_sections.clear();
|
||||
for (const auto &label : labels) {
|
||||
_sections.push_back(Section(label, getLabelStyle()));
|
||||
}
|
||||
refresh();
|
||||
}
|
||||
|
||||
void DiscreteSlider::setSections(
|
||||
const std::vector<TextWithEntities> &labels,
|
||||
Text::MarkedContext context,
|
||||
Fn<bool()> paused) {
|
||||
Assert(!labels.empty());
|
||||
|
||||
context.repaint = [this] { update(); };
|
||||
|
||||
_sections.clear();
|
||||
for (const auto &label : labels) {
|
||||
_sections.push_back(Section(label, getLabelStyle(), context));
|
||||
}
|
||||
_paused = std::move(paused);
|
||||
refresh();
|
||||
}
|
||||
|
||||
void DiscreteSlider::refresh() {
|
||||
stopAnimation();
|
||||
if (_activeIndex >= _sections.size()) {
|
||||
_activeIndex = 0;
|
||||
}
|
||||
if (_selected >= _sections.size()) {
|
||||
_selected = 0;
|
||||
}
|
||||
resizeToWidth(width());
|
||||
update();
|
||||
}
|
||||
|
||||
DiscreteSlider::Range DiscreteSlider::getFinalActiveRange() const {
|
||||
const auto raw = (_sections.empty() || _selected < 0)
|
||||
? nullptr
|
||||
: &_sections[_selected];
|
||||
if (!raw) {
|
||||
return { 0, 0 };
|
||||
}
|
||||
const auto width = _snapToLabel
|
||||
? std::min(raw->width, raw->contentWidth)
|
||||
: raw->width;
|
||||
return { raw->left + ((raw->width - width) / 2), width };
|
||||
}
|
||||
|
||||
DiscreteSlider::Range DiscreteSlider::getCurrentActiveRange() const {
|
||||
const auto to = getFinalActiveRange();
|
||||
return {
|
||||
int(base::SafeRound(_a_left.value(to.left))),
|
||||
int(base::SafeRound(_a_width.value(to.width))),
|
||||
};
|
||||
}
|
||||
|
||||
void DiscreteSlider::enumerateSections(Fn<bool(Section&)> callback) {
|
||||
for (auto §ion : _sections) {
|
||||
if (!callback(section)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DiscreteSlider::enumerateSections(
|
||||
Fn<bool(const Section&)> callback) const {
|
||||
for (const auto §ion : _sections) {
|
||||
if (!callback(section)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DiscreteSlider::mousePressEvent(QMouseEvent *e) {
|
||||
const auto index = getIndexFromPosition(e->pos());
|
||||
if (_selectOnPress) {
|
||||
setSelectedSection(index);
|
||||
}
|
||||
startRipple(index);
|
||||
_pressed = index;
|
||||
}
|
||||
|
||||
void DiscreteSlider::mouseMoveEvent(QMouseEvent *e) {
|
||||
if (_pressed < 0) {
|
||||
return;
|
||||
}
|
||||
if (_selectOnPress) {
|
||||
setSelectedSection(getIndexFromPosition(e->pos()));
|
||||
}
|
||||
}
|
||||
|
||||
void DiscreteSlider::mouseReleaseEvent(QMouseEvent *e) {
|
||||
const auto pressed = std::exchange(_pressed, -1);
|
||||
if (pressed < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto index = getIndexFromPosition(e->pos());
|
||||
if (pressed < _sections.size()) {
|
||||
if (_sections[pressed].ripple) {
|
||||
_sections[pressed].ripple->lastStop();
|
||||
}
|
||||
}
|
||||
if (_selectOnPress || index == pressed) {
|
||||
setActiveSection(index);
|
||||
}
|
||||
}
|
||||
|
||||
void DiscreteSlider::setSelectedSection(int index) {
|
||||
if (index >= int(_sections.size())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_selected != index) {
|
||||
const auto from = getFinalActiveRange();
|
||||
_selected = index;
|
||||
const auto to = getFinalActiveRange();
|
||||
const auto duration = getAnimationDuration();
|
||||
const auto updater = [this] { update(); };
|
||||
_a_left.start(updater, from.left, to.left, duration);
|
||||
_a_width.start(updater, from.width, to.width, duration);
|
||||
_callbackAfterMs = crl::now() + duration;
|
||||
}
|
||||
}
|
||||
|
||||
int DiscreteSlider::getIndexFromPosition(QPoint pos) {
|
||||
const auto count = _sections.size();
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
if (_sections[i].left + _sections[i].width > pos.x()) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return count - 1;
|
||||
}
|
||||
|
||||
DiscreteSlider::Section::Section(
|
||||
const QString &label,
|
||||
const style::TextStyle &st)
|
||||
: label(st, label)
|
||||
, contentWidth(Section::label.maxWidth()) {
|
||||
}
|
||||
|
||||
DiscreteSlider::Section::Section(
|
||||
const TextWithEntities &label,
|
||||
const style::TextStyle &st,
|
||||
const Text::MarkedContext &context) {
|
||||
this->label.setMarkedText(st, label, kMarkupTextOptions, context);
|
||||
contentWidth = Section::label.maxWidth();
|
||||
}
|
||||
|
||||
SettingsSlider::SettingsSlider(
|
||||
QWidget *parent,
|
||||
const style::SettingsSlider &st)
|
||||
: DiscreteSlider(parent, st.barSnapToLabel)
|
||||
, _st(st) {
|
||||
if (_st.barRadius > 0) {
|
||||
_bar.emplace(_st.barRadius, _st.barFg);
|
||||
_barActive.emplace(_st.barRadius, _st.barFgActive);
|
||||
}
|
||||
setSelectOnPress(_st.ripple.showDuration == 0);
|
||||
}
|
||||
|
||||
const style::SettingsSlider &SettingsSlider::st() const {
|
||||
return _st;
|
||||
}
|
||||
|
||||
int SettingsSlider::centerOfSection(int section) const {
|
||||
const auto widths = countSectionsWidths(0);
|
||||
auto result = 0;
|
||||
if (section >= 0 && section < widths.size()) {
|
||||
for (auto i = 0; i < section; i++) {
|
||||
result += widths[i];
|
||||
}
|
||||
result += widths[section] / 2;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void SettingsSlider::fitWidthToSections() {
|
||||
const auto widths = countSectionsWidths(0);
|
||||
resizeToWidth(ranges::accumulate(widths, .0) + _st.padding * 2);
|
||||
}
|
||||
|
||||
void SettingsSlider::setRippleTopRoundRadius(int radius) {
|
||||
_rippleTopRoundRadius = radius;
|
||||
}
|
||||
|
||||
const style::TextStyle &SettingsSlider::getLabelStyle() const {
|
||||
return _st.labelStyle;
|
||||
}
|
||||
|
||||
int SettingsSlider::getAnimationDuration() const {
|
||||
return _st.duration;
|
||||
}
|
||||
|
||||
void SettingsSlider::resizeSections(int newWidth) {
|
||||
const auto count = getSectionsCount();
|
||||
if (!count) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto sectionWidths = countSectionsWidths(newWidth);
|
||||
|
||||
auto skip = 0;
|
||||
auto x = _st.padding * 1.;
|
||||
auto sectionWidth = sectionWidths.begin();
|
||||
enumerateSections([&](Section §ion) {
|
||||
Expects(sectionWidth != sectionWidths.end());
|
||||
|
||||
section.left = std::floor(x) + skip;
|
||||
x += *sectionWidth;
|
||||
section.width = qRound(x) - (section.left - skip);
|
||||
skip += _st.barSkip;
|
||||
++sectionWidth;
|
||||
return true;
|
||||
});
|
||||
stopAnimation();
|
||||
}
|
||||
|
||||
std::vector<float64> SettingsSlider::countSectionsWidths(int newWidth) const {
|
||||
const auto count = getSectionsCount();
|
||||
const auto sectionsWidth = newWidth
|
||||
- 2 * _st.padding
|
||||
- (count - 1) * _st.barSkip;
|
||||
const auto sectionWidth = sectionsWidth / float64(count);
|
||||
|
||||
auto result = std::vector<float64>(count, sectionWidth);
|
||||
auto labelsWidth = 0;
|
||||
auto commonWidth = true;
|
||||
enumerateSections([&](const Section §ion) {
|
||||
labelsWidth += section.contentWidth;
|
||||
if (section.contentWidth >= sectionWidth) {
|
||||
commonWidth = false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
// If labelsWidth > sectionsWidth we're screwed anyway.
|
||||
if (_st.strictSkip || (!commonWidth && labelsWidth <= sectionsWidth)) {
|
||||
auto padding = _st.strictSkip
|
||||
? (_st.strictSkip / 2.)
|
||||
: (sectionsWidth - labelsWidth) / (2. * count);
|
||||
auto currentWidth = result.begin();
|
||||
enumerateSections([&](const Section §ion) {
|
||||
Expects(currentWidth != result.end());
|
||||
|
||||
*currentWidth = padding + section.contentWidth + padding;
|
||||
++currentWidth;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int SettingsSlider::resizeGetHeight(int newWidth) {
|
||||
resizeSections(newWidth);
|
||||
return _st.height;
|
||||
}
|
||||
|
||||
void SettingsSlider::startRipple(int sectionIndex) {
|
||||
if (!_st.ripple.showDuration) {
|
||||
return;
|
||||
}
|
||||
auto index = 0;
|
||||
enumerateSections([this, &index, sectionIndex](Section §ion) {
|
||||
if (index++ == sectionIndex) {
|
||||
if (!section.ripple) {
|
||||
auto mask = prepareRippleMask(sectionIndex, section);
|
||||
section.ripple = std::make_unique<RippleAnimation>(
|
||||
_st.ripple,
|
||||
std::move(mask),
|
||||
[this] { update(); });
|
||||
}
|
||||
const auto point = mapFromGlobal(QCursor::pos());
|
||||
section.ripple->add(point - QPoint(section.left, 0));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
QImage SettingsSlider::prepareRippleMask(
|
||||
int sectionIndex,
|
||||
const Section §ion) {
|
||||
const auto size = QSize(section.width, height() - _st.rippleBottomSkip);
|
||||
if (!_rippleTopRoundRadius
|
||||
|| (sectionIndex > 0 && sectionIndex + 1 < getSectionsCount())) {
|
||||
return RippleAnimation::RectMask(size);
|
||||
}
|
||||
return RippleAnimation::MaskByDrawer(size, false, [&](QPainter &p) {
|
||||
const auto plusRadius = _rippleTopRoundRadius + 1;
|
||||
p.drawRoundedRect(
|
||||
0,
|
||||
0,
|
||||
section.width,
|
||||
height() + plusRadius,
|
||||
_rippleTopRoundRadius,
|
||||
_rippleTopRoundRadius);
|
||||
if (sectionIndex > 0) {
|
||||
p.fillRect(0, 0, plusRadius, plusRadius, p.brush());
|
||||
}
|
||||
if (sectionIndex + 1 < getSectionsCount()) {
|
||||
p.fillRect(
|
||||
section.width - plusRadius,
|
||||
0,
|
||||
plusRadius,
|
||||
plusRadius, p.brush());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SettingsSlider::paintEvent(QPaintEvent *e) {
|
||||
auto p = QPainter(this);
|
||||
|
||||
const auto clip = e->rect();
|
||||
const auto range = DiscreteSlider::getCurrentActiveRange();
|
||||
|
||||
const auto drawRect = [&](QRect rect, bool active = false) {
|
||||
const auto &bar = active ? _barActive : _bar;
|
||||
if (bar) {
|
||||
bar->paint(p, rect);
|
||||
} else {
|
||||
p.fillRect(rect, active ? _st.barFgActive : _st.barFg);
|
||||
}
|
||||
};
|
||||
enumerateSections([&](Section §ion) {
|
||||
const auto activeWidth = _st.barSnapToLabel
|
||||
? section.contentWidth
|
||||
: section.width;
|
||||
const auto activeLeft = section.left
|
||||
+ (section.width - activeWidth) / 2;
|
||||
const auto divider = std::max(std::min(activeWidth, range.width), 1);
|
||||
const auto active = 1.
|
||||
- std::clamp(
|
||||
std::abs(range.left - activeLeft) / float64(divider),
|
||||
0.,
|
||||
1.);
|
||||
if (section.ripple) {
|
||||
const auto color = anim::color(
|
||||
_st.rippleBg,
|
||||
_st.rippleBgActive,
|
||||
active);
|
||||
section.ripple->paint(p, section.left, 0, width(), &color);
|
||||
if (section.ripple->empty()) {
|
||||
section.ripple.reset();
|
||||
}
|
||||
}
|
||||
if (!_st.barSnapToLabel) {
|
||||
auto from = activeLeft;
|
||||
auto tofill = activeWidth;
|
||||
if (range.left > from) {
|
||||
const auto fill = std::min(tofill, range.left - from);
|
||||
drawRect(myrtlrect(from, _st.barTop, fill, _st.barStroke));
|
||||
from += fill;
|
||||
tofill -= fill;
|
||||
}
|
||||
if (range.left + activeWidth > from) {
|
||||
const auto fill = std::min(
|
||||
tofill,
|
||||
range.left + activeWidth - from);
|
||||
if (fill) {
|
||||
drawRect(
|
||||
myrtlrect(from, _st.barTop, fill, _st.barStroke),
|
||||
true);
|
||||
from += fill;
|
||||
tofill -= fill;
|
||||
}
|
||||
}
|
||||
if (tofill) {
|
||||
drawRect(myrtlrect(from, _st.barTop, tofill, _st.barStroke));
|
||||
}
|
||||
}
|
||||
const auto labelLeft = section.left
|
||||
+ (section.width - section.contentWidth) / 2;
|
||||
const auto rect = myrtlrect(
|
||||
labelLeft,
|
||||
_st.labelTop,
|
||||
section.contentWidth,
|
||||
_st.labelStyle.font->height);
|
||||
if (rect.intersects(clip)) {
|
||||
p.setPen(anim::pen(_st.labelFg, _st.labelFgActive, active));
|
||||
section.label.draw(p, {
|
||||
.position = QPoint(labelLeft, _st.labelTop),
|
||||
.outerWidth = width(),
|
||||
.availableWidth = section.label.maxWidth(),
|
||||
.paused = paused(),
|
||||
});
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (_st.barSnapToLabel) {
|
||||
const auto add = _st.barStroke / 2;
|
||||
const auto from = std::max(range.left - add, 0);
|
||||
const auto till = std::min(range.left + range.width + add, width());
|
||||
if (from < till) {
|
||||
drawRect(
|
||||
myrtlrect(from, _st.barTop, till - from, _st.barStroke),
|
||||
true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
Reference in New Issue
Block a user