diff --git a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_setup_widget.cpp b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_setup_widget.cpp index 5753e2c24..88a09f7bf 100644 --- a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_setup_widget.cpp +++ b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_setup_widget.cpp @@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/labels.h" #include "ui/wrap/fade_wrap.h" #include "ui/wrap/vertical_layout.h" +#include "ui/painter.h" #include "ui/ui_utility.h" #include "ui/vertical_list.h" #include "styles/style_info.h" @@ -39,6 +40,7 @@ namespace { constexpr auto kDurationForeverValue = 999; constexpr auto kCommissionDefault = 200; constexpr auto kDurationDefault = 12; +constexpr auto kDisabledFade = 0.3; } // namespace @@ -113,7 +115,8 @@ private: int value, Fn valueProgress, Fn valueFinished, - Fn textByValue) { + Fn textByValue, + bool forbidLessThanValue) { auto result = object_ptr(parent); const auto raw = result.data(); @@ -133,14 +136,23 @@ private: const auto slider = raw->add(object_ptr( raw, *sliderStyle)); - labels->resize(labels->width(), current->height()); + labels->resize( + labels->width(), + current->height() + st::defaultVerticalListSkip); struct State { + int indexMin = 0; int index = 0; }; - const auto state = raw->lifetime().make_state(State{ 0 }); + const auto state = raw->lifetime().make_state(); const auto updatePalette = [=] { + const auto disabled = anim::color( + st::windowSubTextFg, + st::windowBg, + kDisabledFade); min->setTextColorOverride(!state->index ? st::windowActiveTextFg->c + : (state->indexMin > 0) + ? disabled : st::windowSubTextFg->c); max->setTextColorOverride((state->index == valuesCount - 1) ? st::windowActiveTextFg->c @@ -207,15 +219,200 @@ private: updatePalette(); }, raw->lifetime()); updateByValue(value); + state->indexMin = forbidLessThanValue ? state->index : 0; slider->setPseudoDiscrete( valuesCount, valueByIndex, value, progress, - finished); + finished, + state->indexMin); slider->resize(slider->width(), sliderStyle->seekSize.height()); + if (state->indexMin > 0) { + const auto overlay = Ui::CreateChild(slider); + overlay->setAttribute(Qt::WA_TransparentForMouseEvents); + slider->sizeValue() | rpl::start_with_next([=](QSize size) { + overlay->setGeometry(0, 0, size.width(), size.height()); + }, slider->lifetime()); + overlay->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(overlay); + const auto sections = valuesCount - 1; + const auto shift = sliderStyle->seekSize.width(); + const auto skip = shift / 2.; + const auto available = overlay->width() - shift; + const auto till = state->indexMin / float64(sections); + const auto now = state->index / float64(sections); + const auto edge = available * now; + const auto right = int(base::SafeRound( + std::min(skip + available * till, edge))); + if (right > 0) { + p.setOpacity(kDisabledFade); + p.fillRect(0, 0, right, overlay->height(), st::windowBg); + } + }, overlay->lifetime()); + } + + raw->widthValue() | rpl::start_with_next([=](int width) { + labels->resizeToWidth(width); + updateByIndex(); + }, slider->lifetime()); + + return result; +} + +[[nodiscard]] object_ptr MakeSliderWithTopLabels( + QWidget *parent, + not_null sliderStyle, + not_null labelStyle, + int valuesCount, + Fn valueByIndex, + int value, + Fn valueProgress, + Fn valueFinished, + Fn textByValue, + bool forbidLessThanValue) { + auto result = object_ptr(parent); + const auto raw = result.data(); + + const auto labels = raw->add(object_ptr(raw)); + const auto slider = raw->add(object_ptr( + raw, + *sliderStyle)); + + struct State { + std::vector> labels; + int indexMin = 0; + int index = 0; + }; + const auto state = raw->lifetime().make_state(); + + for (auto i = 0; i != valuesCount; ++i) { + state->labels.push_back(Ui::CreateChild( + labels, + textByValue(valueByIndex(i)), + *labelStyle)); + } + labels->widthValue() | rpl::start_with_next([=](int outer) { + const auto shift = sliderStyle->seekSize.width() / 2; + const auto available = outer - sliderStyle->seekSize.width(); + for (auto i = 0; i != state->labels.size(); ++i) { + const auto label = state->labels[i]; + const auto width = label->width(); + const auto half = width / 2; + const auto progress = (i / float64(valuesCount - 1)); + const auto left = int(base::SafeRound(progress * available)); + label->moveToLeft( + std::max(std::min(shift + left - half, outer - width), 0), + 0, + outer); + } + }, slider->lifetime()); + labels->resize( + labels->width(), + state->labels.back()->height() + st::defaultVerticalListSkip); + + const auto updatePalette = [=] { + const auto disabled = anim::color( + st::windowSubTextFg, + st::windowBg, + kDisabledFade); + for (auto i = 0; i != state->labels.size(); ++i) { + state->labels[i]->setTextColorOverride((state->index == i) + ? st::windowActiveTextFg->c + : (state->index < state->indexMin) + ? disabled + : st::windowSubTextFg->c); + } + }; + const auto updateByIndex = [=] { + updatePalette(); + }; + const auto updateByValue = [=](int value) { + state->index = 0; + auto maxIndex = valuesCount - 1; + while (state->index < maxIndex) { + const auto mid = (state->index + maxIndex) / 2; + const auto midValue = valueByIndex(mid); + if (midValue == value) { + state->index = mid; + break; + } else if (midValue < value) { + state->index = mid + 1; + } else { + maxIndex = mid - 1; + } + } + updateByIndex(); + }; + const auto progress = [=](int value) { + updateByValue(value); + valueProgress(value); + }; + const auto finished = [=](int value) { + updateByValue(value); + valueFinished(value); + }; + style::PaletteChanged() | rpl::start_with_next([=] { + updatePalette(); + }, raw->lifetime()); + updateByValue(value); + state->indexMin = forbidLessThanValue ? state->index : 0; + + slider->setPseudoDiscrete( + valuesCount, + valueByIndex, + value, + progress, + finished, + state->indexMin); + slider->resize(slider->width(), sliderStyle->seekSize.height()); + + if (state->indexMin > 0) { + const auto overlay = Ui::CreateChild(slider); + overlay->setAttribute(Qt::WA_TransparentForMouseEvents); + slider->sizeValue() | rpl::start_with_next([=](QSize size) { + overlay->setGeometry(0, 0, size.width(), size.height()); + }, slider->lifetime()); + overlay->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(overlay); + + const auto sections = valuesCount - 1; + const auto shift = sliderStyle->seekSize.width(); + const auto skip = shift / 2.; + const auto available = overlay->width() - shift; + auto hq = PainterHighQualityEnabler(p); + const auto stroke = style::ConvertScale(3); + p.setPen(QPen(st::windowBg, stroke)); + const auto diameter = shift - stroke; + const auto radius = diameter / 2.; + const auto top = (sliderStyle->seekSize.height() / 2.) - radius; + for (auto i = 0; i != valuesCount; ++i) { + if (i < state->index) { + p.setBrush(st::sliderBgActive); + } else if (i > state->index) { + p.setBrush(st::sliderBgInactive); + } else { + continue; + } + const auto progress = i / float64(sections); + const auto position = skip + available * progress; + p.drawEllipse(position - radius, top, diameter, diameter); + } + + const auto till = state->indexMin / float64(sections); + const auto now = state->index / float64(sections); + const auto edge = available * now; + const auto right = int(base::SafeRound( + std::min(skip + available * till + radius, edge))); + if (right > 0) { + p.setOpacity(kDisabledFade); + p.fillRect(0, 0, right, overlay->height(), st::windowBg); + } + }, overlay->lifetime()); + } + raw->widthValue() | rpl::start_with_next([=](int width) { labels->resizeToWidth(width); updateByIndex(); @@ -298,10 +495,11 @@ void InnerWidget::setupCommission() { commission, setCommission, setCommission, - [=](int value) { return QString::number(value / 10.) + '%'; }), + [=](int value) { return QString::number(value / 10.) + '%'; }, + _state.exists), st::boxRowPadding); - Ui::AddSkip(_container); + Ui::AddSkip(_container, st::defaultVerticalListSkip * 2); Ui::AddDividerText(_container, tr::lng_star_ref_commission_about()); } @@ -309,47 +507,41 @@ void InnerWidget::setupDuration() { Ui::AddSkip(_container); Ui::AddSubsectionTitle(_container, tr::lng_star_ref_duration_title()); - auto values = std::vector{ 1, 3, 6, 12, 24, 36, 999 }; - const auto valuesCount = int(values.size()); - - auto sliderWithLabel = ::Settings::MakeSliderWithLabel( - _container, - st::settingsScale, - st::settingsScaleLabel, - st::normalFont->spacew * 2, - st::settingsScaleLabel.style.font->width("3y"), - true); - _container->add( - std::move(sliderWithLabel.widget), - st::settingsBigScalePadding); - const auto slider = sliderWithLabel.slider; - const auto label = sliderWithLabel.label; - - const auto updateLabel = [=](int value) { - const auto labelText = (value < 12) - ? (QString::number(value) + 'm') - : (value < 999) - ? (QString::number(value / 12) + 'y') - : u"inf"_q; - label->setText(labelText); - }; const auto durationMonths = ValueForDurationMonths(_state); + + auto values = std::vector{ 1, 3, 6, 12, 24, 36, 999 }; + if (!ranges::contains(values, durationMonths)) { + values.push_back(durationMonths); + ranges::sort(values); + } + const auto valuesCount = int(values.size()); const auto setDurationMonths = [=](int value) { _state.program.durationMonths = (value == kDurationForeverValue) ? 0 : value; - updateLabel(value); }; - updateLabel(durationMonths); + const auto label = [=](int value) { + return (value < 12) + ? (QString::number(value) + 'm') + : (value < 999) + ? (QString::number(value / 12) + 'y') + : QString::fromUtf8("\xE2\x88\x9E"); // utf-8 infinity + }; + _container->add( + MakeSliderWithTopLabels( + _container, + &st::settingsScale, + &st::settingsScaleLabel, + valuesCount, + [=](int index) { return values[index]; }, + durationMonths, + setDurationMonths, + setDurationMonths, + label, + _state.exists), + st::boxRowPadding); - slider->setPseudoDiscrete( - valuesCount, - [=](int index) { return values[index]; }, - durationMonths, - setDurationMonths, - setDurationMonths); - - Ui::AddSkip(_container); + Ui::AddSkip(_container, st::defaultVerticalListSkip * 2); Ui::AddDividerText(_container, tr::lng_star_ref_duration_about()); } diff --git a/Telegram/SourceFiles/ui/widgets/continuous_sliders.h b/Telegram/SourceFiles/ui/widgets/continuous_sliders.h index 97839b4b1..cec921c8c 100644 --- a/Telegram/SourceFiles/ui/widgets/continuous_sliders.h +++ b/Telegram/SourceFiles/ui/widgets/continuous_sliders.h @@ -156,7 +156,8 @@ public: int valuesCount, Convert &&convert, Value current, - Progress &&progress) { + Progress &&progress, + int indexMin = 0) { Expects(valuesCount > 1); setAlwaysDisplayMarker(true); @@ -171,10 +172,15 @@ public: } } setAdjustCallback([=](float64 value) { - return base::SafeRound(value * sectionsCount) / sectionsCount; + return std::max( + base::SafeRound(value * sectionsCount), + indexMin * 1. + ) / sectionsCount; }); setChangeProgressCallback([=](float64 value) { - const auto index = int(base::SafeRound(value * sectionsCount)); + const auto index = std::max( + int(base::SafeRound(value * sectionsCount)), + indexMin); progress(convert(index)); }); } @@ -193,15 +199,19 @@ public: Convert &&convert, Value current, Progress &&progress, - Finished &&finished) { + Finished &&finished, + int indexMin = 0) { setPseudoDiscrete( valuesCount, std::forward(convert), current, - std::forward(progress)); + std::forward(progress), + indexMin); setChangeFinishedCallback([=](float64 value) { const auto sectionsCount = (valuesCount - 1); - const auto index = int(base::SafeRound(value * sectionsCount)); + const auto index = std::max( + int(base::SafeRound(value * sectionsCount)), + indexMin); finished(convert(index)); }); }