Implement jump-to-start / end by long press in CalendarBox.

This commit is contained in:
John Preston 2021-11-15 13:09:44 +04:00
parent 15dc6064ef
commit 4414369fc8
4 changed files with 165 additions and 75 deletions

View file

@ -78,7 +78,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_month_day_year" = "{month} {day}, {year}"; "lng_month_day_year" = "{month} {day}, {year}";
"lng_month_year" = "{month} {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_ok" = "OK";
"lng_box_done" = "Done"; "lng_box_done" = "Done";

View file

@ -19,6 +19,8 @@ namespace {
constexpr auto kDaysInWeek = 7; constexpr auto kDaysInWeek = 7;
constexpr auto kMaxDaysForScroll = kDaysInWeek * 1000; constexpr auto kMaxDaysForScroll = kDaysInWeek * 1000;
constexpr auto kTooltipDelay = crl::time(1000);
constexpr auto kJumpDelay = 2 * crl::time(1000);
} // namespace } // namespace
@ -26,9 +28,9 @@ class CalendarBox::Context {
public: public:
Context(QDate month, QDate highlighted); Context(QDate month, QDate highlighted);
void setBeginningButton(bool enabled); void setHasSelection(bool has);
[[nodiscard]] bool hasBeginningButton() const { [[nodiscard]] bool hasSelection() const {
return _beginningButton; return _hasSelection;
} }
void setMinDate(QDate date); void setMinDate(QDate date);
@ -63,9 +65,6 @@ public:
[[nodiscard]] bool isEnabled(int index) const { [[nodiscard]] bool isEnabled(int index) const {
return (index >= _minDayIndex) && (index <= _maxDayIndex); return (index >= _minDayIndex) && (index <= _maxDayIndex);
} }
[[nodiscard]] bool atBeginning() const {
return _highlighted == _min;
}
[[nodiscard]] rpl::producer<QDate> monthValue() const { [[nodiscard]] rpl::producer<QDate> monthValue() const {
return _month.value(); return _month.value();
@ -80,7 +79,7 @@ private:
static int DaysShiftForMonth(QDate month, QDate min); static int DaysShiftForMonth(QDate month, QDate min);
static int RowsCountForMonth(QDate month, QDate min, QDate max); static int RowsCountForMonth(QDate month, QDate min, QDate max);
bool _beginningButton = false; bool _hasSelection = false;
rpl::variable<QDate> _month; rpl::variable<QDate> _month;
QDate _min, _max; QDate _min, _max;
@ -102,8 +101,8 @@ CalendarBox::Context::Context(QDate month, QDate highlighted)
showMonth(month); showMonth(month);
} }
void CalendarBox::Context::setBeginningButton(bool enabled) { void CalendarBox::Context::setHasSelection(bool has) {
_beginningButton = enabled; _hasSelection = has;
} }
void CalendarBox::Context::setMinDate(QDate date) { void CalendarBox::Context::setMinDate(QDate date) {
@ -245,7 +244,6 @@ public:
[[nodiscard]] int countMaxHeight() const; [[nodiscard]] int countMaxHeight() const;
void setDateChosenCallback(Fn<void(QDate)> callback); void setDateChosenCallback(Fn<void(QDate)> callback);
void selectBeginning();
~Inner(); ~Inner();
@ -479,10 +477,6 @@ void CalendarBox::Inner::setDateChosenCallback(Fn<void(QDate)> callback) {
_dateChosenCallback = std::move(callback); _dateChosenCallback = std::move(callback);
} }
void CalendarBox::Inner::selectBeginning() {
_dateChosenCallback(_context->dateFromIndex(_context->minDayIndex()));
}
CalendarBox::Inner::~Inner() = default; CalendarBox::Inner::~Inner() = default;
class CalendarBox::Title final : public RpWidget { class CalendarBox::Title final : public RpWidget {
@ -575,9 +569,10 @@ CalendarBox::CalendarBox(QWidget*, CalendarBoxArgs &&args)
, _previous(this, st::calendarPrevious) , _previous(this, st::calendarPrevious)
, _next(this, st::calendarNext) , _next(this, st::calendarNext)
, _callback(std::move(args.callback.value())) , _callback(std::move(args.callback.value()))
, _finalize(std::move(args.finalize)) { , _finalize(std::move(args.finalize))
, _jumpTimer([=] { jump(_jumpButton); }) {
_title->setAttribute(Qt::WA_TransparentForMouseEvents); _title->setAttribute(Qt::WA_TransparentForMouseEvents);
_context->setBeginningButton(args.hasBeginningButton); _context->setHasSelection(args.allowsSelection);
_context->setMinDate(args.minDate); _context->setMinDate(args.minDate);
_context->setMaxDate(args.maxDate); _context->setMaxDate(args.maxDate);
@ -587,6 +582,60 @@ CalendarBox::CalendarBox(QWidget*, CalendarBoxArgs &&args)
}) | rpl::start_with_next([=] { }) | rpl::start_with_next([=] {
processScroll(); processScroll();
}, lifetime()); }, lifetime());
const auto setupJumps = [&](
not_null<IconButton*> button,
not_null<bool*> enabled) {
button->events(
) | rpl::filter([=] {
return *enabled;
}) | rpl::start_with_next([=](not_null<QEvent*> e) {
const auto type = e->type();
if (type == QEvent::MouseMove
&& !(static_cast<QMouseEvent*>(e.get())->buttons()
& Qt::LeftButton)) {
showJumpTooltip(button);
} else if (type == QEvent::Leave) {
Ui::Tooltip::Hide();
} else if (type == QEvent::MouseButtonPress
&& (static_cast<QMouseEvent*>(e.get())->button()
== Qt::LeftButton)) {
jumpAfterDelay(button);
} else if (type == QEvent::MouseButtonRelease
&& (static_cast<QMouseEvent*>(e.get())->button()
== Qt::LeftButton)) {
_jumpTimer.cancel();
}
}, lifetime());
};
setupJumps(_previous.data(), &_previousEnabled);
setupJumps(_next.data(), &_nextEnabled);
}
void CalendarBox::showJumpTooltip(not_null<IconButton*> button) {
_tooltipButton = button;
Ui::Tooltip::Show(kTooltipDelay, this);
}
void CalendarBox::jumpAfterDelay(not_null<IconButton*> button) {
_jumpButton = button;
_jumpTimer.callOnce(kJumpDelay);
Ui::Tooltip::Hide();
}
void CalendarBox::jump(QPointer<IconButton> 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() { void CalendarBox::prepare() {
@ -606,9 +655,8 @@ void CalendarBox::prepare() {
if (_finalize) { if (_finalize) {
_finalize(this); _finalize(this);
} }
if (!_context->atBeginning() && _context->hasBeginningButton()) { if (_context->hasSelection()) {
addLeftButton(tr::lng_calendar_beginning(), [=] { addLeftButton(tr::lng_calendar_select_days(), [=] {
_inner->selectBeginning();
}); });
} }
} }
@ -666,16 +714,40 @@ void CalendarBox::processScroll() {
_watchScroll = true; _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) { void CalendarBox::monthChanged(QDate month) {
setDimensions(_st.width, st::calendarTitleHeight + _st.daysHeight + _inner->countMaxHeight()); setDimensions(_st.width, st::calendarTitleHeight + _st.daysHeight + _inner->countMaxHeight());
auto previousEnabled = isPreviousEnabled();
_previous->setIconOverride(previousEnabled ? nullptr : &st::calendarPreviousDisabled); _previousEnabled = isPreviousEnabled();
_previous->setRippleColorOverride(previousEnabled ? nullptr : &st::boxBg); _previous->setIconOverride(_previousEnabled ? nullptr : &st::calendarPreviousDisabled);
_previous->setCursor(previousEnabled ? style::cur_pointer : style::cur_default); _previous->setRippleColorOverride(_previousEnabled ? nullptr : &st::boxBg);
auto nextEnabled = isNextEnabled(); _previous->setCursor(_previousEnabled ? style::cur_pointer : style::cur_default);
_next->setIconOverride(nextEnabled ? nullptr : &st::calendarNextDisabled); if (!_previousEnabled) {
_next->setRippleColorOverride(nextEnabled ? nullptr : &st::boxBg); _previous->clearState();
_next->setCursor(nextEnabled ? style::cur_pointer : style::cur_default); }
_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) { void CalendarBox::resizeEvent(QResizeEvent *e) {
@ -700,7 +772,9 @@ void CalendarBox::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Escape) { if (e->key() == Qt::Key_Escape) {
e->ignore(); e->ignore();
} else if (e->key() == Qt::Key_Home) { } 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) { } else if (e->key() == Qt::Key_Left || e->key() == Qt::Key_Up) {
goPreviousMonth(); goPreviousMonth();
} else if (e->key() == Qt::Key_Right || e->key() == Qt::Key_Down) { } else if (e->key() == Qt::Key_Right || e->key() == Qt::Key_Down) {

View file

@ -8,7 +8,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once #pragma once
#include "ui/layers/box_content.h" #include "ui/layers/box_content.h"
#include "ui/widgets/tooltip.h"
#include "base/required.h" #include "base/required.h"
#include "base/timer.h"
namespace style { namespace style {
struct CalendarSizes; struct CalendarSizes;
@ -35,10 +37,10 @@ struct CalendarBoxArgs {
const style::CalendarSizes &st = st::defaultCalendarSizes; const style::CalendarSizes &st = st::defaultCalendarSizes;
QDate minDate; QDate minDate;
QDate maxDate; QDate maxDate;
bool hasBeginningButton = false; bool allowsSelection = false;
}; };
class CalendarBox final : public BoxContent { class CalendarBox final : public BoxContent, private AbstractTooltipShower {
public: public:
CalendarBox(QWidget*, CalendarBoxArgs &&args); CalendarBox(QWidget*, CalendarBoxArgs &&args);
~CalendarBox(); ~CalendarBox();
@ -60,6 +62,14 @@ private:
void setExactScroll(); void setExactScroll();
void processScroll(); void processScroll();
void showJumpTooltip(not_null<IconButton*> button);
void jumpAfterDelay(not_null<IconButton*> button);
void jump(QPointer<IconButton> button);
QString tooltipText() const override;
QPoint tooltipPos() const override;
bool tooltipWindowActive() const override;
const style::CalendarSizes &_st; const style::CalendarSizes &_st;
class Context; class Context;
@ -74,11 +84,17 @@ private:
object_ptr<Title> _title; object_ptr<Title> _title;
object_ptr<IconButton> _previous; object_ptr<IconButton> _previous;
object_ptr<IconButton> _next; object_ptr<IconButton> _next;
bool _previousEnabled = false;
bool _nextEnabled = false;
Fn<void(QDate date)> _callback; Fn<void(QDate date)> _callback;
FnMut<void(not_null<CalendarBox*>)> _finalize; FnMut<void(not_null<CalendarBox*>)> _finalize;
bool _watchScroll = false; bool _watchScroll = false;
QPointer<IconButton> _tooltipButton;
QPointer<IconButton> _jumpButton;
base::Timer _jumpTimer;
}; };
} // namespace Ui } // namespace Ui

View file

@ -1147,63 +1147,61 @@ void SessionController::startOrJoinGroupCall(
} }
void SessionController::showJumpToDate(Dialogs::Key chat, QDate requestedDate) { void SessionController::showJumpToDate(Dialogs::Key chat, QDate requestedDate) {
const auto history = chat.history();
if (!history) {
return;
}
const auto currentPeerDate = [&] { const auto currentPeerDate = [&] {
if (const auto history = chat.history()) { if (history->scrollTopItem) {
if (history->scrollTopItem) { return history->scrollTopItem->dateTime().date();
return history->scrollTopItem->dateTime().date(); } else if (history->loadedAtTop()
} else if (history->loadedAtTop() && !history->isEmpty()
&& !history->isEmpty() && history->peer->migrateFrom()) {
&& history->peer->migrateFrom()) { if (const auto migrated = history->owner().historyLoaded(history->peer->migrateFrom())) {
if (const auto migrated = history->owner().historyLoaded(history->peer->migrateFrom())) { if (migrated->scrollTopItem) {
if (migrated->scrollTopItem) { // We're up in the migrated history.
// We're up in the migrated history. // So current date is the date of first message here.
// So current date is the date of first message here. return history->blocks.front()->messages.front()->dateTime().date();
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(); return QDate();
}(); }();
const auto maxPeerDate = [](Dialogs::Key chat) { const auto maxPeerDate = [&] {
if (auto history = chat.history()) { const auto check = history->peer->migrateTo()
if (const auto channel = history->peer->migrateTo()) { ? history->owner().historyLoaded(history->peer->migrateTo())
history = channel->owner().historyLoaded(channel); : history;
} if (const auto item = check ? check->lastMessage() : nullptr) {
if (const auto item = history ? history->lastMessage() : nullptr) { return base::unixtime::parse(item->date()).date();
return base::unixtime::parse(item->date()).date();
}
} }
return QDate::currentDate(); return QDate();
}; }();
const auto minPeerDate = [](Dialogs::Key chat) { const auto minPeerDate = [&] {
const auto startDate = [] { const auto startDate = [] {
// Telegram was launched in August 2013 :) // Telegram was launched in August 2013 :)
return QDate(2013, 8, 1); return QDate(2013, 8, 1);
}; };
if (const auto history = chat.history()) { if (const auto chat = history->peer->migrateFrom()) {
if (const auto chat = history->peer->migrateFrom()) { if (const auto history = chat->owner().historyLoaded(chat)) {
if (const auto history = chat->owner().historyLoaded(chat)) { if (history->loadedAtTop()) {
if (history->loadedAtTop()) { if (!history->isEmpty()) {
if (!history->isEmpty()) { return history->blocks.front()->messages.front()->dateTime().date();
return history->blocks.front()->messages.front()->dateTime().date();
}
} else {
return startDate();
} }
} else {
return startDate();
} }
} }
if (history->loadedAtTop()) { }
if (!history->isEmpty()) { if (history->loadedAtTop()) {
return history->blocks.front()->messages.front()->dateTime().date(); if (!history->isEmpty()) {
} return history->blocks.front()->messages.front()->dateTime().date();
return QDate::currentDate(); }
} return QDate::currentDate();
} }
return startDate(); return startDate();
}; }();
const auto highlighted = !requestedDate.isNull() const auto highlighted = !requestedDate.isNull()
? requestedDate ? requestedDate
: !currentPeerDate.isNull() : !currentPeerDate.isNull()
@ -1215,9 +1213,9 @@ void SessionController::showJumpToDate(Dialogs::Key chat, QDate requestedDate) {
.callback = [=](const QDate &date) { .callback = [=](const QDate &date) {
session().api().jumpToDate(chat, date); session().api().jumpToDate(chat, date);
}, },
.minDate = minPeerDate(chat), .minDate = minPeerDate,
.maxDate = maxPeerDate(chat), .maxDate = maxPeerDate,
.hasBeginningButton = true, .allowsSelection = history->peer->isUser(),
})); }));
} }