edit_birthday_box.cpp 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  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/edit_birthday_box.h"
  8. #include "base/event_filter.h"
  9. #include "data/data_birthday.h"
  10. #include "lang/lang_keys.h"
  11. #include "ui/layers/generic_box.h"
  12. #include "ui/widgets/vertical_drum_picker.h"
  13. #include "ui/ui_utility.h"
  14. #include "styles/style_layers.h"
  15. #include "styles/style_settings.h"
  16. #include <QtCore/QDate>
  17. namespace Ui {
  18. class GenericBox;
  19. void EditBirthdayBox(
  20. not_null<Ui::GenericBox*> box,
  21. Data::Birthday current,
  22. Fn<void(Data::Birthday)> save) {
  23. box->setWidth(st::boxWideWidth);
  24. const auto content = box->addRow(object_ptr<Ui::FixedHeightWidget>(
  25. box,
  26. st::settingsWorkingHoursPicker));
  27. const auto font = st::boxTextFont;
  28. const auto itemHeight = st::settingsWorkingHoursPickerItemHeight;
  29. const auto picker = [=](
  30. int count,
  31. int startIndex,
  32. Fn<void(QPainter &p, QRectF rect, int index)> paint) {
  33. auto paintCallback = [=](
  34. QPainter &p,
  35. int index,
  36. float64 y,
  37. float64 distanceFromCenter,
  38. int outerWidth) {
  39. const auto r = QRectF(0, y, outerWidth, itemHeight);
  40. const auto progress = std::abs(distanceFromCenter);
  41. const auto revProgress = 1. - progress;
  42. p.save();
  43. p.translate(r.center());
  44. constexpr auto kMinYScale = 0.2;
  45. const auto yScale = kMinYScale
  46. + (1. - kMinYScale) * anim::easeOutCubic(1., revProgress);
  47. p.scale(1., yScale);
  48. p.translate(-r.center());
  49. p.setOpacity(revProgress);
  50. p.setFont(font);
  51. p.setPen(st::defaultFlatLabel.textFg);
  52. paint(p, r, index);
  53. p.restore();
  54. };
  55. return Ui::CreateChild<Ui::VerticalDrumPicker>(
  56. content,
  57. std::move(paintCallback),
  58. count,
  59. itemHeight,
  60. startIndex);
  61. };
  62. const auto nowDate = QDate::currentDate();
  63. const auto nowYear = nowDate.year();
  64. const auto nowMonth = nowDate.month();
  65. const auto nowDay = nowDate.day();
  66. const auto now = Data::Birthday(nowDay, nowMonth, nowYear);
  67. const auto max = current.year() ? std::max(now, current) : now;
  68. const auto maxYear = max.year();
  69. const auto minYear = Data::Birthday::kYearMin;
  70. const auto yearsCount = (maxYear - minYear + 2); // Last - not set.
  71. const auto yearsStartIndex = current.year()
  72. ? (current.year() - minYear)
  73. : (yearsCount - 1);
  74. const auto yearsPaint = [=](QPainter &p, QRectF rect, int index) {
  75. p.drawText(
  76. rect,
  77. (index < yearsCount - 1
  78. ? QString::number(minYear + index)
  79. : QString::fromUtf8("\xe2\x80\x94")),
  80. style::al_center);
  81. };
  82. const auto years = picker(yearsCount, yearsStartIndex, yearsPaint);
  83. struct State {
  84. rpl::variable<Ui::VerticalDrumPicker*> months;
  85. rpl::variable<Ui::VerticalDrumPicker*> days;
  86. };
  87. const auto state = content->lifetime().make_state<State>();
  88. // years->value() is valid only after size is set.
  89. rpl::combine(
  90. content->sizeValue(),
  91. state->months.value(),
  92. state->days.value()
  93. ) | rpl::start_with_next([=](
  94. QSize s,
  95. Ui::VerticalDrumPicker *months,
  96. Ui::VerticalDrumPicker *days) {
  97. const auto half = s.width() / 2;
  98. years->setGeometry(half * 3 / 2, 0, half / 2, s.height());
  99. if (months) {
  100. months->setGeometry(half / 2, 0, half, s.height());
  101. }
  102. if (days) {
  103. days->setGeometry(0, 0, half / 2, s.height());
  104. }
  105. }, content->lifetime());
  106. Ui::SendPendingMoveResizeEvents(years);
  107. years->value() | rpl::start_with_next([=](int yearsIndex) {
  108. const auto year = (yearsIndex == yearsCount - 1)
  109. ? 0
  110. : minYear + yearsIndex;
  111. const auto monthsCount = (year == maxYear)
  112. ? max.month()
  113. : 12;
  114. const auto monthsStartIndex = std::clamp(
  115. (state->months.current()
  116. ? state->months.current()->index()
  117. : current.month()
  118. ? (current.month() - 1)
  119. : (now.month() - 1)),
  120. 0,
  121. monthsCount - 1);
  122. const auto monthsPaint = [=](QPainter &p, QRectF rect, int index) {
  123. p.drawText(
  124. rect,
  125. Lang::Month(index + 1)(tr::now),
  126. style::al_center);
  127. };
  128. const auto updated = picker(
  129. monthsCount,
  130. monthsStartIndex,
  131. monthsPaint);
  132. delete state->months.current();
  133. state->months = updated;
  134. state->months.current()->show();
  135. }, years->lifetime());
  136. Ui::SendPendingMoveResizeEvents(state->months.current());
  137. state->months.value() | rpl::map([=](Ui::VerticalDrumPicker *picker) {
  138. return picker ? picker->value() : rpl::single(current.month()
  139. ? (current.month() - 1)
  140. : (now.month() - 1));
  141. }) | rpl::flatten_latest() | rpl::start_with_next([=](int monthIndex) {
  142. const auto month = monthIndex + 1;
  143. const auto yearsIndex = years->index();
  144. const auto year = (yearsIndex == yearsCount - 1)
  145. ? 0
  146. : minYear + yearsIndex;
  147. const auto daysCount = (year == maxYear && month == max.month())
  148. ? max.day()
  149. : (month == 2)
  150. ? ((!year || ((year % 4) && (!(year % 100) || (year % 400))))
  151. ? 29
  152. : 28)
  153. : ((month == 4) || (month == 6) || (month == 9) || (month == 11))
  154. ? 30
  155. : 31;
  156. const auto daysStartIndex = std::clamp(
  157. (state->days.current()
  158. ? state->days.current()->index()
  159. : current.day()
  160. ? (current.day() - 1)
  161. : (now.day() - 1)),
  162. 0,
  163. daysCount - 1);
  164. const auto daysPaint = [=](QPainter &p, QRectF rect, int index) {
  165. p.drawText(rect, QString::number(index + 1), style::al_center);
  166. };
  167. const auto updated = picker(
  168. daysCount,
  169. daysStartIndex,
  170. daysPaint);
  171. delete state->days.current();
  172. state->days = updated;
  173. state->days.current()->show();
  174. }, years->lifetime());
  175. content->paintRequest(
  176. ) | rpl::start_with_next([=](const QRect &r) {
  177. auto p = QPainter(content);
  178. p.fillRect(r, Qt::transparent);
  179. const auto lineRect = QRect(
  180. 0,
  181. content->height() / 2,
  182. content->width(),
  183. st::defaultInputField.borderActive);
  184. p.fillRect(lineRect.translated(0, itemHeight / 2), st::activeLineFg);
  185. p.fillRect(lineRect.translated(0, -itemHeight / 2), st::activeLineFg);
  186. }, content->lifetime());
  187. base::install_event_filter(box, [=](not_null<QEvent*> e) {
  188. if (e->type() == QEvent::KeyPress) {
  189. years->handleKeyEvent(static_cast<QKeyEvent*>(e.get()));
  190. }
  191. return base::EventFilterResult::Continue;
  192. });
  193. box->addButton(tr::lng_settings_save(), [=] {
  194. const auto result = Data::Birthday(
  195. state->days.current()->index() + 1,
  196. state->months.current()->index() + 1,
  197. ((years->index() == yearsCount - 1)
  198. ? 0
  199. : minYear + years->index()));
  200. box->closeBox();
  201. save(result);
  202. });
  203. box->addButton(tr::lng_cancel(), [=] {
  204. box->closeBox();
  205. });
  206. if (current) {
  207. box->addLeftButton(tr::lng_settings_birthday_reset(), [=] {
  208. box->closeBox();
  209. save(Data::Birthday());
  210. });
  211. }
  212. }
  213. } // namespace Ui