vertical_layout_reorder.cpp 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  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/wrap/vertical_layout_reorder.h"
  8. #include "ui/wrap/vertical_layout.h"
  9. #include "styles/style_basic.h"
  10. #include <QtGui/QtEvents>
  11. #include <QtWidgets/QApplication>
  12. namespace Ui {
  13. namespace {
  14. constexpr auto kScrollFactor = 0.05;
  15. } // namespace
  16. VerticalLayoutReorder::VerticalLayoutReorder(
  17. not_null<VerticalLayout*> layout,
  18. not_null<ScrollArea*> scroll)
  19. : _layout(layout)
  20. , _scroll(scroll)
  21. , _scrollAnimation([=] { updateScrollCallback(); }) {
  22. }
  23. VerticalLayoutReorder::VerticalLayoutReorder(not_null<VerticalLayout*> layout)
  24. : _layout(layout) {
  25. }
  26. void VerticalLayoutReorder::cancel() {
  27. if (_currentWidget) {
  28. cancelCurrent(indexOf(_currentWidget));
  29. }
  30. _lifetime.destroy();
  31. for (auto i = 0, count = _layout->count(); i != count; ++i) {
  32. _layout->setVerticalShift(i, 0);
  33. }
  34. _entries.clear();
  35. }
  36. void VerticalLayoutReorder::start() {
  37. const auto count = _layout->count();
  38. if (count < 2) {
  39. return;
  40. }
  41. for (auto i = 0; i != count; ++i) {
  42. const auto widget = _layout->widgetAt(i);
  43. const auto eventsProducer = _proxyWidgetCallback
  44. ? _proxyWidgetCallback(i)
  45. : widget;
  46. eventsProducer->events(
  47. ) | rpl::start_with_next_done([=](not_null<QEvent*> e) {
  48. switch (e->type()) {
  49. case QEvent::MouseMove:
  50. mouseMove(
  51. widget,
  52. static_cast<QMouseEvent*>(e.get())->globalPos());
  53. break;
  54. case QEvent::MouseButtonPress:
  55. mousePress(
  56. widget,
  57. static_cast<QMouseEvent*>(e.get())->button(),
  58. static_cast<QMouseEvent*>(e.get())->globalPos());
  59. break;
  60. case QEvent::MouseButtonRelease:
  61. mouseRelease(static_cast<QMouseEvent*>(e.get())->button());
  62. break;
  63. }
  64. }, [=] {
  65. cancel();
  66. }, _lifetime);
  67. _entries.push_back({ widget });
  68. }
  69. }
  70. void VerticalLayoutReorder::addPinnedInterval(int from, int length) {
  71. _pinnedIntervals.push_back({ from, length });
  72. }
  73. void VerticalLayoutReorder::clearPinnedIntervals() {
  74. _pinnedIntervals.clear();
  75. }
  76. void VerticalLayoutReorder::setMouseEventProxy(ProxyCallback callback) {
  77. _proxyWidgetCallback = std::move(callback);
  78. }
  79. bool VerticalLayoutReorder::Interval::isIn(int index) const {
  80. return (index >= from) && (index < (from + length));
  81. }
  82. bool VerticalLayoutReorder::isIndexPinned(int index) const {
  83. return ranges::any_of(_pinnedIntervals, [&](const Interval &i) {
  84. return i.isIn(index);
  85. });
  86. }
  87. void VerticalLayoutReorder::mouseMove(
  88. not_null<RpWidget*> widget,
  89. QPoint position) {
  90. if (_currentWidget != widget) {
  91. return;
  92. } else if (_currentState != State::Started) {
  93. checkForStart(position);
  94. } else {
  95. updateOrder(indexOf(_currentWidget), position);
  96. }
  97. }
  98. void VerticalLayoutReorder::checkForStart(QPoint position) {
  99. const auto shift = position.y() - _currentStart;
  100. const auto delta = QApplication::startDragDistance();
  101. if (std::abs(shift) <= delta) {
  102. return;
  103. }
  104. _currentWidget->raise();
  105. _currentState = State::Started;
  106. _currentStart += (shift > 0) ? delta : -delta;
  107. const auto index = indexOf(_currentWidget);
  108. _currentDesiredIndex = index;
  109. _updates.fire({ _currentWidget, index, index, _currentState });
  110. updateOrder(index, position);
  111. }
  112. void VerticalLayoutReorder::updateOrder(int index, QPoint position) {
  113. if (isIndexPinned(index)) {
  114. return;
  115. }
  116. const auto shift = position.y() - _currentStart;
  117. auto &current = _entries[index];
  118. current.shiftAnimation.stop();
  119. current.shift = current.finalShift = shift;
  120. _layout->setVerticalShift(index, shift);
  121. checkForScrollAnimation();
  122. const auto count = _entries.size();
  123. const auto currentHeight = current.widget->height();
  124. const auto currentMiddle = current.widget->y() + currentHeight / 2;
  125. _currentDesiredIndex = index;
  126. if (shift > 0) {
  127. auto top = current.widget->y() - shift;
  128. for (auto next = index + 1; next != count; ++next) {
  129. if (isIndexPinned(next)) {
  130. return;
  131. }
  132. const auto &entry = _entries[next];
  133. top += entry.widget->height();
  134. if (currentMiddle < top) {
  135. moveToShift(next, 0);
  136. } else {
  137. _currentDesiredIndex = next;
  138. moveToShift(next, -currentHeight);
  139. }
  140. }
  141. for (auto prev = index - 1; prev >= 0; --prev) {
  142. moveToShift(prev, 0);
  143. }
  144. } else {
  145. for (auto next = index + 1; next != count; ++next) {
  146. moveToShift(next, 0);
  147. }
  148. for (auto prev = index - 1; prev >= 0; --prev) {
  149. if (isIndexPinned(prev)) {
  150. return;
  151. }
  152. const auto &entry = _entries[prev];
  153. if (currentMiddle >= entry.widget->y() - entry.shift + currentHeight) {
  154. moveToShift(prev, 0);
  155. } else {
  156. _currentDesiredIndex = prev;
  157. moveToShift(prev, currentHeight);
  158. }
  159. }
  160. }
  161. }
  162. void VerticalLayoutReorder::mousePress(
  163. not_null<RpWidget*> widget,
  164. Qt::MouseButton button,
  165. QPoint position) {
  166. if (button != Qt::LeftButton) {
  167. return;
  168. }
  169. cancelCurrent();
  170. _currentWidget = widget;
  171. _currentStart = position.y();
  172. }
  173. void VerticalLayoutReorder::mouseRelease(Qt::MouseButton button) {
  174. if (button != Qt::LeftButton) {
  175. return;
  176. }
  177. finishReordering();
  178. }
  179. void VerticalLayoutReorder::cancelCurrent() {
  180. if (_currentWidget) {
  181. cancelCurrent(indexOf(_currentWidget));
  182. }
  183. }
  184. void VerticalLayoutReorder::cancelCurrent(int index) {
  185. Expects(_currentWidget != nullptr);
  186. if (_currentState == State::Started) {
  187. _currentState = State::Cancelled;
  188. _updates.fire({ _currentWidget, index, index, _currentState });
  189. }
  190. _currentWidget = nullptr;
  191. for (auto i = 0, count = int(_entries.size()); i != count; ++i) {
  192. moveToShift(i, 0);
  193. }
  194. }
  195. void VerticalLayoutReorder::finishReordering() {
  196. if (_scroll) {
  197. _scrollAnimation.stop();
  198. }
  199. finishCurrent();
  200. }
  201. void VerticalLayoutReorder::finishCurrent() {
  202. if (!_currentWidget) {
  203. return;
  204. }
  205. const auto index = indexOf(_currentWidget);
  206. if (_currentDesiredIndex == index || _currentState != State::Started) {
  207. cancelCurrent(index);
  208. return;
  209. }
  210. const auto result = _currentDesiredIndex;
  211. const auto widget = _currentWidget;
  212. _currentState = State::Cancelled;
  213. _currentWidget = nullptr;
  214. auto &current = _entries[index];
  215. const auto height = current.widget->height();
  216. if (index < result) {
  217. auto sum = 0;
  218. for (auto i = index; i != result; ++i) {
  219. auto &entry = _entries[i + 1];
  220. const auto widget = entry.widget;
  221. entry.deltaShift += height;
  222. updateShift(widget, i + 1);
  223. sum += widget->height();
  224. }
  225. current.finalShift -= sum;
  226. } else if (index > result) {
  227. auto sum = 0;
  228. for (auto i = result; i != index; ++i) {
  229. auto &entry = _entries[i];
  230. const auto widget = entry.widget;
  231. entry.deltaShift -= height;
  232. updateShift(widget, i);
  233. sum += widget->height();
  234. }
  235. current.finalShift += sum;
  236. }
  237. if (!(current.finalShift + current.deltaShift)) {
  238. current.shift = 0;
  239. _layout->setVerticalShift(index, 0);
  240. }
  241. base::reorder(_entries, index, result);
  242. _layout->reorderRows(index, _currentDesiredIndex);
  243. for (auto i = 0, count = int(_entries.size()); i != count; ++i) {
  244. moveToShift(i, 0);
  245. }
  246. _updates.fire({ widget, index, result, State::Applied });
  247. }
  248. void VerticalLayoutReorder::moveToShift(int index, int shift) {
  249. auto &entry = _entries[index];
  250. if (entry.finalShift + entry.deltaShift == shift) {
  251. return;
  252. }
  253. const auto widget = entry.widget;
  254. entry.shiftAnimation.start(
  255. [=] { updateShift(widget, index); },
  256. entry.finalShift,
  257. shift - entry.deltaShift,
  258. st::slideWrapDuration);
  259. entry.finalShift = shift - entry.deltaShift;
  260. }
  261. void VerticalLayoutReorder::updateShift(
  262. not_null<RpWidget*> widget,
  263. int indexHint) {
  264. Expects(indexHint >= 0 && indexHint < _entries.size());
  265. const auto index = (_entries[indexHint].widget == widget)
  266. ? indexHint
  267. : indexOf(widget);
  268. auto &entry = _entries[index];
  269. entry.shift = base::SafeRound(
  270. entry.shiftAnimation.value(entry.finalShift)
  271. ) + entry.deltaShift;
  272. if (entry.deltaShift && !entry.shiftAnimation.animating()) {
  273. entry.finalShift += entry.deltaShift;
  274. entry.deltaShift = 0;
  275. }
  276. _layout->setVerticalShift(index, entry.shift);
  277. }
  278. int VerticalLayoutReorder::indexOf(not_null<RpWidget*> widget) const {
  279. const auto i = ranges::find(_entries, widget, &Entry::widget);
  280. Assert(i != end(_entries));
  281. return i - begin(_entries);
  282. }
  283. auto VerticalLayoutReorder::updates() const -> rpl::producer<Single> {
  284. return _updates.events();
  285. }
  286. void VerticalLayoutReorder::updateScrollCallback() {
  287. if (!_scroll) {
  288. return;
  289. }
  290. const auto delta = deltaFromEdge();
  291. const auto oldTop = _scroll->scrollTop();
  292. _scroll->scrollToY(oldTop + delta);
  293. const auto newTop = _scroll->scrollTop();
  294. _currentStart += oldTop - newTop;
  295. if (newTop == 0 || newTop == _scroll->scrollTopMax()) {
  296. _scrollAnimation.stop();
  297. }
  298. }
  299. void VerticalLayoutReorder::checkForScrollAnimation() {
  300. if (!_scroll || !deltaFromEdge() || _scrollAnimation.animating()) {
  301. return;
  302. }
  303. _scrollAnimation.start();
  304. }
  305. int VerticalLayoutReorder::deltaFromEdge() {
  306. Expects(_currentWidget != nullptr);
  307. Expects(_scroll);
  308. const auto globalPosition = _currentWidget->mapToGlobal(QPoint(0, 0));
  309. const auto localTop = _scroll->mapFromGlobal(globalPosition).y();
  310. const auto localBottom = localTop
  311. + _currentWidget->height()
  312. - _scroll->height();
  313. const auto isTopEdge = (localTop < 0);
  314. const auto isBottomEdge = (localBottom > 0);
  315. if (!isTopEdge && !isBottomEdge) {
  316. _scrollAnimation.stop();
  317. return 0;
  318. }
  319. return int((isBottomEdge ? localBottom : localTop) * kScrollFactor);
  320. }
  321. } // namespace Ui