settings_intro.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  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 "settings/settings_intro.h"
  8. #include "settings/settings_advanced.h"
  9. #include "settings/settings_main.h"
  10. #include "settings/settings_chat.h"
  11. #include "settings/settings_codes.h"
  12. #include "ui/basic_click_handlers.h"
  13. #include "ui/wrap/fade_wrap.h"
  14. #include "ui/wrap/vertical_layout.h"
  15. #include "ui/widgets/shadow.h"
  16. #include "ui/widgets/buttons.h"
  17. #include "ui/widgets/scroll_area.h"
  18. #include "ui/cached_round_corners.h"
  19. #include "ui/vertical_list.h"
  20. #include "lang/lang_keys.h"
  21. #include "boxes/abstract_box.h"
  22. #include "window/window_controller.h"
  23. #include "styles/style_settings.h"
  24. #include "styles/style_layers.h"
  25. #include "styles/style_info.h"
  26. namespace Settings {
  27. namespace {
  28. class TopBar : public Ui::RpWidget {
  29. public:
  30. TopBar(QWidget *parent, const style::InfoTopBar &st);
  31. void setTitle(rpl::producer<QString> &&title);
  32. template <typename ButtonWidget>
  33. ButtonWidget *addButton(base::unique_qptr<ButtonWidget> button) {
  34. auto result = button.get();
  35. pushButton(std::move(button));
  36. return result;
  37. }
  38. protected:
  39. int resizeGetHeight(int newWidth) override;
  40. void paintEvent(QPaintEvent *e) override;
  41. private:
  42. void updateControlsGeometry(int newWidth);
  43. Ui::RpWidget *pushButton(base::unique_qptr<Ui::RpWidget> button);
  44. const style::InfoTopBar &_st;
  45. std::vector<base::unique_qptr<Ui::RpWidget>> _buttons;
  46. QPointer<Ui::FlatLabel> _title;
  47. };
  48. object_ptr<Ui::RpWidget> CreateIntroSettings(
  49. QWidget *parent,
  50. not_null<Window::Controller*> window) {
  51. auto result = object_ptr<Ui::VerticalLayout>(parent);
  52. Ui::AddDivider(result);
  53. Ui::AddSkip(result);
  54. SetupLanguageButton(window, result);
  55. SetupConnectionType(window, &window->account(), result);
  56. Ui::AddSkip(result);
  57. if (HasUpdate()) {
  58. Ui::AddDivider(result);
  59. Ui::AddSkip(result);
  60. SetupUpdate(result);
  61. Ui::AddSkip(result);
  62. }
  63. {
  64. auto wrap = object_ptr<Ui::VerticalLayout>(result);
  65. SetupSystemIntegrationContent(
  66. window->sessionController(),
  67. wrap.data());
  68. SetupWindowTitleContent(
  69. window->sessionController(),
  70. wrap.data());
  71. if (wrap->count() > 0) {
  72. Ui::AddDivider(result);
  73. Ui::AddSkip(result);
  74. result->add(object_ptr<Ui::OverrideMargins>(
  75. result,
  76. std::move(wrap)));
  77. Ui::AddSkip(result);
  78. }
  79. }
  80. Ui::AddDivider(result);
  81. Ui::AddSkip(result);
  82. SetupInterfaceScale(window, result, false);
  83. SetupDefaultThemes(window, result);
  84. Ui::AddSkip(result);
  85. if (anim::Disabled()) {
  86. Ui::AddDivider(result);
  87. Ui::AddSkip(result);
  88. SetupAnimations(window, result);
  89. Ui::AddSkip(result);
  90. }
  91. Ui::AddDivider(result);
  92. Ui::AddSkip(result);
  93. AddButtonWithIcon(
  94. result,
  95. tr::lng_settings_faq(),
  96. st::settingsButtonNoIcon
  97. )->addClickHandler([] {
  98. OpenFaq(nullptr);
  99. });
  100. return result;
  101. }
  102. TopBar::TopBar(QWidget *parent, const style::InfoTopBar &st)
  103. : RpWidget(parent)
  104. , _st(st) {
  105. }
  106. void TopBar::setTitle(rpl::producer<QString> &&title) {
  107. if (_title) {
  108. delete _title;
  109. }
  110. _title = Ui::CreateChild<Ui::FlatLabel>(
  111. this,
  112. std::move(title),
  113. _st.title);
  114. updateControlsGeometry(width());
  115. }
  116. Ui::RpWidget *TopBar::pushButton(base::unique_qptr<Ui::RpWidget> button) {
  117. auto wrapped = std::move(button);
  118. auto weak = wrapped.get();
  119. _buttons.push_back(std::move(wrapped));
  120. weak->widthValue(
  121. ) | rpl::start_with_next([this] {
  122. updateControlsGeometry(width());
  123. }, lifetime());
  124. return weak;
  125. }
  126. int TopBar::resizeGetHeight(int newWidth) {
  127. updateControlsGeometry(newWidth);
  128. return _st.height;
  129. }
  130. void TopBar::updateControlsGeometry(int newWidth) {
  131. auto right = 0;
  132. for (auto &button : _buttons) {
  133. if (!button) continue;
  134. button->moveToRight(right, 0, newWidth);
  135. right += button->width();
  136. }
  137. if (_title) {
  138. _title->moveToLeft(
  139. _st.titlePosition.x(),
  140. _st.titlePosition.y(),
  141. newWidth);
  142. }
  143. }
  144. void TopBar::paintEvent(QPaintEvent *e) {
  145. const auto radius = st::boxRadius;
  146. QPainter(this).fillRect(
  147. e->rect().intersected({ 0, radius, width(), height() - radius }),
  148. _st.bg);
  149. }
  150. } // namespace
  151. class IntroWidget : public Ui::RpWidget {
  152. public:
  153. IntroWidget(
  154. QWidget *parent,
  155. not_null<Window::Controller*> window);
  156. void forceContentRepaint();
  157. rpl::producer<int> desiredHeightValue() const override;
  158. void updateGeometry(QRect newGeometry, int additionalScroll);
  159. int scrollTillBottom(int forHeight) const;
  160. rpl::producer<int> scrollTillBottomChanges() const;
  161. void setInnerFocus();
  162. ~IntroWidget();
  163. protected:
  164. void resizeEvent(QResizeEvent *e) override;
  165. void keyPressEvent(QKeyEvent *e) override;
  166. private:
  167. void updateControlsGeometry();
  168. QRect contentGeometry() const;
  169. void setInnerWidget(object_ptr<Ui::RpWidget> content);
  170. void showContent(not_null<Window::Controller*> window);
  171. rpl::producer<bool> topShadowToggledValue() const;
  172. void createTopBar(not_null<Window::Controller*> window);
  173. void applyAdditionalScroll(int additionalScroll);
  174. rpl::variable<int> _scrollTopSkip = -1;
  175. rpl::event_stream<int> _scrollTillBottomChanges;
  176. object_ptr<Ui::RpWidget> _wrap;
  177. not_null<Ui::ScrollArea*> _scroll;
  178. Ui::PaddingWrap<Ui::RpWidget> *_innerWrap = nullptr;
  179. int _innerDesiredHeight = 0;
  180. int _additionalScroll = 0;
  181. object_ptr<TopBar> _topBar = { nullptr };
  182. object_ptr<Ui::FadeShadow> _topShadow;
  183. };
  184. IntroWidget::IntroWidget(
  185. QWidget *parent,
  186. not_null<Window::Controller*> window)
  187. : RpWidget(parent)
  188. , _wrap(this)
  189. , _scroll(Ui::CreateChild<Ui::ScrollArea>(_wrap.data()))
  190. , _topShadow(this) {
  191. _wrap->setAttribute(Qt::WA_OpaquePaintEvent);
  192. _wrap->paintRequest(
  193. ) | rpl::start_with_next([=](QRect clip) {
  194. auto p = QPainter(_wrap.data());
  195. p.fillRect(clip, st::boxBg);
  196. }, _wrap->lifetime());
  197. _scrollTopSkip.changes(
  198. ) | rpl::start_with_next([this] {
  199. updateControlsGeometry();
  200. }, lifetime());
  201. createTopBar(window);
  202. showContent(window);
  203. _topShadow->toggleOn(
  204. topShadowToggledValue(
  205. ) | rpl::filter([](bool shown) {
  206. return true;
  207. }));
  208. }
  209. void IntroWidget::updateControlsGeometry() {
  210. if (!_innerWrap) {
  211. return;
  212. }
  213. _topBar->resizeToWidth(width());
  214. _topShadow->resizeToWidth(width());
  215. _topShadow->moveToLeft(0, _topBar->height());
  216. _wrap->setGeometry(contentGeometry());
  217. auto scrollGeometry = _wrap->rect().marginsRemoved(
  218. QMargins(0, _scrollTopSkip.current(), 0, 0));
  219. if (_scroll->geometry() != scrollGeometry) {
  220. _scroll->setGeometry(scrollGeometry);
  221. _innerWrap->resizeToWidth(_scroll->width());
  222. }
  223. if (!_scroll->isHidden()) {
  224. auto scrollTop = _scroll->scrollTop();
  225. _innerWrap->setVisibleTopBottom(
  226. scrollTop,
  227. scrollTop + _scroll->height());
  228. }
  229. }
  230. void IntroWidget::forceContentRepaint() {
  231. // WA_OpaquePaintEvent on TopBar creates render glitches when
  232. // animating the LayerWidget's height :( Fixing by repainting.
  233. if (_topBar) {
  234. _topBar->update();
  235. }
  236. _scroll->update();
  237. if (_innerWrap) {
  238. _innerWrap->update();
  239. }
  240. }
  241. void IntroWidget::createTopBar(not_null<Window::Controller*> window) {
  242. _topBar.create(this, st::infoLayerTopBar);
  243. _topBar->setTitle(tr::lng_menu_settings());
  244. auto close = _topBar->addButton(
  245. base::make_unique_q<Ui::IconButton>(
  246. _topBar,
  247. st::infoLayerTopBarClose));
  248. close->addClickHandler([=] {
  249. window->hideSettingsAndLayer();
  250. });
  251. _topBar->lower();
  252. _topBar->resizeToWidth(width());
  253. _topBar->show();
  254. }
  255. void IntroWidget::setInnerWidget(object_ptr<Ui::RpWidget> content) {
  256. _innerWrap = _scroll->setOwnedWidget(
  257. object_ptr<Ui::PaddingWrap<Ui::RpWidget>>(
  258. this,
  259. std::move(content),
  260. _innerWrap ? _innerWrap->padding() : style::margins()));
  261. _innerWrap->move(0, 0);
  262. // MSVC BUG + REGRESSION rpl::mappers::tuple :(
  263. rpl::combine(
  264. _scroll->scrollTopValue(),
  265. _scroll->heightValue(),
  266. _innerWrap->entity()->desiredHeightValue()
  267. ) | rpl::start_with_next([this](
  268. int top,
  269. int height,
  270. int desired) {
  271. const auto bottom = top + height;
  272. _innerDesiredHeight = desired;
  273. _innerWrap->setVisibleTopBottom(top, bottom);
  274. _scrollTillBottomChanges.fire_copy(std::max(desired - bottom, 0));
  275. }, _innerWrap->lifetime());
  276. }
  277. rpl::producer<bool> IntroWidget::topShadowToggledValue() const {
  278. using namespace rpl::mappers;
  279. return rpl::combine(
  280. _scroll->scrollTopValue(),
  281. _scrollTopSkip.value()
  282. ) | rpl::map((_1 > 0) || (_2 > 0));
  283. }
  284. void IntroWidget::showContent(not_null<Window::Controller*> window) {
  285. setInnerWidget(CreateIntroSettings(_scroll, window));
  286. _additionalScroll = 0;
  287. updateControlsGeometry();
  288. _topShadow->raise();
  289. _topShadow->finishAnimating();
  290. }
  291. void IntroWidget::setInnerFocus() {
  292. setFocus();
  293. }
  294. rpl::producer<int> IntroWidget::desiredHeightValue() const {
  295. using namespace rpl::mappers;
  296. return rpl::combine(
  297. _topBar->heightValue(),
  298. _innerWrap->entity()->desiredHeightValue(),
  299. _scrollTopSkip.value()
  300. ) | rpl::map(_1 + _2 + _3);
  301. }
  302. QRect IntroWidget::contentGeometry() const {
  303. return rect().marginsRemoved({ 0, _topBar->height(), 0, 0 });
  304. }
  305. void IntroWidget::resizeEvent(QResizeEvent *e) {
  306. updateControlsGeometry();
  307. }
  308. void IntroWidget::keyPressEvent(QKeyEvent *e) {
  309. crl::on_main(this, [text = e->text()]{
  310. CodesFeedString(nullptr, text);
  311. });
  312. return RpWidget::keyPressEvent(e);
  313. }
  314. void IntroWidget::applyAdditionalScroll(int additionalScroll) {
  315. if (_innerWrap) {
  316. _innerWrap->setPadding({ 0, 0, 0, additionalScroll });
  317. }
  318. }
  319. void IntroWidget::updateGeometry(QRect newGeometry, int additionalScroll) {
  320. auto scrollChanged = (_additionalScroll != additionalScroll);
  321. auto geometryChanged = (geometry() != newGeometry);
  322. auto shrinkingContent = (additionalScroll < _additionalScroll);
  323. _additionalScroll = additionalScroll;
  324. if (geometryChanged) {
  325. if (shrinkingContent) {
  326. setGeometry(newGeometry);
  327. }
  328. if (scrollChanged) {
  329. applyAdditionalScroll(additionalScroll);
  330. }
  331. if (!shrinkingContent) {
  332. setGeometry(newGeometry);
  333. }
  334. } else if (scrollChanged) {
  335. applyAdditionalScroll(additionalScroll);
  336. }
  337. }
  338. int IntroWidget::scrollTillBottom(int forHeight) const {
  339. auto scrollHeight = forHeight
  340. - _scrollTopSkip.current()
  341. - _topBar->height();
  342. auto scrollBottom = _scroll->scrollTop() + scrollHeight;
  343. auto desired = _innerDesiredHeight;
  344. return std::max(desired - scrollBottom, 0);
  345. }
  346. rpl::producer<int> IntroWidget::scrollTillBottomChanges() const {
  347. return _scrollTillBottomChanges.events();
  348. }
  349. IntroWidget::~IntroWidget() = default;
  350. LayerWidget::LayerWidget(QWidget*, not_null<Window::Controller*> window)
  351. : _content(this, window) {
  352. setupHeightConsumers();
  353. }
  354. void LayerWidget::setupHeightConsumers() {
  355. _content->scrollTillBottomChanges(
  356. ) | rpl::filter([this] {
  357. return !_inResize;
  358. }) | rpl::start_with_next([this] {
  359. resizeToWidth(width());
  360. }, lifetime());
  361. _content->desiredHeightValue(
  362. ) | rpl::start_with_next([this](int height) {
  363. accumulate_max(_desiredHeight, height);
  364. if (_content && !_inResize) {
  365. resizeToWidth(width());
  366. }
  367. }, lifetime());
  368. }
  369. void LayerWidget::showFinished() {
  370. }
  371. void LayerWidget::parentResized() {
  372. const auto parentSize = parentWidget()->size();
  373. const auto parentWidth = parentSize.width();
  374. const auto newWidth = (parentWidth < MinimalSupportedWidth())
  375. ? parentWidth
  376. : qMin(
  377. parentWidth - 2 * st::infoMinimalLayerMargin,
  378. st::infoDesiredWidth);
  379. resizeToWidth(newWidth);
  380. }
  381. int LayerWidget::MinimalSupportedWidth() {
  382. auto minimalMargins = 2 * st::infoMinimalLayerMargin;
  383. return st::infoMinimalWidth + minimalMargins;
  384. }
  385. int LayerWidget::resizeGetHeight(int newWidth) {
  386. if (!parentWidget() || !_content) {
  387. return 0;
  388. }
  389. _inResize = true;
  390. auto guard = gsl::finally([&] { _inResize = false; });
  391. auto parentSize = parentWidget()->size();
  392. auto windowWidth = parentSize.width();
  393. auto windowHeight = parentSize.height();
  394. auto newLeft = (windowWidth - newWidth) / 2;
  395. if (!newLeft) {
  396. _content->updateGeometry({ 0, 0, windowWidth, windowHeight }, 0);
  397. auto newGeometry = QRect(0, 0, windowWidth, windowHeight);
  398. if (newGeometry != geometry()) {
  399. _content->forceContentRepaint();
  400. }
  401. if (newGeometry.topLeft() != geometry().topLeft()) {
  402. move(newGeometry.topLeft());
  403. }
  404. _tillTop = _tillBottom = true;
  405. return windowHeight;
  406. }
  407. auto newTop = std::clamp(
  408. windowHeight / 24,
  409. st::infoLayerTopMinimal,
  410. st::infoLayerTopMaximal);
  411. auto newBottom = newTop;
  412. auto desiredHeight = _desiredHeight + st::boxRadius;
  413. accumulate_min(desiredHeight, windowHeight - newTop - newBottom);
  414. // First resize content to new width and get the new desired height.
  415. auto contentLeft = 0;
  416. auto contentTop = 0;
  417. auto contentBottom = st::boxRadius;
  418. auto contentWidth = newWidth;
  419. auto contentHeight = desiredHeight - contentTop - contentBottom;
  420. auto scrollTillBottom = _content->scrollTillBottom(contentHeight);
  421. auto additionalScroll = std::min(scrollTillBottom, newBottom);
  422. desiredHeight += additionalScroll;
  423. contentHeight += additionalScroll;
  424. _tillTop = false;
  425. _tillBottom = (newTop + desiredHeight >= windowHeight);
  426. if (_tillBottom) {
  427. contentHeight += contentBottom;
  428. additionalScroll += contentBottom;
  429. }
  430. _content->updateGeometry({
  431. contentLeft,
  432. contentTop,
  433. contentWidth,
  434. contentHeight }, additionalScroll);
  435. auto newGeometry = QRect(newLeft, newTop, newWidth, desiredHeight);
  436. if (newGeometry != geometry()) {
  437. _content->forceContentRepaint();
  438. }
  439. if (newGeometry.topLeft() != geometry().topLeft()) {
  440. move(newGeometry.topLeft());
  441. }
  442. return desiredHeight;
  443. }
  444. void LayerWidget::doSetInnerFocus() {
  445. _content->setInnerFocus();
  446. }
  447. void LayerWidget::paintEvent(QPaintEvent *e) {
  448. auto p = QPainter(this);
  449. auto clip = e->rect();
  450. auto r = st::boxRadius;
  451. const auto &pixmaps = Ui::CachedCornerPixmaps(Ui::BoxCorners);
  452. if (!_tillTop && clip.intersects({ 0, 0, width(), r })) {
  453. Ui::FillRoundRect(p, 0, 0, width(), r, st::boxBg, {
  454. .p = { pixmaps.p[0], pixmaps.p[1], QPixmap(), QPixmap() },
  455. });
  456. }
  457. if (!_tillBottom && clip.intersects({ 0, height() - r, width(), r })) {
  458. Ui::FillRoundRect(p, 0, height() - r, width(), r, st::boxBg, {
  459. .p = { QPixmap(), QPixmap(), pixmaps.p[2], pixmaps.p[3] },
  460. });
  461. }
  462. if (_tillTop) {
  463. p.fillRect(0, 0, width(), r, st::boxBg);
  464. }
  465. }
  466. } // namespace Settings