box_content.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. // This file is part of Desktop App Toolkit,
  2. // a set of libraries for developing nice desktop applications.
  3. //
  4. // For license and copyright information please follow this link:
  5. // https://github.com/desktop-app/legal/blob/master/LEGAL
  6. //
  7. #include "ui/layers/box_content.h"
  8. #include "ui/widgets/buttons.h"
  9. #include "ui/widgets/scroll_area.h"
  10. #include "ui/widgets/labels.h"
  11. #include "ui/widgets/shadow.h"
  12. #include "ui/wrap/fade_wrap.h"
  13. #include "ui/text/text_utilities.h"
  14. #include "ui/rect_part.h"
  15. #include "ui/painter.h"
  16. #include "ui/qt_weak_factory.h"
  17. #include "ui/ui_utility.h"
  18. #include "base/timer.h"
  19. #include "styles/style_layers.h"
  20. #include "styles/palette.h"
  21. namespace Ui {
  22. namespace {
  23. class BoxShow final : public Show {
  24. public:
  25. explicit BoxShow(not_null<Ui::BoxContent*> box);
  26. ~BoxShow();
  27. void showOrHideBoxOrLayer(
  28. std::variant<
  29. v::null_t,
  30. object_ptr<BoxContent>,
  31. std::unique_ptr<LayerWidget>> &&layer,
  32. LayerOptions options,
  33. anim::type animated) const override;
  34. [[nodiscard]] not_null<QWidget*> toastParent() const override;
  35. [[nodiscard]] bool valid() const override;
  36. operator bool() const override;
  37. private:
  38. BoxShow(QPointer<BoxContent> weak, ShowPtr wrapped);
  39. bool resolve() const;
  40. const QPointer<Ui::BoxContent> _weak;
  41. mutable std::shared_ptr<Show> _wrapped;
  42. rpl::lifetime _lifetime;
  43. };
  44. BoxShow::BoxShow(not_null<BoxContent*> box)
  45. : BoxShow(MakeWeak(box.get()), nullptr) {
  46. }
  47. BoxShow::BoxShow(QPointer<BoxContent> weak, ShowPtr wrapped)
  48. : _weak(weak)
  49. , _wrapped(std::move(wrapped)) {
  50. if (!resolve()) {
  51. if (const auto box = _weak.data()) {
  52. box->boxClosing(
  53. ) | rpl::start_with_next([=] {
  54. resolve();
  55. _lifetime.destroy();
  56. }, _lifetime);
  57. }
  58. }
  59. }
  60. BoxShow::~BoxShow() = default;
  61. bool BoxShow::resolve() const {
  62. if (_wrapped) {
  63. return true;
  64. } else if (const auto strong = _weak.data()) {
  65. if (strong->hasDelegate()) {
  66. _wrapped = strong->getDelegate()->showFactory()();
  67. return true;
  68. }
  69. }
  70. return false;
  71. }
  72. void BoxShow::showOrHideBoxOrLayer(
  73. std::variant<
  74. v::null_t,
  75. object_ptr<BoxContent>,
  76. std::unique_ptr<LayerWidget>> &&layer,
  77. LayerOptions options,
  78. anim::type animated) const {
  79. if (resolve()) {
  80. _wrapped->showOrHideBoxOrLayer(std::move(layer), options, animated);
  81. }
  82. }
  83. not_null<QWidget*> BoxShow::toastParent() const {
  84. if (resolve()) {
  85. return _wrapped->toastParent();
  86. }
  87. Unexpected("Stale BoxShow::toastParent call.");
  88. }
  89. bool BoxShow::valid() const {
  90. return resolve() && _wrapped->valid();
  91. }
  92. BoxShow::operator bool() const {
  93. return valid();
  94. }
  95. } // namespace
  96. void BoxContent::setTitle(rpl::producer<QString> title) {
  97. getDelegate()->setTitle(std::move(title) | Text::ToWithEntities());
  98. }
  99. QPointer<AbstractButton> BoxContent::addButton(
  100. object_ptr<AbstractButton> button) {
  101. auto result = QPointer<AbstractButton>(button.data());
  102. getDelegate()->addButton(std::move(button));
  103. return result;
  104. }
  105. QPointer<RoundButton> BoxContent::addButton(
  106. rpl::producer<QString> text,
  107. Fn<void()> clickCallback) {
  108. return addButton(
  109. std::move(text),
  110. std::move(clickCallback),
  111. getDelegate()->style().button);
  112. }
  113. QPointer<RoundButton> BoxContent::addButton(
  114. rpl::producer<QString> text,
  115. const style::RoundButton &st) {
  116. return addButton(std::move(text), nullptr, st);
  117. }
  118. QPointer<RoundButton> BoxContent::addButton(
  119. rpl::producer<QString> text,
  120. Fn<void()> clickCallback,
  121. const style::RoundButton &st) {
  122. auto button = object_ptr<RoundButton>(this, std::move(text), st);
  123. auto result = QPointer<RoundButton>(button.data());
  124. result->setTextTransform(RoundButton::TextTransform::NoTransform);
  125. result->setClickedCallback(std::move(clickCallback));
  126. getDelegate()->addButton(std::move(button));
  127. return result;
  128. }
  129. QPointer<AbstractButton> BoxContent::addLeftButton(
  130. object_ptr<AbstractButton> button) {
  131. auto result = QPointer<AbstractButton>(button.data());
  132. getDelegate()->addLeftButton(std::move(button));
  133. return result;
  134. }
  135. QPointer<RoundButton> BoxContent::addLeftButton(
  136. rpl::producer<QString> text,
  137. Fn<void()> clickCallback) {
  138. return addLeftButton(
  139. std::move(text),
  140. std::move(clickCallback),
  141. getDelegate()->style().button);
  142. }
  143. QPointer<RoundButton> BoxContent::addLeftButton(
  144. rpl::producer<QString> text,
  145. Fn<void()> clickCallback,
  146. const style::RoundButton &st) {
  147. auto button = object_ptr<RoundButton>(this, std::move(text), st);
  148. const auto result = QPointer<RoundButton>(button.data());
  149. result->setTextTransform(RoundButton::TextTransform::NoTransform);
  150. result->setClickedCallback(std::move(clickCallback));
  151. getDelegate()->addLeftButton(std::move(button));
  152. return result;
  153. }
  154. QPointer<AbstractButton> BoxContent::addTopButton(
  155. object_ptr<AbstractButton> button) {
  156. auto result = QPointer<AbstractButton>(button.data());
  157. getDelegate()->addTopButton(std::move(button));
  158. return result;
  159. }
  160. QPointer<IconButton> BoxContent::addTopButton(
  161. const style::IconButton &st,
  162. Fn<void()> clickCallback) {
  163. auto button = object_ptr<IconButton>(this, st);
  164. const auto result = QPointer<IconButton>(button.data());
  165. result->setClickedCallback(std::move(clickCallback));
  166. getDelegate()->addTopButton(std::move(button));
  167. return result;
  168. }
  169. void BoxContent::setInner(
  170. object_ptr<TWidget> inner,
  171. const style::ScrollArea &st) {
  172. if (inner) {
  173. getDelegate()->setLayerType(true);
  174. _scroll.create(this, st);
  175. _scroll->setGeometryToLeft(0, _innerTopSkip, width(), 0);
  176. _scroll->setOwnedWidget(std::move(inner));
  177. if (_topShadow) {
  178. _topShadow->raise();
  179. _bottomShadow->raise();
  180. } else {
  181. _topShadow.create(this);
  182. _bottomShadow.create(this);
  183. }
  184. if (!_preparing) {
  185. // We didn't set dimensions yet, this will be called from finishPrepare();
  186. finishScrollCreate();
  187. }
  188. } else {
  189. getDelegate()->setLayerType(false);
  190. _scroll.destroyDelayed();
  191. _topShadow.destroyDelayed();
  192. _bottomShadow.destroyDelayed();
  193. }
  194. }
  195. void BoxContent::finishPrepare() {
  196. _preparing = false;
  197. if (_scroll) {
  198. finishScrollCreate();
  199. }
  200. setInnerFocus();
  201. }
  202. void BoxContent::finishScrollCreate() {
  203. Expects(_scroll != nullptr);
  204. if (!_scroll->isHidden()) {
  205. _scroll->show();
  206. }
  207. updateScrollAreaGeometry();
  208. _scroll->scrolls(
  209. ) | rpl::start_with_next([=] {
  210. updateInnerVisibleTopBottom();
  211. updateShadowsVisibility();
  212. }, lifetime());
  213. _scroll->innerResizes(
  214. ) | rpl::start_with_next([=] {
  215. updateInnerVisibleTopBottom();
  216. updateShadowsVisibility();
  217. }, lifetime());
  218. _draggingScroll.scrolls(
  219. ) | rpl::start_with_next([=](int delta) {
  220. if (_scroll) {
  221. _scroll->scrollToY(_scroll->scrollTop() + delta);
  222. }
  223. }, lifetime());
  224. }
  225. void BoxContent::scrollToWidget(not_null<QWidget*> widget) {
  226. if (_scroll) {
  227. _scroll->scrollToWidget(widget);
  228. }
  229. }
  230. void BoxContent::scrollToY(int top, int bottom) {
  231. scrollTo({ top, bottom });
  232. }
  233. void BoxContent::scrollTo(ScrollToRequest request, anim::type animated) {
  234. if (_scroll) {
  235. const auto v = _scroll->computeScrollToY(request.ymin, request.ymax);
  236. const auto now = _scroll->scrollTop();
  237. if (animated == anim::type::instant || v == now) {
  238. _scrollAnimation.stop();
  239. _scroll->scrollToY(v);
  240. } else {
  241. _scrollAnimation.start([=] {
  242. _scroll->scrollToY(_scrollAnimation.value(v));
  243. }, now, v, st::slideWrapDuration, anim::sineInOut);
  244. }
  245. }
  246. }
  247. void BoxContent::sendScrollViewportEvent(not_null<QEvent*> event) {
  248. if (_scroll) {
  249. _scroll->viewportEvent(event);
  250. }
  251. }
  252. rpl::producer<> BoxContent::scrolls() const {
  253. return _scroll ? _scroll->scrolls() : rpl::never<>();
  254. }
  255. int BoxContent::scrollTop() const {
  256. return _scroll ? _scroll->scrollTop() : 0;
  257. }
  258. int BoxContent::scrollHeight() const {
  259. return _scroll ? _scroll->height() : 0;
  260. }
  261. base::weak_ptr<Toast::Instance> BoxContent::showToast(
  262. Toast::Config &&config) {
  263. return BoxShow(this).showToast(std::move(config));
  264. }
  265. base::weak_ptr<Toast::Instance> BoxContent::showToast(
  266. TextWithEntities &&text,
  267. crl::time duration) {
  268. return BoxShow(this).showToast(std::move(text), duration);
  269. }
  270. base::weak_ptr<Toast::Instance> BoxContent::showToast(
  271. const QString &text,
  272. crl::time duration) {
  273. return BoxShow(this).showToast(text, duration);
  274. }
  275. std::shared_ptr<Show> BoxContent::uiShow() {
  276. return std::make_shared<BoxShow>(this);
  277. }
  278. void BoxContent::scrollByDraggingDelta(int delta) {
  279. _draggingScroll.checkDeltaScroll(_scroll ? delta : 0);
  280. }
  281. void BoxContent::updateInnerVisibleTopBottom() {
  282. const auto widget = static_cast<TWidget*>(_scroll
  283. ? _scroll->widget()
  284. : nullptr);
  285. if (widget) {
  286. const auto top = _scroll->scrollTop();
  287. widget->setVisibleTopBottom(top, top + _scroll->height());
  288. }
  289. }
  290. void BoxContent::updateShadowsVisibility(anim::type animated) {
  291. if (!_scroll) {
  292. return;
  293. }
  294. const auto top = _scroll->scrollTop();
  295. _topShadow->toggle(
  296. ((top > 0)
  297. || (_innerTopSkip > 0
  298. && !getDelegate()->style().shadowIgnoreTopSkip)),
  299. animated);
  300. _bottomShadow->toggle(
  301. (top < _scroll->scrollTopMax())
  302. || (_innerBottomSkip > 0
  303. && !getDelegate()->style().shadowIgnoreBottomSkip),
  304. animated);
  305. }
  306. void BoxContent::setDimensionsToContent(
  307. int newWidth,
  308. not_null<RpWidget*> content) {
  309. content->resizeToWidth(newWidth);
  310. content->heightValue(
  311. ) | rpl::start_with_next([=](int height) {
  312. setDimensions(newWidth, height);
  313. }, content->lifetime());
  314. }
  315. void BoxContent::setInnerTopSkip(int innerTopSkip, bool scrollBottomFixed) {
  316. if (_innerTopSkip != innerTopSkip) {
  317. const auto delta = innerTopSkip - _innerTopSkip;
  318. _innerTopSkip = innerTopSkip;
  319. if (_scroll && width() > 0) {
  320. auto scrollTopWas = _scroll->scrollTop();
  321. updateScrollAreaGeometry();
  322. if (scrollBottomFixed) {
  323. _scroll->scrollToY(scrollTopWas + delta);
  324. }
  325. }
  326. }
  327. }
  328. void BoxContent::setInnerBottomSkip(int innerBottomSkip) {
  329. if (_innerBottomSkip != innerBottomSkip) {
  330. _innerBottomSkip = innerBottomSkip;
  331. if (_scroll && width() > 0) {
  332. updateScrollAreaGeometry();
  333. }
  334. }
  335. }
  336. void BoxContent::setInnerVisible(bool scrollAreaVisible) {
  337. if (_scroll) {
  338. _scroll->setVisible(scrollAreaVisible);
  339. }
  340. }
  341. QPixmap BoxContent::grabInnerCache() {
  342. const auto isTopShadowVisible = !_topShadow->isHidden();
  343. const auto isBottomShadowVisible = !_bottomShadow->isHidden();
  344. if (isTopShadowVisible) {
  345. _topShadow->setVisible(false);
  346. }
  347. if (isBottomShadowVisible) {
  348. _bottomShadow->setVisible(false);
  349. }
  350. const auto result = GrabWidget(this, _scroll->geometry());
  351. if (isTopShadowVisible) {
  352. _topShadow->setVisible(true);
  353. }
  354. if (isBottomShadowVisible) {
  355. _bottomShadow->setVisible(true);
  356. }
  357. return result;
  358. }
  359. void BoxContent::resizeEvent(QResizeEvent *e) {
  360. if (_scroll) {
  361. updateScrollAreaGeometry();
  362. }
  363. }
  364. void BoxContent::keyPressEvent(QKeyEvent *e) {
  365. if (e->key() == Qt::Key_Escape && !_closeByEscape) {
  366. e->accept();
  367. } else {
  368. RpWidget::keyPressEvent(e);
  369. }
  370. }
  371. void BoxContent::updateScrollAreaGeometry() {
  372. const auto newScrollHeight = height() - _innerTopSkip - _innerBottomSkip;
  373. const auto changed = (_scroll->height() != newScrollHeight);
  374. _scroll->setGeometryToLeft(0, _innerTopSkip, width(), newScrollHeight);
  375. _topShadow->entity()->resize(width(), st::lineWidth);
  376. _topShadow->moveToLeft(0, _innerTopSkip);
  377. _bottomShadow->entity()->resize(width(), st::lineWidth);
  378. _bottomShadow->moveToLeft(
  379. 0,
  380. height() - _innerBottomSkip - st::lineWidth);
  381. if (changed) {
  382. updateInnerVisibleTopBottom();
  383. updateShadowsVisibility(anim::type::instant);
  384. }
  385. }
  386. object_ptr<TWidget> BoxContent::doTakeInnerWidget() {
  387. return _scroll->takeWidget<TWidget>();
  388. }
  389. void BoxContent::paintEvent(QPaintEvent *e) {
  390. Painter p(this);
  391. if (testAttribute(Qt::WA_OpaquePaintEvent)) {
  392. const auto &color = getDelegate()->style().bg;
  393. for (const auto &rect : e->region()) {
  394. p.fillRect(rect, color);
  395. }
  396. }
  397. }
  398. } // namespace Ui