| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485 |
- /*
- 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();
- 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;
- }
- }
- void DiscreteSlider::setSelectOnPress(bool selectOnPress) {
- _selectOnPress = selectOnPress;
- }
- 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) {
- Assert(!labels.empty());
- context.repaint = [this] { update(); };
- _sections.clear();
- for (const auto &label : labels) {
- _sections.push_back(Section(label, getLabelStyle(), context));
- }
- refresh();
- }
- void DiscreteSlider::refresh() {
- stopAnimation();
- if (_activeIndex >= _sections.size()) {
- _activeIndex = 0;
- }
- if (_selected >= _sections.size()) {
- _selected = 0;
- }
- resizeToWidth(width());
- }
- DiscreteSlider::Range DiscreteSlider::getFinalActiveRange() const {
- const auto raw = _sections.empty() ? 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 < 0 || index >= _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 active = 1.
- - std::clamp(
- std::abs(range.left - activeLeft) / float64(range.width),
- 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(),
- });
- }
- 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
|