inner_dropdown.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  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/widgets/inner_dropdown.h"
  8. #include "ui/widgets/scroll_area.h"
  9. #include "ui/widgets/shadow.h"
  10. #include "ui/effects/panel_animation.h"
  11. #include "ui/image/image_prepare.h"
  12. #include "ui/qt_weak_factory.h"
  13. #include "ui/ui_utility.h"
  14. namespace Ui {
  15. InnerDropdown::InnerDropdown(
  16. QWidget *parent,
  17. const style::InnerDropdown &st)
  18. : RpWidget(parent)
  19. , _st(st)
  20. , _roundRect(ImageRoundRadius::Small, _st.bg)
  21. , _hideTimer([=] { hideAnimated(); })
  22. , _scroll(this, _st.scroll) {
  23. _scroll->scrolls(
  24. ) | rpl::start_with_next([=] {
  25. scrolled();
  26. }, lifetime());
  27. hide();
  28. shownValue(
  29. ) | rpl::filter([](bool shown) {
  30. return shown;
  31. }) | rpl::take(1) | rpl::map([=] {
  32. // We can't invoke this before the window is created.
  33. // So instead we start handling them on the first show().
  34. return macWindowDeactivateEvents();
  35. }) | rpl::flatten_latest(
  36. ) | rpl::filter([=] {
  37. return !isHidden();
  38. }) | rpl::start_with_next([=] {
  39. leaveEvent(nullptr);
  40. }, lifetime());
  41. }
  42. QPointer<RpWidget> InnerDropdown::doSetOwnedWidget(
  43. object_ptr<RpWidget> widget) {
  44. auto result = QPointer<RpWidget>(widget);
  45. widget->heightValue(
  46. ) | rpl::skip(1) | rpl::start_with_next([=] {
  47. resizeToContent();
  48. }, widget->lifetime());
  49. auto container = _scroll->setOwnedWidget(
  50. object_ptr<Container>(
  51. _scroll,
  52. std::move(widget),
  53. _st));
  54. container->resizeToWidth(_scroll->width());
  55. container->moveToLeft(0, 0);
  56. container->show();
  57. result->show();
  58. return result;
  59. }
  60. void InnerDropdown::setMaxHeight(int newMaxHeight) {
  61. _maxHeight = newMaxHeight;
  62. resizeToContent();
  63. }
  64. void InnerDropdown::resizeToContent() {
  65. auto newWidth = _st.padding.left() + _st.scrollMargin.left() + _st.scrollMargin.right() + _st.padding.right();
  66. auto newHeight = _st.padding.top() + _st.scrollMargin.top() + _st.scrollMargin.bottom() + _st.padding.bottom();
  67. if (auto widget = static_cast<Container*>(_scroll->widget())) {
  68. widget->resizeToContent();
  69. newWidth += widget->width();
  70. newHeight += widget->height();
  71. }
  72. if (_maxHeight > 0) {
  73. accumulate_min(newHeight, _maxHeight);
  74. }
  75. if (newWidth != width() || newHeight != height()) {
  76. resize(newWidth, newHeight);
  77. update();
  78. finishAnimating();
  79. }
  80. }
  81. void InnerDropdown::resizeEvent(QResizeEvent *e) {
  82. _scroll->setGeometry(rect().marginsRemoved(_st.padding).marginsRemoved(_st.scrollMargin));
  83. if (auto widget = static_cast<TWidget*>(_scroll->widget())) {
  84. widget->resizeToWidth(_scroll->width());
  85. scrolled();
  86. }
  87. }
  88. void InnerDropdown::scrolled() {
  89. if (auto widget = static_cast<TWidget*>(_scroll->widget())) {
  90. int visibleTop = _scroll->scrollTop();
  91. int visibleBottom = visibleTop + _scroll->height();
  92. widget->setVisibleTopBottom(visibleTop, visibleBottom);
  93. }
  94. }
  95. void InnerDropdown::paintEvent(QPaintEvent *e) {
  96. QPainter p(this);
  97. if (_a_show.animating()) {
  98. if (auto opacity = _a_opacity.value(_hiding ? 0. : 1.)) {
  99. // _a_opacity.current(ms)->opacityAnimationCallback()->_showAnimation.reset()
  100. if (_showAnimation) {
  101. _showAnimation->paintFrame(p, 0, 0, width(), _a_show.value(1.), opacity);
  102. }
  103. }
  104. } else if (_a_opacity.animating()) {
  105. p.setOpacity(_a_opacity.value(0.));
  106. p.drawPixmap(0, 0, _cache);
  107. } else if (_hiding || isHidden()) {
  108. hideFinished();
  109. } else if (_showAnimation) {
  110. _showAnimation->paintFrame(p, 0, 0, width(), 1., 1.);
  111. _showAnimation.reset();
  112. showChildren();
  113. } else {
  114. if (!_cache.isNull()) _cache = QPixmap();
  115. const auto inner = rect().marginsRemoved(_st.padding);
  116. Shadow::paint(p, inner, width(), _st.shadow);
  117. _roundRect.paint(p, inner);
  118. }
  119. }
  120. void InnerDropdown::enterEventHook(QEnterEvent *e) {
  121. if (_autoHiding) {
  122. showAnimated(_origin);
  123. }
  124. return RpWidget::enterEventHook(e);
  125. }
  126. void InnerDropdown::leaveEventHook(QEvent *e) {
  127. if (_autoHiding) {
  128. if (_a_show.animating() || _a_opacity.animating()) {
  129. hideAnimated();
  130. } else {
  131. _hideTimer.callOnce(300);
  132. }
  133. }
  134. return RpWidget::leaveEventHook(e);
  135. }
  136. void InnerDropdown::otherEnter() {
  137. if (_autoHiding) {
  138. showAnimated(_origin);
  139. }
  140. }
  141. void InnerDropdown::otherLeave() {
  142. if (_autoHiding) {
  143. if (_a_show.animating() || _a_opacity.animating()) {
  144. hideAnimated();
  145. } else {
  146. _hideTimer.callOnce(0);
  147. }
  148. }
  149. }
  150. void InnerDropdown::setOrigin(PanelAnimation::Origin origin) {
  151. _origin = origin;
  152. }
  153. void InnerDropdown::showAnimated(PanelAnimation::Origin origin) {
  154. setOrigin(origin);
  155. showAnimated();
  156. }
  157. void InnerDropdown::showAnimated() {
  158. _hideTimer.cancel();
  159. showStarted();
  160. }
  161. void InnerDropdown::hideAnimated(HideOption option) {
  162. if (isHidden()) return;
  163. if (option == HideOption::IgnoreShow) {
  164. _ignoreShowEvents = true;
  165. }
  166. if (_hiding) return;
  167. _hideTimer.cancel();
  168. startOpacityAnimation(true);
  169. }
  170. void InnerDropdown::finishAnimating() {
  171. if (_a_show.animating()) {
  172. _a_show.stop();
  173. showAnimationCallback();
  174. }
  175. if (_showAnimation) {
  176. _showAnimation.reset();
  177. showChildren();
  178. }
  179. if (_a_opacity.animating()) {
  180. _a_opacity.stop();
  181. opacityAnimationCallback();
  182. }
  183. }
  184. void InnerDropdown::showFast() {
  185. _hideTimer.cancel();
  186. finishAnimating();
  187. if (isHidden()) {
  188. showChildren();
  189. show();
  190. }
  191. _hiding = false;
  192. }
  193. void InnerDropdown::hideFast() {
  194. if (isHidden()) return;
  195. _hideTimer.cancel();
  196. finishAnimating();
  197. _hiding = false;
  198. hideFinished();
  199. }
  200. void InnerDropdown::hideFinished() {
  201. _a_show.stop();
  202. _showAnimation.reset();
  203. _cache = QPixmap();
  204. _ignoreShowEvents = false;
  205. if (!isHidden()) {
  206. const auto weak = Ui::MakeWeak(this);
  207. if (const auto onstack = _hiddenCallback) {
  208. onstack();
  209. }
  210. if (weak) {
  211. hide();
  212. }
  213. }
  214. }
  215. void InnerDropdown::prepareCache() {
  216. if (_a_opacity.animating()) return;
  217. const auto animating = _a_show.animating();
  218. auto showAnimation = base::take(_a_show);
  219. auto showAnimationData = base::take(_showAnimation);
  220. showChildren();
  221. _cache = GrabWidget(this);
  222. if (animating) {
  223. hideChildren();
  224. }
  225. _showAnimation = base::take(showAnimationData);
  226. _a_show = base::take(showAnimation);
  227. }
  228. void InnerDropdown::startOpacityAnimation(bool hiding) {
  229. const auto weak = Ui::MakeWeak(this);
  230. if (hiding) {
  231. if (const auto onstack = _hideStartCallback) {
  232. onstack();
  233. }
  234. } else if (const auto onstack = _showStartCallback) {
  235. onstack();
  236. }
  237. if (!weak) {
  238. return;
  239. }
  240. _hiding = false;
  241. prepareCache();
  242. _hiding = hiding;
  243. hideChildren();
  244. _a_opacity.start(
  245. [=] { opacityAnimationCallback(); },
  246. _hiding ? 1. : 0.,
  247. _hiding ? 0. : 1.,
  248. _st.duration);
  249. }
  250. void InnerDropdown::showStarted() {
  251. if (_ignoreShowEvents) return;
  252. if (isHidden()) {
  253. show();
  254. startShowAnimation();
  255. return;
  256. } else if (!_hiding) {
  257. return;
  258. }
  259. startOpacityAnimation(false);
  260. }
  261. void InnerDropdown::startShowAnimation() {
  262. if (_showStartCallback) {
  263. _showStartCallback();
  264. }
  265. if (!_a_show.animating()) {
  266. auto opacityAnimation = base::take(_a_opacity);
  267. showChildren();
  268. auto cache = grabForPanelAnimation();
  269. _a_opacity = base::take(opacityAnimation);
  270. const auto pixelRatio = style::DevicePixelRatio();
  271. _showAnimation = std::make_unique<PanelAnimation>(_st.animation, _origin);
  272. auto inner = rect().marginsRemoved(_st.padding);
  273. _showAnimation->setFinalImage(std::move(cache), QRect(inner.topLeft() * pixelRatio, inner.size() * pixelRatio));
  274. _showAnimation->setCornerMasks(
  275. Images::CornersMask(ImageRoundRadius::Small));
  276. _showAnimation->start();
  277. }
  278. hideChildren();
  279. _a_show.start([this] { showAnimationCallback(); }, 0., 1., _st.showDuration);
  280. }
  281. QImage InnerDropdown::grabForPanelAnimation() {
  282. SendPendingMoveResizeEvents(this);
  283. const auto pixelRatio = style::DevicePixelRatio();
  284. auto result = QImage(size() * pixelRatio, QImage::Format_ARGB32_Premultiplied);
  285. result.setDevicePixelRatio(pixelRatio);
  286. result.fill(Qt::transparent);
  287. {
  288. QPainter p(&result);
  289. _roundRect.paint(p, rect().marginsRemoved(_st.padding));
  290. for (const auto child : children()) {
  291. if (const auto widget = qobject_cast<QWidget*>(child)) {
  292. RenderWidget(p, widget, widget->pos());
  293. }
  294. }
  295. }
  296. return result;
  297. }
  298. void InnerDropdown::opacityAnimationCallback() {
  299. update();
  300. if (!_a_opacity.animating()) {
  301. if (_hiding) {
  302. _hiding = false;
  303. hideFinished();
  304. } else if (!_a_show.animating()) {
  305. showChildren();
  306. }
  307. }
  308. }
  309. void InnerDropdown::showAnimationCallback() {
  310. update();
  311. }
  312. bool InnerDropdown::eventFilter(QObject *obj, QEvent *e) {
  313. if (e->type() == QEvent::Enter) {
  314. otherEnter();
  315. } else if (e->type() == QEvent::Leave) {
  316. otherLeave();
  317. } else if (e->type() == QEvent::MouseButtonRelease && static_cast<QMouseEvent*>(e)->button() == Qt::LeftButton) {
  318. if (isHidden() || _hiding) {
  319. otherEnter();
  320. } else {
  321. otherLeave();
  322. }
  323. }
  324. return false;
  325. }
  326. int InnerDropdown::resizeGetHeight(int newWidth) {
  327. auto newHeight = _st.padding.top() + _st.scrollMargin.top() + _st.scrollMargin.bottom() + _st.padding.bottom();
  328. if (auto widget = static_cast<TWidget*>(_scroll->widget())) {
  329. auto containerWidth = newWidth - _st.padding.left() - _st.padding.right() - _st.scrollMargin.left() - _st.scrollMargin.right();
  330. widget->resizeToWidth(containerWidth);
  331. newHeight += widget->height();
  332. }
  333. if (_maxHeight > 0) {
  334. accumulate_min(newHeight, _maxHeight);
  335. }
  336. return newHeight;
  337. }
  338. InnerDropdown::Container::Container(QWidget *parent, object_ptr<TWidget> child, const style::InnerDropdown &st) : TWidget(parent)
  339. , _child(std::move(child))
  340. , _st(st) {
  341. _child->setParent(this);
  342. _child->moveToLeft(_st.scrollPadding.left(), _st.scrollPadding.top());
  343. }
  344. void InnerDropdown::Container::visibleTopBottomUpdated(
  345. int visibleTop,
  346. int visibleBottom) {
  347. setChildVisibleTopBottom(_child, visibleTop, visibleBottom);
  348. }
  349. void InnerDropdown::Container::resizeToContent() {
  350. auto newWidth = _st.scrollPadding.left() + _st.scrollPadding.right();
  351. auto newHeight = _st.scrollPadding.top() + _st.scrollPadding.bottom();
  352. if (auto child = static_cast<TWidget*>(children().front())) {
  353. newWidth += child->width();
  354. newHeight += child->height();
  355. }
  356. if (newWidth != width() || newHeight != height()) {
  357. resize(newWidth, newHeight);
  358. }
  359. }
  360. int InnerDropdown::Container::resizeGetHeight(int newWidth) {
  361. auto innerWidth = newWidth - _st.scrollPadding.left() - _st.scrollPadding.right();
  362. auto result = _st.scrollPadding.top() + _st.scrollPadding.bottom();
  363. _child->resizeToWidth(innerWidth);
  364. _child->moveToLeft(_st.scrollPadding.left(), _st.scrollPadding.top());
  365. result += _child->height();
  366. return result;
  367. }
  368. } // namespace Ui