time_picker_box.cpp 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "ui/boxes/time_picker_box.h"
  8. #include "base/event_filter.h"
  9. #include "lang/lang_keys.h"
  10. #include "ui/layers/generic_box.h"
  11. #include "ui/effects/animation_value.h"
  12. #include "ui/ui_utility.h"
  13. #include "ui/widgets/vertical_drum_picker.h"
  14. #include "styles/style_chat_helpers.h"
  15. #include "styles/style_layers.h"
  16. namespace Ui {
  17. namespace {
  18. constexpr auto kMinYScale = 0.2;
  19. } // namespace
  20. std::vector<TimeId> DefaultTimePickerValues() {
  21. return {
  22. (60 * 15),
  23. (60 * 30),
  24. (3600 * 1),
  25. (3600 * 2),
  26. (3600 * 3),
  27. (3600 * 4),
  28. (3600 * 8),
  29. (3600 * 12),
  30. (86400 * 1),
  31. (86400 * 2),
  32. (86400 * 3),
  33. (86400 * 7 * 1),
  34. (86400 * 7 * 2),
  35. (86400 * 31 * 1),
  36. (86400 * 31 * 2),
  37. (86400 * 31 * 3),
  38. };
  39. }
  40. Fn<TimeId()> TimePickerBox(
  41. not_null<GenericBox*> box,
  42. std::vector<TimeId> values,
  43. std::vector<QString> phrases,
  44. TimeId startValue) {
  45. Expects(phrases.size() == values.size());
  46. const auto startIndex = [&, &v = startValue] {
  47. const auto it = ranges::lower_bound(values, v);
  48. if (it == begin(values)) {
  49. return 0;
  50. }
  51. const auto left = *(it - 1);
  52. const auto right = *it;
  53. const auto shift = (std::abs(v - left) < std::abs(v - right))
  54. ? -1
  55. : 0;
  56. return int(std::distance(begin(values), it - shift));
  57. }();
  58. const auto content = box->addRow(object_ptr<Ui::FixedHeightWidget>(
  59. box,
  60. st::historyMessagesTTLPickerHeight));
  61. const auto font = st::boxTextFont;
  62. const auto maxPhraseWidth = [&] {
  63. // We have to use QFontMetricsF instead of
  64. // FontData::width for more precise calculation.
  65. const auto mf = QFontMetricsF(font->f);
  66. const auto maxPhrase = ranges::max_element(
  67. phrases,
  68. std::less<>(),
  69. [&](const QString &s) { return mf.horizontalAdvance(s); });
  70. return std::ceil(mf.horizontalAdvance(*maxPhrase));
  71. }();
  72. const auto itemHeight = st::historyMessagesTTLPickerItemHeight;
  73. auto paintCallback = [=](
  74. QPainter &p,
  75. int index,
  76. float64 y,
  77. float64 distanceFromCenter,
  78. int outerWidth) {
  79. const auto r = QRectF(0, y, outerWidth, itemHeight);
  80. const auto progress = std::abs(distanceFromCenter);
  81. const auto revProgress = 1. - progress;
  82. p.save();
  83. p.translate(r.center());
  84. const auto yScale = kMinYScale
  85. + (1. - kMinYScale) * anim::easeOutCubic(1., revProgress);
  86. p.scale(1., yScale);
  87. p.translate(-r.center());
  88. p.setOpacity(revProgress);
  89. p.setFont(font);
  90. p.setPen(st::defaultFlatLabel.textFg);
  91. p.drawText(r, phrases[index], style::al_center);
  92. p.restore();
  93. };
  94. const auto picker = Ui::CreateChild<Ui::VerticalDrumPicker>(
  95. content,
  96. std::move(paintCallback),
  97. phrases.size(),
  98. itemHeight,
  99. startIndex);
  100. content->sizeValue(
  101. ) | rpl::start_with_next([=](const QSize &s) {
  102. picker->resize(maxPhraseWidth, s.height());
  103. picker->moveToLeft((s.width() - picker->width()) / 2, 0);
  104. }, content->lifetime());
  105. content->paintRequest(
  106. ) | rpl::start_with_next([=](const QRect &r) {
  107. auto p = QPainter(content);
  108. p.fillRect(r, Qt::transparent);
  109. const auto lineRect = QRect(
  110. 0,
  111. content->height() / 2,
  112. content->width(),
  113. st::defaultInputField.borderActive);
  114. p.fillRect(lineRect.translated(0, itemHeight / 2), st::activeLineFg);
  115. p.fillRect(lineRect.translated(0, -itemHeight / 2), st::activeLineFg);
  116. }, content->lifetime());
  117. base::install_event_filter(content, [=](not_null<QEvent*> e) {
  118. if ((e->type() == QEvent::MouseButtonPress)
  119. || (e->type() == QEvent::MouseButtonRelease)
  120. || (e->type() == QEvent::MouseMove)) {
  121. picker->handleMouseEvent(static_cast<QMouseEvent*>(e.get()));
  122. } else if (e->type() == QEvent::Wheel) {
  123. picker->handleWheelEvent(static_cast<QWheelEvent*>(e.get()));
  124. }
  125. return base::EventFilterResult::Continue;
  126. });
  127. base::install_event_filter(box, [=](not_null<QEvent*> e) {
  128. if (e->type() == QEvent::KeyPress) {
  129. picker->handleKeyEvent(static_cast<QKeyEvent*>(e.get()));
  130. }
  131. return base::EventFilterResult::Continue;
  132. });
  133. return [=] { return values[picker->index()]; };
  134. }
  135. } // namespace Ui