diff --git a/Telegram/SourceFiles/ui/boxes/calendar_box.cpp b/Telegram/SourceFiles/ui/boxes/calendar_box.cpp index f8e91fb896..25608d165b 100644 --- a/Telegram/SourceFiles/ui/boxes/calendar_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/calendar_box.cpp @@ -9,14 +9,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/buttons.h" #include "ui/widgets/scroll_area.h" +#include "ui/widgets/vertical_drum_picker.h" #include "ui/effects/ripple_animation.h" #include "ui/chat/chat_style.h" #include "ui/ui_utility.h" #include "ui/painter.h" #include "ui/cached_round_corners.h" +#include "ui/layers/generic_box.h" #include "lang/lang_keys.h" #include "styles/style_boxes.h" #include "styles/style_chat.h" +#include "styles/style_settings.h" +#include "styles/style_layers.h" #include @@ -32,6 +36,172 @@ constexpr auto kJumpDelay = 2 * crl::time(1000); return (kDaysInWeek + date.dayOfWeek() - firstDayOfWeek) % kDaysInWeek; } +void FillMonthYearPicker( + not_null box, + QDate current, + QDate minDate, + QDate maxDate, + const style::CalendarSizes &st, + Fn done) { + box->setWidth(st::boxWideWidth); + const auto content = box->addRow( + object_ptr(box, st::settingsWorkingHoursPicker)); + + const auto font = st::boxTextFont; + const auto itemHeight = st::settingsWorkingHoursPickerItemHeight; + const auto picker = [=]( + int count, + int startIndex, + Fn paint) { + auto paintCallback = [=]( + QPainter &p, + int index, + float64 y, + float64 distanceFromCenter, + int outerWidth) { + const auto r = QRectF(0, y, outerWidth, itemHeight); + const auto progress = std::abs(distanceFromCenter); + const auto revProgress = 1. - progress; + p.save(); + p.translate(r.center()); + constexpr auto kMinYScale = 0.2; + const auto yScale = kMinYScale + + (1. - kMinYScale) * anim::easeOutCubic(1., revProgress); + p.scale(1., yScale); + p.translate(-r.center()); + p.setOpacity(revProgress); + p.setFont(font); + p.setPen(st::defaultFlatLabel.textFg); + paint(p, r, index); + p.restore(); + }; + const auto result = CreateChild( + content, + std::move(paintCallback), + count, + itemHeight, + startIndex); + result->show(); + return result; + }; + + const auto effectiveMaxDate = maxDate.isValid() + ? maxDate + : QDate::currentDate().addDays(365); + const auto minYear = minDate.isValid() ? minDate.year() : 2013; + const auto maxYear = effectiveMaxDate.year(); + const auto yearsCount = maxYear - minYear + 1; + const auto yearsStartIndex = current.year() - minYear; + const auto yearsPaint = [=](QPainter &p, QRectF rect, int index) { + p.drawText(rect, QString::number(minYear + index), style::al_center); + }; + const auto years = picker(yearsCount, yearsStartIndex, yearsPaint); + + const auto monthsRange = [=](int year) { + auto minMonth = 1; + auto maxMonth = 12; + if (minDate.isValid() && minDate.year() == year) { + minMonth = minDate.month(); + } + if (maxDate.isValid() && maxDate.year() == year) { + maxMonth = maxDate.month(); + } + return std::make_pair(minMonth, maxMonth); + }; + + struct State { + base::unique_qptr months; + int currentMinMonth = 0; + int currentMaxMonth = 0; + }; + const auto state = box->lifetime().make_state(); + + const auto [minMonth, maxMonth] = monthsRange(current.year()); + const auto monthsCount = maxMonth - minMonth + 1; + const auto monthsStartIndex = current.month() - minMonth; + const auto monthsPaint = [=, minMonth = minMonth]( + QPainter &p, + QRectF rect, + int index) { + p.drawText( + rect, + Lang::Month(minMonth + index)(tr::now), + style::al_center); + }; + state->months = base::unique_qptr( + picker(monthsCount, monthsStartIndex, monthsPaint)); + state->currentMinMonth = minMonth; + state->currentMaxMonth = maxMonth; + + years->value( + ) | rpl::skip(1) | rpl::start_with_next([=](int yearIndex) { + const auto year = minYear + yearIndex; + const auto [newMinMonth, newMaxMonth] = monthsRange(year); + + if (newMinMonth != state->currentMinMonth + || newMaxMonth != state->currentMaxMonth) { + const auto newMonthsCount = newMaxMonth - newMinMonth + 1; + const auto oldMonth = state->currentMinMonth + + state->months->index(); + const auto clampedMonth = std::clamp( + oldMonth - newMinMonth, + 0, + newMonthsCount - 1); + const auto newMonthsPaint = [=, minMonth = newMinMonth]( + QPainter &p, + QRectF rect, + int index) { + p.drawText( + rect, + Lang::Month(minMonth + index)(tr::now), + style::al_center); + }; + state->months = base::unique_qptr( + picker(newMonthsCount, clampedMonth, newMonthsPaint)); + state->currentMinMonth = newMinMonth; + state->currentMaxMonth = newMaxMonth; + const auto s = content->size(); + if (s.isValid()) { + const auto half = s.width() / 2; + state->months->setGeometry(0, 0, half, s.height()); + } + } + }, box->lifetime()); + + content->sizeValue( + ) | rpl::start_with_next([=](QSize s) { + const auto half = s.width() / 2; + state->months->setGeometry(0, 0, half, s.height()); + years->setGeometry(half, 0, half, s.height()); + }, content->lifetime()); + + content->paintRequest( + ) | rpl::start_with_next([=](const QRect &r) { + auto p = QPainter(content); + p.fillRect(r, Qt::transparent); + const auto lineRect = QRect( + 0, + content->height() / 2, + content->width(), + st::defaultInputField.borderActive); + p.fillRect( + lineRect.translated(0, itemHeight / 2), + st::activeLineFg); + p.fillRect( + lineRect.translated(0, -itemHeight / 2), + st::activeLineFg); + }, content->lifetime()); + + box->addButton(tr::lng_gift_menu_show(), [=] { + const auto year = minYear + years->index(); + const auto [minMonth, maxMonth] = monthsRange(year); + const auto month = minMonth + state->months->index(); + done(QDate(year, month, 1)); + box->closeBox(); + }); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); +} + } // namespace class CalendarBox::Context { @@ -755,7 +925,7 @@ void CalendarBox::Inner::setDateChosenCallback(Fn callback) { CalendarBox::Inner::~Inner() = default; -class CalendarBox::Title final : public RpWidget { +class CalendarBox::Title final : public AbstractButton { public: Title( QWidget *parent, @@ -786,7 +956,7 @@ CalendarBox::Title::Title( not_null context, const style::CalendarSizes &st, const style::CalendarColors &styleColors) -: RpWidget(parent) +: AbstractButton(parent) , _st(st) , _styleColors(styleColors) , _context(context) { @@ -804,13 +974,18 @@ CalendarBox::Title::Title( ) | rpl::start_with_next([=] { if (!_context->selectionMode()) { setTextFromMonth(_context->month()); - } else if (!_context->selectedMin()) { - setText(tr::lng_calendar_select_days(tr::now)); + setAttribute(Qt::WA_TransparentForMouseEvents, false); } else { - setText(tr::lng_calendar_days( - tr::now, - lt_count, - (1 + *_context->selectedMax() - *_context->selectedMin()))); + setAttribute(Qt::WA_TransparentForMouseEvents, true); + if (!_context->selectedMin()) { + setText(tr::lng_calendar_select_days(tr::now)); + } else { + setText(tr::lng_calendar_days( + tr::now, + lt_count, + (1 + *_context->selectedMax() + - *_context->selectedMin()))); + } } }, lifetime()); } @@ -879,11 +1054,28 @@ CalendarBox::CalendarBox(QWidget*, CalendarBoxArgs &&args) , _finalize(std::move(args.finalize)) , _jumpTimer([=] { jump(_jumpButton); }) , _selectionChanged(std::move(args.selectionChanged)) { - _title->setAttribute(Qt::WA_TransparentForMouseEvents); _context->setAllowsSelection(args.allowsSelection); _context->setMinDate(args.minDate); _context->setMaxDate(args.maxDate); + _title->setClickedCallback([=, + minDate = args.minDate, + maxDate = args.maxDate] { + BoxContent::uiShow()->show(Box([=](not_null box) { + FillMonthYearPicker( + box, + _context->month(), + minDate, + maxDate, + _st, + [=](QDate date) { + _watchScroll = false; + _context->showMonth(date); + setExactScroll(); + }); + }), LayerOption::KeepOther); + }); + _scroll->scrolls( ) | rpl::filter([=] { return _watchScroll;