export_view_progress.cpp 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  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 "export/view/export_view_progress.h"
  8. #include "ui/effects/animations.h"
  9. #include "ui/widgets/labels.h"
  10. #include "ui/widgets/buttons.h"
  11. #include "ui/wrap/fade_wrap.h"
  12. #include "ui/wrap/vertical_layout.h"
  13. #include "lang/lang_keys.h"
  14. #include "styles/style_boxes.h"
  15. #include "styles/style_export.h"
  16. namespace Export {
  17. namespace View {
  18. namespace {
  19. constexpr auto kShowSkipFileTimeout = 5 * crl::time(1000);
  20. } // namespace
  21. class ProgressWidget::Row : public Ui::RpWidget {
  22. public:
  23. Row(QWidget *parent, Content::Row &&data);
  24. void updateData(Content::Row &&data);
  25. protected:
  26. int resizeGetHeight(int newWidth) override;
  27. void paintEvent(QPaintEvent *e) override;
  28. private:
  29. struct Instance {
  30. base::unique_qptr<Ui::FadeWrap<Ui::FlatLabel>> label;
  31. base::unique_qptr<Ui::FadeWrap<Ui::FlatLabel>> info;
  32. float64 value = 0.;
  33. Ui::Animations::Simple progress;
  34. bool hiding = true;
  35. Ui::Animations::Simple opacity;
  36. };
  37. void fillCurrentInstance();
  38. void hideCurrentInstance();
  39. void setInstanceProgress(Instance &instance, float64 progress);
  40. void toggleInstance(Instance &data, bool shown);
  41. void instanceOpacityCallback(QPointer<Ui::FlatLabel> label);
  42. void removeOldInstance(QPointer<Ui::FlatLabel> label);
  43. void paintInstance(QPainter &p, const Instance &data);
  44. void updateControlsGeometry(int newWidth);
  45. void updateInstanceGeometry(const Instance &instance, int newWidth);
  46. Content::Row _data;
  47. Instance _current;
  48. std::vector<Instance> _old;
  49. };
  50. ProgressWidget::Row::Row(QWidget *parent, Content::Row &&data)
  51. : RpWidget(parent)
  52. , _data(std::move(data)) {
  53. fillCurrentInstance();
  54. }
  55. void ProgressWidget::Row::updateData(Content::Row &&data) {
  56. const auto wasId = _data.id;
  57. const auto nowId = data.id;
  58. _data = std::move(data);
  59. if (nowId.isEmpty()) {
  60. hideCurrentInstance();
  61. } else if (wasId.isEmpty()) {
  62. fillCurrentInstance();
  63. } else {
  64. _current.label->entity()->setText(_data.label);
  65. _current.info->entity()->setText(_data.info);
  66. setInstanceProgress(_current, _data.progress);
  67. if (nowId != wasId) {
  68. _current.progress.stop();
  69. }
  70. }
  71. updateControlsGeometry(width());
  72. update();
  73. }
  74. void ProgressWidget::Row::fillCurrentInstance() {
  75. _current.label = base::make_unique_q<Ui::FadeWrap<Ui::FlatLabel>>(
  76. this,
  77. object_ptr<Ui::FlatLabel>(
  78. this,
  79. _data.label,
  80. st::exportProgressLabel));
  81. _current.info = base::make_unique_q<Ui::FadeWrap<Ui::FlatLabel>>(
  82. this,
  83. object_ptr<Ui::FlatLabel>(
  84. this,
  85. _data.info,
  86. st::exportProgressInfoLabel));
  87. _current.label->hide(anim::type::instant);
  88. _current.info->hide(anim::type::instant);
  89. setInstanceProgress(_current, _data.progress);
  90. toggleInstance(_current, true);
  91. if (_data.id == "main") {
  92. _current.opacity.stop();
  93. _current.label->finishAnimating();
  94. _current.info->finishAnimating();
  95. }
  96. }
  97. void ProgressWidget::Row::hideCurrentInstance() {
  98. if (!_current.label) {
  99. return;
  100. }
  101. setInstanceProgress(_current, 1.);
  102. toggleInstance(_current, false);
  103. _old.push_back(std::move(_current));
  104. }
  105. void ProgressWidget::Row::setInstanceProgress(
  106. Instance &instance,
  107. float64 progress) {
  108. if (_current.value < progress) {
  109. _current.progress.start(
  110. [=] { update(); },
  111. _current.value,
  112. progress,
  113. st::exportProgressDuration,
  114. anim::sineInOut);
  115. } else if (_current.value > progress) {
  116. _current.progress.stop();
  117. }
  118. _current.value = progress;
  119. }
  120. void ProgressWidget::Row::toggleInstance(Instance &instance, bool shown) {
  121. Expects(instance.label != nullptr);
  122. if (instance.hiding != shown) {
  123. return;
  124. }
  125. const auto label = Ui::MakeWeak(instance.label->entity());
  126. instance.opacity.start(
  127. [=] { instanceOpacityCallback(label); },
  128. shown ? 0. : 1.,
  129. shown ? 1. : 0.,
  130. st::exportProgressDuration);
  131. instance.hiding = !shown;
  132. _current.label->toggle(shown, anim::type::normal);
  133. _current.info->toggle(shown, anim::type::normal);
  134. }
  135. void ProgressWidget::Row::instanceOpacityCallback(
  136. QPointer<Ui::FlatLabel> label) {
  137. update();
  138. const auto i = ranges::find(_old, label, [](const Instance &instance) {
  139. return Ui::MakeWeak(instance.label->entity());
  140. });
  141. if (i != end(_old) && i->hiding && !i->opacity.animating()) {
  142. crl::on_main(this, [=] {
  143. removeOldInstance(label);
  144. });
  145. }
  146. }
  147. void ProgressWidget::Row::removeOldInstance(QPointer<Ui::FlatLabel> label) {
  148. const auto i = ranges::find(_old, label, [](const Instance &instance) {
  149. return Ui::MakeWeak(instance.label->entity());
  150. });
  151. if (i != end(_old)) {
  152. _old.erase(i);
  153. }
  154. }
  155. int ProgressWidget::Row::resizeGetHeight(int newWidth) {
  156. updateControlsGeometry(newWidth);
  157. return st::exportProgressRowHeight;
  158. }
  159. void ProgressWidget::Row::paintEvent(QPaintEvent *e) {
  160. auto p = QPainter(this);
  161. const auto thickness = st::exportProgressWidth;
  162. const auto top = height() - thickness;
  163. p.fillRect(0, top, width(), thickness, st::shadowFg);
  164. for (const auto &instance : _old) {
  165. paintInstance(p, instance);
  166. }
  167. paintInstance(p, _current);
  168. }
  169. void ProgressWidget::Row::paintInstance(QPainter &p, const Instance &data) {
  170. const auto opacity = data.opacity.value(data.hiding ? 0. : 1.);
  171. if (!opacity) {
  172. return;
  173. }
  174. p.setOpacity(opacity);
  175. const auto thickness = st::exportProgressWidth;
  176. const auto top = height() - thickness;
  177. const auto till = qRound(data.progress.value(data.value) * width());
  178. if (till > 0) {
  179. p.fillRect(0, top, till, thickness, st::exportProgressFg);
  180. }
  181. if (till < width()) {
  182. const auto left = width() - till;
  183. p.fillRect(till, top, left, thickness, st::exportProgressBg);
  184. }
  185. }
  186. void ProgressWidget::Row::updateControlsGeometry(int newWidth) {
  187. updateInstanceGeometry(_current, newWidth);
  188. for (const auto &instance : _old) {
  189. updateInstanceGeometry(instance, newWidth);
  190. }
  191. }
  192. void ProgressWidget::Row::updateInstanceGeometry(
  193. const Instance &instance,
  194. int newWidth) {
  195. if (!instance.label) {
  196. return;
  197. }
  198. instance.info->resizeToNaturalWidth(newWidth);
  199. instance.label->resizeToWidth(newWidth - instance.info->width());
  200. instance.info->moveToRight(0, 0, newWidth);
  201. instance.label->moveToLeft(0, 0, newWidth);
  202. }
  203. ProgressWidget::ProgressWidget(
  204. QWidget *parent,
  205. rpl::producer<Content> content)
  206. : RpWidget(parent)
  207. , _body(this)
  208. , _fileShowSkipTimer([=] { _skipFile->show(anim::type::normal); }) {
  209. widthValue(
  210. ) | rpl::start_with_next([=](int width) {
  211. _body->resizeToWidth(width);
  212. _body->moveToLeft(0, 0);
  213. }, _body->lifetime());
  214. auto skipFileWrap = _body->add(object_ptr<Ui::FixedHeightWidget>(
  215. _body.data(),
  216. st::defaultLinkButton.font->height + st::exportProgressRowSkip));
  217. _skipFile = base::make_unique_q<Ui::FadeWrap<Ui::LinkButton>>(
  218. skipFileWrap,
  219. object_ptr<Ui::LinkButton>(
  220. this,
  221. tr::lng_export_skip_file(tr::now),
  222. st::defaultLinkButton));
  223. _skipFile->hide(anim::type::instant);
  224. _skipFile->moveToLeft(st::exportProgressRowPadding.left(), 0);
  225. _about = _body->add(
  226. object_ptr<Ui::FlatLabel>(
  227. this,
  228. tr::lng_export_progress(tr::now),
  229. st::exportAboutLabel),
  230. st::exportAboutPadding);
  231. std::move(
  232. content
  233. ) | rpl::start_with_next([=](Content &&content) {
  234. updateState(std::move(content));
  235. }, lifetime());
  236. _cancel = base::make_unique_q<Ui::RoundButton>(
  237. this,
  238. tr::lng_export_stop(),
  239. st::exportCancelButton);
  240. setupBottomButton(_cancel.get());
  241. }
  242. rpl::producer<uint64> ProgressWidget::skipFileClicks() const {
  243. return _skipFile->entity()->clicks(
  244. ) | rpl::map([=] { return _fileRandomId; });
  245. }
  246. rpl::producer<> ProgressWidget::cancelClicks() const {
  247. return _cancel
  248. ? (_cancel->clicks() | rpl::to_empty)
  249. : (rpl::never<>() | rpl::type_erased());
  250. }
  251. rpl::producer<> ProgressWidget::doneClicks() const {
  252. return _doneClicks.events();
  253. }
  254. void ProgressWidget::setupBottomButton(not_null<Ui::RoundButton*> button) {
  255. button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
  256. button->show();
  257. sizeValue(
  258. ) | rpl::start_with_next([=](QSize size) {
  259. button->move(
  260. (size.width() - button->width()) / 2,
  261. (size.height() - st::exportCancelBottom - button->height()));
  262. }, button->lifetime());
  263. }
  264. void ProgressWidget::updateState(Content &&content) {
  265. if (!content.rows.empty() && content.rows[0].id == Content::kDoneId) {
  266. showDone();
  267. }
  268. const auto wasCount = _rows.size();
  269. auto index = 0;
  270. for (auto &row : content.rows) {
  271. if (index < _rows.size()) {
  272. _rows[index]->updateData(std::move(row));
  273. } else {
  274. if (index > 0) {
  275. _body->insert(
  276. index * 2 - 1,
  277. object_ptr<Ui::FixedHeightWidget>(
  278. this,
  279. st::exportProgressRowSkip));
  280. }
  281. _rows.push_back(_body->insert(
  282. index * 2,
  283. object_ptr<Row>(this, std::move(row)),
  284. st::exportProgressRowPadding));
  285. _rows.back()->show();
  286. }
  287. ++index;
  288. }
  289. const auto fileRandomId = !content.rows.empty()
  290. ? content.rows.back().randomId
  291. : uint64(0);
  292. if (_fileRandomId != fileRandomId) {
  293. _fileShowSkipTimer.cancel();
  294. _skipFile->hide(anim::type::normal);
  295. _fileRandomId = fileRandomId;
  296. if (_fileRandomId) {
  297. _fileShowSkipTimer.callOnce(kShowSkipFileTimeout);
  298. }
  299. }
  300. for (const auto count = _rows.size(); index != count; ++index) {
  301. _rows[index]->updateData(Content::Row());
  302. }
  303. if (_rows.size() != wasCount) {
  304. _body->resizeToWidth(width());
  305. }
  306. }
  307. void ProgressWidget::showDone() {
  308. _cancel = nullptr;
  309. _skipFile->hide(anim::type::instant);
  310. _fileShowSkipTimer.cancel();
  311. _about->setText(tr::lng_export_about_done(tr::now));
  312. _done = base::make_unique_q<Ui::RoundButton>(
  313. this,
  314. tr::lng_export_done(),
  315. st::exportDoneButton);
  316. const auto desired = std::min(
  317. st::exportDoneButton.style.font->width(tr::lng_export_done(tr::now))
  318. + st::exportDoneButton.height
  319. - st::exportDoneButton.style.font->height,
  320. st::exportPanelSize.width() - 2 * st::exportCancelBottom);
  321. if (_done->width() < desired) {
  322. _done->setFullWidth(desired);
  323. }
  324. _done->clicks(
  325. ) | rpl::to_empty
  326. | rpl::start_to_stream(_doneClicks, _done->lifetime());
  327. setupBottomButton(_done.get());
  328. }
  329. ProgressWidget::~ProgressWidget() = default;
  330. } // namespace View
  331. } // namespace Export