From 4414369fc86a52be7bf0267e86d38f240f0a5aaf Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 15 Nov 2021 13:09:44 +0400 Subject: [PATCH] Implement jump-to-start / end by long press in CalendarBox. --- Telegram/Resources/langs/lang.strings | 4 +- .../SourceFiles/ui/boxes/calendar_box.cpp | 130 ++++++++++++++---- Telegram/SourceFiles/ui/boxes/calendar_box.h | 20 ++- .../window/window_session_controller.cpp | 86 ++++++------ 4 files changed, 165 insertions(+), 75 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index bdf04cba7..a08ae7ded 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -78,7 +78,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_month_day_year" = "{month} {day}, {year}"; "lng_month_year" = "{month} {year}"; -"lng_calendar_beginning" = "Beginning"; +"lng_calendar_select_days" = "Select days"; +"lng_calendar_start_tip" = "Press and hold to jump to the start."; +"lng_calendar_end_tip" = "Press and hold to jump to the end."; "lng_box_ok" = "OK"; "lng_box_done" = "Done"; diff --git a/Telegram/SourceFiles/ui/boxes/calendar_box.cpp b/Telegram/SourceFiles/ui/boxes/calendar_box.cpp index 8e96b29c1..5673eb42f 100644 --- a/Telegram/SourceFiles/ui/boxes/calendar_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/calendar_box.cpp @@ -19,6 +19,8 @@ namespace { constexpr auto kDaysInWeek = 7; constexpr auto kMaxDaysForScroll = kDaysInWeek * 1000; +constexpr auto kTooltipDelay = crl::time(1000); +constexpr auto kJumpDelay = 2 * crl::time(1000); } // namespace @@ -26,9 +28,9 @@ class CalendarBox::Context { public: Context(QDate month, QDate highlighted); - void setBeginningButton(bool enabled); - [[nodiscard]] bool hasBeginningButton() const { - return _beginningButton; + void setHasSelection(bool has); + [[nodiscard]] bool hasSelection() const { + return _hasSelection; } void setMinDate(QDate date); @@ -63,9 +65,6 @@ public: [[nodiscard]] bool isEnabled(int index) const { return (index >= _minDayIndex) && (index <= _maxDayIndex); } - [[nodiscard]] bool atBeginning() const { - return _highlighted == _min; - } [[nodiscard]] rpl::producer monthValue() const { return _month.value(); @@ -80,7 +79,7 @@ private: static int DaysShiftForMonth(QDate month, QDate min); static int RowsCountForMonth(QDate month, QDate min, QDate max); - bool _beginningButton = false; + bool _hasSelection = false; rpl::variable _month; QDate _min, _max; @@ -102,8 +101,8 @@ CalendarBox::Context::Context(QDate month, QDate highlighted) showMonth(month); } -void CalendarBox::Context::setBeginningButton(bool enabled) { - _beginningButton = enabled; +void CalendarBox::Context::setHasSelection(bool has) { + _hasSelection = has; } void CalendarBox::Context::setMinDate(QDate date) { @@ -245,7 +244,6 @@ public: [[nodiscard]] int countMaxHeight() const; void setDateChosenCallback(Fn callback); - void selectBeginning(); ~Inner(); @@ -479,10 +477,6 @@ void CalendarBox::Inner::setDateChosenCallback(Fn callback) { _dateChosenCallback = std::move(callback); } -void CalendarBox::Inner::selectBeginning() { - _dateChosenCallback(_context->dateFromIndex(_context->minDayIndex())); -} - CalendarBox::Inner::~Inner() = default; class CalendarBox::Title final : public RpWidget { @@ -575,9 +569,10 @@ CalendarBox::CalendarBox(QWidget*, CalendarBoxArgs &&args) , _previous(this, st::calendarPrevious) , _next(this, st::calendarNext) , _callback(std::move(args.callback.value())) -, _finalize(std::move(args.finalize)) { +, _finalize(std::move(args.finalize)) +, _jumpTimer([=] { jump(_jumpButton); }) { _title->setAttribute(Qt::WA_TransparentForMouseEvents); - _context->setBeginningButton(args.hasBeginningButton); + _context->setHasSelection(args.allowsSelection); _context->setMinDate(args.minDate); _context->setMaxDate(args.maxDate); @@ -587,6 +582,60 @@ CalendarBox::CalendarBox(QWidget*, CalendarBoxArgs &&args) }) | rpl::start_with_next([=] { processScroll(); }, lifetime()); + + const auto setupJumps = [&]( + not_null button, + not_null enabled) { + button->events( + ) | rpl::filter([=] { + return *enabled; + }) | rpl::start_with_next([=](not_null e) { + const auto type = e->type(); + if (type == QEvent::MouseMove + && !(static_cast(e.get())->buttons() + & Qt::LeftButton)) { + showJumpTooltip(button); + } else if (type == QEvent::Leave) { + Ui::Tooltip::Hide(); + } else if (type == QEvent::MouseButtonPress + && (static_cast(e.get())->button() + == Qt::LeftButton)) { + jumpAfterDelay(button); + } else if (type == QEvent::MouseButtonRelease + && (static_cast(e.get())->button() + == Qt::LeftButton)) { + _jumpTimer.cancel(); + } + }, lifetime()); + }; + setupJumps(_previous.data(), &_previousEnabled); + setupJumps(_next.data(), &_nextEnabled); +} + +void CalendarBox::showJumpTooltip(not_null button) { + _tooltipButton = button; + Ui::Tooltip::Show(kTooltipDelay, this); +} + +void CalendarBox::jumpAfterDelay(not_null button) { + _jumpButton = button; + _jumpTimer.callOnce(kJumpDelay); + Ui::Tooltip::Hide(); +} + +void CalendarBox::jump(QPointer button) { + const auto jumpToIndex = [&](int index) { + _watchScroll = false; + _context->showMonth(_context->dateFromIndex(index)); + setExactScroll(); + }; + if (_jumpButton == _previous.data() && _previousEnabled) { + jumpToIndex(_context->minDayIndex()); + } else if (_jumpButton == _next.data() && _nextEnabled) { + jumpToIndex(_context->maxDayIndex()); + } + _jumpButton = nullptr; + _jumpTimer.cancel(); } void CalendarBox::prepare() { @@ -606,9 +655,8 @@ void CalendarBox::prepare() { if (_finalize) { _finalize(this); } - if (!_context->atBeginning() && _context->hasBeginningButton()) { - addLeftButton(tr::lng_calendar_beginning(), [=] { - _inner->selectBeginning(); + if (_context->hasSelection()) { + addLeftButton(tr::lng_calendar_select_days(), [=] { }); } } @@ -666,16 +714,40 @@ void CalendarBox::processScroll() { _watchScroll = true; } +QString CalendarBox::tooltipText() const { + if (_tooltipButton == _previous.data()) { + return tr::lng_calendar_start_tip(tr::now); + } else if (_tooltipButton == _next.data()) { + return tr::lng_calendar_end_tip(tr::now); + } + return QString(); +} + +QPoint CalendarBox::tooltipPos() const { + return QCursor::pos(); +} + +bool CalendarBox::tooltipWindowActive() const { + return window()->isActiveWindow(); +} + void CalendarBox::monthChanged(QDate month) { setDimensions(_st.width, st::calendarTitleHeight + _st.daysHeight + _inner->countMaxHeight()); - auto previousEnabled = isPreviousEnabled(); - _previous->setIconOverride(previousEnabled ? nullptr : &st::calendarPreviousDisabled); - _previous->setRippleColorOverride(previousEnabled ? nullptr : &st::boxBg); - _previous->setCursor(previousEnabled ? style::cur_pointer : style::cur_default); - auto nextEnabled = isNextEnabled(); - _next->setIconOverride(nextEnabled ? nullptr : &st::calendarNextDisabled); - _next->setRippleColorOverride(nextEnabled ? nullptr : &st::boxBg); - _next->setCursor(nextEnabled ? style::cur_pointer : style::cur_default); + + _previousEnabled = isPreviousEnabled(); + _previous->setIconOverride(_previousEnabled ? nullptr : &st::calendarPreviousDisabled); + _previous->setRippleColorOverride(_previousEnabled ? nullptr : &st::boxBg); + _previous->setCursor(_previousEnabled ? style::cur_pointer : style::cur_default); + if (!_previousEnabled) { + _previous->clearState(); + } + _nextEnabled = isNextEnabled(); + _next->setIconOverride(_nextEnabled ? nullptr : &st::calendarNextDisabled); + _next->setRippleColorOverride(_nextEnabled ? nullptr : &st::boxBg); + _next->setCursor(_nextEnabled ? style::cur_pointer : style::cur_default); + if (!_nextEnabled) { + _next->clearState(); + } } void CalendarBox::resizeEvent(QResizeEvent *e) { @@ -700,7 +772,9 @@ void CalendarBox::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Escape) { e->ignore(); } else if (e->key() == Qt::Key_Home) { - _inner->selectBeginning(); + jump(_previous.data()); + } else if (e->key() == Qt::Key_End) { + jump(_next.data()); } else if (e->key() == Qt::Key_Left || e->key() == Qt::Key_Up) { goPreviousMonth(); } else if (e->key() == Qt::Key_Right || e->key() == Qt::Key_Down) { diff --git a/Telegram/SourceFiles/ui/boxes/calendar_box.h b/Telegram/SourceFiles/ui/boxes/calendar_box.h index a5115ffce..7a2ee2e36 100644 --- a/Telegram/SourceFiles/ui/boxes/calendar_box.h +++ b/Telegram/SourceFiles/ui/boxes/calendar_box.h @@ -8,7 +8,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "ui/layers/box_content.h" +#include "ui/widgets/tooltip.h" #include "base/required.h" +#include "base/timer.h" namespace style { struct CalendarSizes; @@ -35,10 +37,10 @@ struct CalendarBoxArgs { const style::CalendarSizes &st = st::defaultCalendarSizes; QDate minDate; QDate maxDate; - bool hasBeginningButton = false; + bool allowsSelection = false; }; -class CalendarBox final : public BoxContent { +class CalendarBox final : public BoxContent, private AbstractTooltipShower { public: CalendarBox(QWidget*, CalendarBoxArgs &&args); ~CalendarBox(); @@ -60,6 +62,14 @@ private: void setExactScroll(); void processScroll(); + void showJumpTooltip(not_null button); + void jumpAfterDelay(not_null button); + void jump(QPointer button); + + QString tooltipText() const override; + QPoint tooltipPos() const override; + bool tooltipWindowActive() const override; + const style::CalendarSizes &_st; class Context; @@ -74,11 +84,17 @@ private: object_ptr _title; object_ptr<IconButton> _previous; object_ptr<IconButton> _next; + bool _previousEnabled = false; + bool _nextEnabled = false; Fn<void(QDate date)> _callback; FnMut<void(not_null<CalendarBox*>)> _finalize; bool _watchScroll = false; + QPointer<IconButton> _tooltipButton; + QPointer<IconButton> _jumpButton; + base::Timer _jumpTimer; + }; } // namespace Ui diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index d4454785d..891cae582 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -1147,63 +1147,61 @@ void SessionController::startOrJoinGroupCall( } void SessionController::showJumpToDate(Dialogs::Key chat, QDate requestedDate) { + const auto history = chat.history(); + if (!history) { + return; + } const auto currentPeerDate = [&] { - if (const auto history = chat.history()) { - if (history->scrollTopItem) { - return history->scrollTopItem->dateTime().date(); - } else if (history->loadedAtTop() - && !history->isEmpty() - && history->peer->migrateFrom()) { - if (const auto migrated = history->owner().historyLoaded(history->peer->migrateFrom())) { - if (migrated->scrollTopItem) { - // We're up in the migrated history. - // So current date is the date of first message here. - return history->blocks.front()->messages.front()->dateTime().date(); - } + if (history->scrollTopItem) { + return history->scrollTopItem->dateTime().date(); + } else if (history->loadedAtTop() + && !history->isEmpty() + && history->peer->migrateFrom()) { + if (const auto migrated = history->owner().historyLoaded(history->peer->migrateFrom())) { + if (migrated->scrollTopItem) { + // We're up in the migrated history. + // So current date is the date of first message here. + return history->blocks.front()->messages.front()->dateTime().date(); } - } else if (const auto item = history->lastMessage()) { - return base::unixtime::parse(item->date()).date(); } + } else if (const auto item = history->lastMessage()) { + return base::unixtime::parse(item->date()).date(); } return QDate(); }(); - const auto maxPeerDate = [](Dialogs::Key chat) { - if (auto history = chat.history()) { - if (const auto channel = history->peer->migrateTo()) { - history = channel->owner().historyLoaded(channel); - } - if (const auto item = history ? history->lastMessage() : nullptr) { - return base::unixtime::parse(item->date()).date(); - } + const auto maxPeerDate = [&] { + const auto check = history->peer->migrateTo() + ? history->owner().historyLoaded(history->peer->migrateTo()) + : history; + if (const auto item = check ? check->lastMessage() : nullptr) { + return base::unixtime::parse(item->date()).date(); } - return QDate::currentDate(); - }; - const auto minPeerDate = [](Dialogs::Key chat) { + return QDate(); + }(); + const auto minPeerDate = [&] { const auto startDate = [] { // Telegram was launched in August 2013 :) return QDate(2013, 8, 1); }; - if (const auto history = chat.history()) { - if (const auto chat = history->peer->migrateFrom()) { - if (const auto history = chat->owner().historyLoaded(chat)) { - if (history->loadedAtTop()) { - if (!history->isEmpty()) { - return history->blocks.front()->messages.front()->dateTime().date(); - } - } else { - return startDate(); + if (const auto chat = history->peer->migrateFrom()) { + if (const auto history = chat->owner().historyLoaded(chat)) { + if (history->loadedAtTop()) { + if (!history->isEmpty()) { + return history->blocks.front()->messages.front()->dateTime().date(); } + } else { + return startDate(); } } - if (history->loadedAtTop()) { - if (!history->isEmpty()) { - return history->blocks.front()->messages.front()->dateTime().date(); - } - return QDate::currentDate(); - } + } + if (history->loadedAtTop()) { + if (!history->isEmpty()) { + return history->blocks.front()->messages.front()->dateTime().date(); + } + return QDate::currentDate(); } return startDate(); - }; + }(); const auto highlighted = !requestedDate.isNull() ? requestedDate : !currentPeerDate.isNull() @@ -1215,9 +1213,9 @@ void SessionController::showJumpToDate(Dialogs::Key chat, QDate requestedDate) { .callback = [=](const QDate &date) { session().api().jumpToDate(chat, date); }, - .minDate = minPeerDate(chat), - .maxDate = maxPeerDate(chat), - .hasBeginningButton = true, + .minDate = minPeerDate, + .maxDate = maxPeerDate, + .allowsSelection = history->peer->isUser(), })); }