| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364 |
- // This file is part of Desktop App Toolkit,
- // a set of libraries for developing nice desktop applications.
- //
- // For license and copyright information please follow this link:
- // https://github.com/desktop-app/legal/blob/master/LEGAL
- //
- #include "ui/wrap/vertical_layout_reorder.h"
- #include "ui/wrap/vertical_layout.h"
- #include "styles/style_basic.h"
- #include <QtGui/QtEvents>
- #include <QtWidgets/QApplication>
- namespace Ui {
- namespace {
- constexpr auto kScrollFactor = 0.05;
- } // namespace
- VerticalLayoutReorder::VerticalLayoutReorder(
- not_null<VerticalLayout*> layout,
- not_null<ScrollArea*> scroll)
- : _layout(layout)
- , _scroll(scroll)
- , _scrollAnimation([=] { updateScrollCallback(); }) {
- }
- VerticalLayoutReorder::VerticalLayoutReorder(not_null<VerticalLayout*> layout)
- : _layout(layout) {
- }
- void VerticalLayoutReorder::cancel() {
- if (_currentWidget) {
- cancelCurrent(indexOf(_currentWidget));
- }
- _lifetime.destroy();
- for (auto i = 0, count = _layout->count(); i != count; ++i) {
- _layout->setVerticalShift(i, 0);
- }
- _entries.clear();
- }
- void VerticalLayoutReorder::start() {
- const auto count = _layout->count();
- if (count < 2) {
- return;
- }
- for (auto i = 0; i != count; ++i) {
- const auto widget = _layout->widgetAt(i);
- const auto eventsProducer = _proxyWidgetCallback
- ? _proxyWidgetCallback(i)
- : widget;
- eventsProducer->events(
- ) | rpl::start_with_next_done([=](not_null<QEvent*> e) {
- switch (e->type()) {
- case QEvent::MouseMove:
- mouseMove(
- widget,
- static_cast<QMouseEvent*>(e.get())->globalPos());
- break;
- case QEvent::MouseButtonPress:
- mousePress(
- widget,
- static_cast<QMouseEvent*>(e.get())->button(),
- static_cast<QMouseEvent*>(e.get())->globalPos());
- break;
- case QEvent::MouseButtonRelease:
- mouseRelease(static_cast<QMouseEvent*>(e.get())->button());
- break;
- }
- }, [=] {
- cancel();
- }, _lifetime);
- _entries.push_back({ widget });
- }
- }
- void VerticalLayoutReorder::addPinnedInterval(int from, int length) {
- _pinnedIntervals.push_back({ from, length });
- }
- void VerticalLayoutReorder::clearPinnedIntervals() {
- _pinnedIntervals.clear();
- }
- void VerticalLayoutReorder::setMouseEventProxy(ProxyCallback callback) {
- _proxyWidgetCallback = std::move(callback);
- }
- bool VerticalLayoutReorder::Interval::isIn(int index) const {
- return (index >= from) && (index < (from + length));
- }
- bool VerticalLayoutReorder::isIndexPinned(int index) const {
- return ranges::any_of(_pinnedIntervals, [&](const Interval &i) {
- return i.isIn(index);
- });
- }
- void VerticalLayoutReorder::mouseMove(
- not_null<RpWidget*> widget,
- QPoint position) {
- if (_currentWidget != widget) {
- return;
- } else if (_currentState != State::Started) {
- checkForStart(position);
- } else {
- updateOrder(indexOf(_currentWidget), position);
- }
- }
- void VerticalLayoutReorder::checkForStart(QPoint position) {
- const auto shift = position.y() - _currentStart;
- const auto delta = QApplication::startDragDistance();
- if (std::abs(shift) <= delta) {
- return;
- }
- _currentWidget->raise();
- _currentState = State::Started;
- _currentStart += (shift > 0) ? delta : -delta;
- const auto index = indexOf(_currentWidget);
- _currentDesiredIndex = index;
- _updates.fire({ _currentWidget, index, index, _currentState });
- updateOrder(index, position);
- }
- void VerticalLayoutReorder::updateOrder(int index, QPoint position) {
- if (isIndexPinned(index)) {
- return;
- }
- const auto shift = position.y() - _currentStart;
- auto ¤t = _entries[index];
- current.shiftAnimation.stop();
- current.shift = current.finalShift = shift;
- _layout->setVerticalShift(index, shift);
- checkForScrollAnimation();
- const auto count = _entries.size();
- const auto currentHeight = current.widget->height();
- const auto currentMiddle = current.widget->y() + currentHeight / 2;
- _currentDesiredIndex = index;
- if (shift > 0) {
- auto top = current.widget->y() - shift;
- for (auto next = index + 1; next != count; ++next) {
- if (isIndexPinned(next)) {
- return;
- }
- const auto &entry = _entries[next];
- top += entry.widget->height();
- if (currentMiddle < top) {
- moveToShift(next, 0);
- } else {
- _currentDesiredIndex = next;
- moveToShift(next, -currentHeight);
- }
- }
- for (auto prev = index - 1; prev >= 0; --prev) {
- moveToShift(prev, 0);
- }
- } else {
- for (auto next = index + 1; next != count; ++next) {
- moveToShift(next, 0);
- }
- for (auto prev = index - 1; prev >= 0; --prev) {
- if (isIndexPinned(prev)) {
- return;
- }
- const auto &entry = _entries[prev];
- if (currentMiddle >= entry.widget->y() - entry.shift + currentHeight) {
- moveToShift(prev, 0);
- } else {
- _currentDesiredIndex = prev;
- moveToShift(prev, currentHeight);
- }
- }
- }
- }
- void VerticalLayoutReorder::mousePress(
- not_null<RpWidget*> widget,
- Qt::MouseButton button,
- QPoint position) {
- if (button != Qt::LeftButton) {
- return;
- }
- cancelCurrent();
- _currentWidget = widget;
- _currentStart = position.y();
- }
- void VerticalLayoutReorder::mouseRelease(Qt::MouseButton button) {
- if (button != Qt::LeftButton) {
- return;
- }
- finishReordering();
- }
- void VerticalLayoutReorder::cancelCurrent() {
- if (_currentWidget) {
- cancelCurrent(indexOf(_currentWidget));
- }
- }
- void VerticalLayoutReorder::cancelCurrent(int index) {
- Expects(_currentWidget != nullptr);
- if (_currentState == State::Started) {
- _currentState = State::Cancelled;
- _updates.fire({ _currentWidget, index, index, _currentState });
- }
- _currentWidget = nullptr;
- for (auto i = 0, count = int(_entries.size()); i != count; ++i) {
- moveToShift(i, 0);
- }
- }
- void VerticalLayoutReorder::finishReordering() {
- if (_scroll) {
- _scrollAnimation.stop();
- }
- finishCurrent();
- }
- void VerticalLayoutReorder::finishCurrent() {
- if (!_currentWidget) {
- return;
- }
- const auto index = indexOf(_currentWidget);
- if (_currentDesiredIndex == index || _currentState != State::Started) {
- cancelCurrent(index);
- return;
- }
- const auto result = _currentDesiredIndex;
- const auto widget = _currentWidget;
- _currentState = State::Cancelled;
- _currentWidget = nullptr;
- auto ¤t = _entries[index];
- const auto height = current.widget->height();
- if (index < result) {
- auto sum = 0;
- for (auto i = index; i != result; ++i) {
- auto &entry = _entries[i + 1];
- const auto widget = entry.widget;
- entry.deltaShift += height;
- updateShift(widget, i + 1);
- sum += widget->height();
- }
- current.finalShift -= sum;
- } else if (index > result) {
- auto sum = 0;
- for (auto i = result; i != index; ++i) {
- auto &entry = _entries[i];
- const auto widget = entry.widget;
- entry.deltaShift -= height;
- updateShift(widget, i);
- sum += widget->height();
- }
- current.finalShift += sum;
- }
- if (!(current.finalShift + current.deltaShift)) {
- current.shift = 0;
- _layout->setVerticalShift(index, 0);
- }
- base::reorder(_entries, index, result);
- _layout->reorderRows(index, _currentDesiredIndex);
- for (auto i = 0, count = int(_entries.size()); i != count; ++i) {
- moveToShift(i, 0);
- }
- _updates.fire({ widget, index, result, State::Applied });
- }
- void VerticalLayoutReorder::moveToShift(int index, int shift) {
- auto &entry = _entries[index];
- if (entry.finalShift + entry.deltaShift == shift) {
- return;
- }
- const auto widget = entry.widget;
- entry.shiftAnimation.start(
- [=] { updateShift(widget, index); },
- entry.finalShift,
- shift - entry.deltaShift,
- st::slideWrapDuration);
- entry.finalShift = shift - entry.deltaShift;
- }
- void VerticalLayoutReorder::updateShift(
- not_null<RpWidget*> widget,
- int indexHint) {
- Expects(indexHint >= 0 && indexHint < _entries.size());
- const auto index = (_entries[indexHint].widget == widget)
- ? indexHint
- : indexOf(widget);
- auto &entry = _entries[index];
- entry.shift = base::SafeRound(
- entry.shiftAnimation.value(entry.finalShift)
- ) + entry.deltaShift;
- if (entry.deltaShift && !entry.shiftAnimation.animating()) {
- entry.finalShift += entry.deltaShift;
- entry.deltaShift = 0;
- }
- _layout->setVerticalShift(index, entry.shift);
- }
- int VerticalLayoutReorder::indexOf(not_null<RpWidget*> widget) const {
- const auto i = ranges::find(_entries, widget, &Entry::widget);
- Assert(i != end(_entries));
- return i - begin(_entries);
- }
- auto VerticalLayoutReorder::updates() const -> rpl::producer<Single> {
- return _updates.events();
- }
- void VerticalLayoutReorder::updateScrollCallback() {
- if (!_scroll) {
- return;
- }
- const auto delta = deltaFromEdge();
- const auto oldTop = _scroll->scrollTop();
- _scroll->scrollToY(oldTop + delta);
- const auto newTop = _scroll->scrollTop();
- _currentStart += oldTop - newTop;
- if (newTop == 0 || newTop == _scroll->scrollTopMax()) {
- _scrollAnimation.stop();
- }
- }
- void VerticalLayoutReorder::checkForScrollAnimation() {
- if (!_scroll || !deltaFromEdge() || _scrollAnimation.animating()) {
- return;
- }
- _scrollAnimation.start();
- }
- int VerticalLayoutReorder::deltaFromEdge() {
- Expects(_currentWidget != nullptr);
- Expects(_scroll);
- const auto globalPosition = _currentWidget->mapToGlobal(QPoint(0, 0));
- const auto localTop = _scroll->mapFromGlobal(globalPosition).y();
- const auto localBottom = localTop
- + _currentWidget->height()
- - _scroll->height();
- const auto isTopEdge = (localTop < 0);
- const auto isBottomEdge = (localBottom > 0);
- if (!isTopEdge && !isBottomEdge) {
- _scrollAnimation.stop();
- return 0;
- }
- return int((isBottomEdge ? localBottom : localTop) * kScrollFactor);
- }
- } // namespace Ui
|