box_layer_widget.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  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_layer_widget.h"
  8. #include "ui/effects/radial_animation.h"
  9. #include "ui/widgets/buttons.h"
  10. #include "ui/widgets/labels.h"
  11. #include "ui/painter.h"
  12. #include "base/timer.h"
  13. #include "styles/style_layers.h"
  14. #include "styles/palette.h"
  15. namespace Ui {
  16. struct BoxLayerWidget::LoadingProgress {
  17. LoadingProgress(
  18. Fn<void()> &&callback,
  19. const style::InfiniteRadialAnimation &st);
  20. InfiniteRadialAnimation animation;
  21. base::Timer removeTimer;
  22. };
  23. BoxLayerWidget::LoadingProgress::LoadingProgress(
  24. Fn<void()> &&callback,
  25. const style::InfiniteRadialAnimation &st)
  26. : animation(std::move(callback), st) {
  27. }
  28. BoxLayerWidget::BoxLayerWidget(
  29. not_null<LayerStackWidget*> layer,
  30. object_ptr<BoxContent> content)
  31. : LayerWidget(layer)
  32. , _layer(layer)
  33. , _content(std::move(content))
  34. , _roundRect(st::boxRadius, st().bg) {
  35. _content->setParent(this);
  36. _content->setDelegate(this);
  37. _additionalTitle.changes(
  38. ) | rpl::start_with_next([=] {
  39. updateSize();
  40. update();
  41. }, lifetime());
  42. }
  43. BoxLayerWidget::~BoxLayerWidget() = default;
  44. void BoxLayerWidget::setLayerType(bool layerType) {
  45. if (_layerType == layerType) {
  46. return;
  47. }
  48. _layerType = layerType;
  49. updateTitlePosition();
  50. if (_maxContentHeight) {
  51. setDimensions(width(), _maxContentHeight);
  52. }
  53. }
  54. int BoxLayerWidget::titleHeight() const {
  55. return st::boxTitleHeight;
  56. }
  57. const style::Box &BoxLayerWidget::st() const {
  58. return _st
  59. ? *_st
  60. : _layerType
  61. ? (_layer->boxStyleOverrideLayer()
  62. ? *_layer->boxStyleOverrideLayer()
  63. : st::layerBox)
  64. : (_layer->boxStyleOverride()
  65. ? *_layer->boxStyleOverride()
  66. : st::defaultBox);
  67. }
  68. void BoxLayerWidget::setStyle(const style::Box &st) {
  69. _st = &st;
  70. _roundRect.setColor(st.bg);
  71. }
  72. const style::Box &BoxLayerWidget::style() {
  73. return st();
  74. }
  75. int BoxLayerWidget::buttonsHeight() const {
  76. const auto padding = st().buttonPadding;
  77. return padding.top() + st().buttonHeight + padding.bottom();
  78. }
  79. int BoxLayerWidget::buttonsTop() const {
  80. const auto padding = st().buttonPadding;
  81. return height() - padding.bottom() - st().buttonHeight;
  82. }
  83. QRect BoxLayerWidget::loadingRect() const {
  84. const auto padding = st().buttonPadding;
  85. const auto size = st::boxLoadingSize;
  86. const auto skipx = st::boxTitlePosition.x();
  87. const auto skipy = (st().buttonHeight - size) / 2;
  88. return QRect(
  89. skipx,
  90. height() - padding.bottom() - skipy - size,
  91. size,
  92. size);
  93. }
  94. void BoxLayerWidget::paintEvent(QPaintEvent *e) {
  95. Painter p(this);
  96. const auto clip = e->rect();
  97. const auto paintTopRounded = !(_customCornersFilling & RectPart::FullTop)
  98. && clip.intersects(QRect(0, 0, width(), st::boxRadius));
  99. const auto paintBottomRounded = !(_customCornersFilling
  100. & RectPart::FullBottom)
  101. && clip.intersects(
  102. QRect(0, height() - st::boxRadius, width(), st::boxRadius));
  103. if (paintTopRounded || paintBottomRounded) {
  104. _roundRect.paint(p, rect(), RectPart::None
  105. | (paintTopRounded ? RectPart::FullTop : RectPart::None)
  106. | (paintBottomRounded ? RectPart::FullBottom : RectPart::None));
  107. }
  108. const auto other = e->region().intersected(
  109. QRect(0, st::boxRadius, width(), height() - 2 * st::boxRadius));
  110. if (!other.isEmpty()) {
  111. for (const auto &rect : other) {
  112. p.fillRect(rect, st().bg);
  113. }
  114. }
  115. if (!_additionalTitle.current().isEmpty()
  116. && clip.intersects(QRect(0, 0, width(), titleHeight()))) {
  117. paintAdditionalTitle(p);
  118. }
  119. if (_loadingProgress) {
  120. const auto rect = loadingRect();
  121. _loadingProgress->animation.draw(
  122. p,
  123. rect.topLeft(),
  124. rect.size(),
  125. width());
  126. }
  127. }
  128. void BoxLayerWidget::paintAdditionalTitle(Painter &p) {
  129. p.setFont(st::boxTitleAdditionalFont);
  130. p.setPen(st().titleAdditionalFg);
  131. p.drawTextLeft(
  132. _titleLeft + (_title ? _title->width() : 0) + st::boxTitleAdditionalSkip,
  133. _titleTop + st::boxTitleFont->ascent - st::boxTitleAdditionalFont->ascent,
  134. width(),
  135. _additionalTitle.current());
  136. }
  137. void BoxLayerWidget::parentResized() {
  138. auto newHeight = countRealHeight();
  139. auto parentSize = parentWidget()->size();
  140. setGeometry(
  141. (parentSize.width() - width()) / 2,
  142. (parentSize.height() - newHeight) / 2,
  143. width(),
  144. newHeight);
  145. update();
  146. }
  147. void BoxLayerWidget::setTitle(rpl::producer<TextWithEntities> title) {
  148. const auto wasTitle = hasTitle();
  149. if (title) {
  150. _title.create(this, rpl::duplicate(title), st().title);
  151. _title->show();
  152. std::move(
  153. title
  154. ) | rpl::start_with_next([=] {
  155. updateTitlePosition();
  156. }, _title->lifetime());
  157. } else {
  158. _title.destroy();
  159. }
  160. if (wasTitle != hasTitle()) {
  161. updateSize();
  162. }
  163. }
  164. void BoxLayerWidget::setAdditionalTitle(rpl::producer<QString> additional) {
  165. _additionalTitle = std::move(additional);
  166. }
  167. void BoxLayerWidget::triggerButton(int index) {
  168. if (index < _buttons.size()) {
  169. _buttons[index]->clicked(Qt::KeyboardModifiers(), Qt::LeftButton);
  170. }
  171. }
  172. void BoxLayerWidget::setCloseByOutsideClick(bool close) {
  173. _closeByOutsideClick = close;
  174. }
  175. bool BoxLayerWidget::closeByOutsideClick() const {
  176. return _closeByOutsideClick;
  177. }
  178. bool BoxLayerWidget::hasTitle() const {
  179. return (_title != nullptr) || !_additionalTitle.current().isEmpty();
  180. }
  181. void BoxLayerWidget::showBox(
  182. object_ptr<BoxContent> box,
  183. LayerOptions options,
  184. anim::type animated) {
  185. _layer->showBox(std::move(box), options, animated);
  186. }
  187. void BoxLayerWidget::hideLayer() {
  188. _layer->hideLayers(anim::type::normal);
  189. }
  190. void BoxLayerWidget::updateSize() {
  191. setDimensions(width(), _maxContentHeight);
  192. }
  193. void BoxLayerWidget::updateButtonsPositions() {
  194. if (!_buttons.empty() || _leftButton) {
  195. auto padding = st().buttonPadding;
  196. auto right = padding.right();
  197. auto top = buttonsTop();
  198. if (_leftButton) {
  199. _leftButton->moveToLeft(right, top);
  200. }
  201. for (const auto &button : _buttons) {
  202. button->moveToRight(right, top);
  203. right += button->width() + padding.left();
  204. }
  205. }
  206. if (_topButton) {
  207. _topButton->moveToRight(0, 0);
  208. }
  209. }
  210. ShowFactory BoxLayerWidget::showFactory() {
  211. return _layer->showFactory();
  212. }
  213. QPointer<QWidget> BoxLayerWidget::outerContainer() {
  214. return parentWidget();
  215. }
  216. void BoxLayerWidget::updateTitlePosition() {
  217. _titleLeft = st::boxTitlePosition.x();
  218. _titleTop = st::boxTitlePosition.y();
  219. if (_title) {
  220. const auto topButtonSkip = _topButton
  221. ? (_topButton->width() / 2)
  222. : 0;
  223. _title->resizeToNaturalWidth(
  224. width() - _titleLeft * 2 - topButtonSkip);
  225. _title->moveToLeft(_titleLeft, _titleTop);
  226. }
  227. }
  228. void BoxLayerWidget::setCustomCornersFilling(RectParts corners) {
  229. _customCornersFilling = corners;
  230. update();
  231. }
  232. void BoxLayerWidget::clearButtons() {
  233. for (auto &button : base::take(_buttons)) {
  234. button.destroy();
  235. }
  236. _leftButton.destroy();
  237. _topButton = nullptr;
  238. }
  239. void BoxLayerWidget::addButton(object_ptr<AbstractButton> button) {
  240. _buttons.push_back(std::move(button));
  241. const auto raw = _buttons.back().data();
  242. raw->setParent(this);
  243. raw->show();
  244. raw->widthValue(
  245. ) | rpl::start_with_next([=] {
  246. updateButtonsPositions();
  247. }, raw->lifetime());
  248. }
  249. void BoxLayerWidget::addLeftButton(object_ptr<AbstractButton> button) {
  250. _leftButton = std::move(button);
  251. const auto raw = _leftButton.data();
  252. raw->setParent(this);
  253. raw->show();
  254. raw->widthValue(
  255. ) | rpl::start_with_next([=] {
  256. updateButtonsPositions();
  257. }, raw->lifetime());
  258. }
  259. void BoxLayerWidget::addTopButton(object_ptr<AbstractButton> button) {
  260. _topButton = base::unique_qptr<AbstractButton>(button.release());
  261. const auto raw = _topButton.get();
  262. raw->setParent(this);
  263. raw->show();
  264. updateButtonsPositions();
  265. updateTitlePosition();
  266. }
  267. void BoxLayerWidget::showLoading(bool show) {
  268. const auto &st = st::boxLoadingAnimation;
  269. if (!show) {
  270. if (_loadingProgress && !_loadingProgress->removeTimer.isActive()) {
  271. _loadingProgress->removeTimer.callOnce(
  272. st.sineDuration + st.sinePeriod);
  273. _loadingProgress->animation.stop();
  274. }
  275. return;
  276. }
  277. if (!_loadingProgress) {
  278. const auto callback = [=] {
  279. if (!anim::Disabled()) {
  280. const auto t = st::boxLoadingAnimation.thickness;
  281. update(loadingRect().marginsAdded({ t, t, t, t }));
  282. }
  283. };
  284. _loadingProgress = std::make_unique<LoadingProgress>(
  285. callback,
  286. st::boxLoadingAnimation);
  287. _loadingProgress->removeTimer.setCallback([=] {
  288. _loadingProgress = nullptr;
  289. });
  290. } else {
  291. _loadingProgress->removeTimer.cancel();
  292. }
  293. _loadingProgress->animation.start();
  294. }
  295. void BoxLayerWidget::setDimensions(int newWidth, int maxHeight, bool forceCenterPosition) {
  296. _maxContentHeight = maxHeight;
  297. auto fullHeight = countFullHeight();
  298. if (width() != newWidth || _fullHeight != fullHeight) {
  299. _fullHeight = fullHeight;
  300. if (parentWidget()) {
  301. auto oldGeometry = geometry();
  302. resize(newWidth, countRealHeight());
  303. auto newGeometry = geometry();
  304. auto parentHeight = parentWidget()->height();
  305. const auto bottomMargin = st().margin.bottom();
  306. if (newGeometry.top() + newGeometry.height() + bottomMargin > parentHeight
  307. || forceCenterPosition) {
  308. const auto top1 = parentHeight - bottomMargin - newGeometry.height();
  309. const auto top2 = (parentHeight - newGeometry.height()) / 2;
  310. const auto newTop = forceCenterPosition
  311. ? std::min(top1, top2)
  312. : std::max(top1, top2);
  313. if (newTop != newGeometry.top()) {
  314. move(newGeometry.left(), newTop);
  315. resizeEvent(0);
  316. }
  317. }
  318. parentWidget()->update(oldGeometry.united(geometry()).marginsAdded(st::boxRoundShadow.extend));
  319. } else {
  320. resize(newWidth, 0);
  321. }
  322. }
  323. }
  324. int BoxLayerWidget::countRealHeight() const {
  325. const auto &margin = st().margin;
  326. return std::min(
  327. _fullHeight,
  328. parentWidget()->height() - margin.top() - margin.bottom());
  329. }
  330. int BoxLayerWidget::countFullHeight() const {
  331. return contentTop() + _maxContentHeight + buttonsHeight();
  332. }
  333. int BoxLayerWidget::contentTop() const {
  334. return hasTitle()
  335. ? titleHeight()
  336. : _noContentMargin
  337. ?
  338. 0
  339. : st::boxTopMargin;
  340. }
  341. void BoxLayerWidget::resizeEvent(QResizeEvent *e) {
  342. updateButtonsPositions();
  343. updateTitlePosition();
  344. const auto top = contentTop();
  345. _content->resize(width(), height() - top - buttonsHeight());
  346. _content->moveToLeft(0, top);
  347. LayerWidget::resizeEvent(e);
  348. }
  349. void BoxLayerWidget::keyPressEvent(QKeyEvent *e) {
  350. if (e->key() == Qt::Key_Escape) {
  351. closeBox();
  352. } else {
  353. LayerWidget::keyPressEvent(e);
  354. }
  355. }
  356. } // namespace Ui