init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,400 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "ui/controls/subsection_tabs_slider_reorder.h"
|
||||
|
||||
#include "ui/controls/subsection_tabs_slider.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/controls/subsection_tabs_slider.h"
|
||||
#include "styles/style_basic.h"
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
namespace Ui {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kScrollFactor = 0.05;
|
||||
|
||||
} // namespace
|
||||
|
||||
SubsectionSliderReorder::SubsectionSliderReorder(
|
||||
not_null<SubsectionSlider*> slider,
|
||||
not_null<ScrollArea*> scroll)
|
||||
: _slider(slider)
|
||||
, _scroll(scroll)
|
||||
, _scrollAnimation([=] { updateScrollCallback(); }) {
|
||||
}
|
||||
|
||||
SubsectionSliderReorder::SubsectionSliderReorder(
|
||||
not_null<SubsectionSlider*> slider)
|
||||
: _slider(slider) {
|
||||
}
|
||||
|
||||
SubsectionSliderReorder::~SubsectionSliderReorder() {
|
||||
cancel();
|
||||
}
|
||||
|
||||
void SubsectionSliderReorder::cancel() {
|
||||
if (_currentButton) {
|
||||
cancelCurrent(indexOf(_currentButton));
|
||||
}
|
||||
_lifetime.destroy();
|
||||
_entries.clear();
|
||||
}
|
||||
|
||||
void SubsectionSliderReorder::start() {
|
||||
const auto count = _slider->sectionsCount();
|
||||
if (count < 2) {
|
||||
return;
|
||||
}
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
const auto button = _slider->buttonAt(i);
|
||||
const auto eventsProducer = _proxyButtonCallback
|
||||
? _proxyButtonCallback(i)
|
||||
: button;
|
||||
eventsProducer->events(
|
||||
) | rpl::on_next_done([=](not_null<QEvent*> e) {
|
||||
switch (e->type()) {
|
||||
case QEvent::MouseMove:
|
||||
mouseMove(
|
||||
button,
|
||||
static_cast<QMouseEvent*>(e.get())->globalPos());
|
||||
break;
|
||||
case QEvent::MouseButtonPress: {
|
||||
const auto event = static_cast<QMouseEvent*>(e.get());
|
||||
mousePress(button, event->button(), event->globalPos());
|
||||
break;
|
||||
}
|
||||
case QEvent::MouseButtonRelease:
|
||||
mouseRelease(static_cast<QMouseEvent*>(e.get())->button());
|
||||
break;
|
||||
}
|
||||
}, [=] {
|
||||
cancel();
|
||||
}, _lifetime);
|
||||
_entries.push_back({ button });
|
||||
}
|
||||
}
|
||||
|
||||
void SubsectionSliderReorder::addPinnedInterval(int from, int length) {
|
||||
_pinnedIntervals.push_back({ from, length });
|
||||
}
|
||||
|
||||
void SubsectionSliderReorder::clearPinnedIntervals() {
|
||||
_pinnedIntervals.clear();
|
||||
}
|
||||
|
||||
void SubsectionSliderReorder::setMouseEventProxy(ProxyCallback callback) {
|
||||
_proxyButtonCallback = std::move(callback);
|
||||
}
|
||||
|
||||
bool SubsectionSliderReorder::Interval::isIn(int index) const {
|
||||
return (index >= from) && (index < (from + length));
|
||||
}
|
||||
|
||||
bool SubsectionSliderReorder::isIndexPinned(int index) const {
|
||||
return ranges::any_of(_pinnedIntervals, [&](const Interval &i) {
|
||||
return i.isIn(index);
|
||||
});
|
||||
}
|
||||
|
||||
void SubsectionSliderReorder::mouseMove(
|
||||
not_null<SubsectionButton*> button,
|
||||
QPoint position) {
|
||||
if (_currentButton != button) {
|
||||
return;
|
||||
} else if (_currentState != State::Started) {
|
||||
checkForStart(position);
|
||||
} else {
|
||||
updateOrder(indexOf(_currentButton), position);
|
||||
|
||||
checkForScrollAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
void SubsectionSliderReorder::checkForStart(QPoint position) {
|
||||
const auto shift = _slider->isVertical()
|
||||
? (position.y() - _currentStart)
|
||||
: (position.x() - _currentStart);
|
||||
const auto delta = QApplication::startDragDistance();
|
||||
if (std::abs(shift) <= delta) {
|
||||
return;
|
||||
}
|
||||
_currentButton->raise();
|
||||
_currentState = State::Started;
|
||||
_currentStart += (shift > 0) ? delta : -delta;
|
||||
|
||||
const auto index = indexOf(_currentButton);
|
||||
_currentDesiredIndex = index;
|
||||
_updates.fire({ _currentButton, index, index, _currentState });
|
||||
|
||||
updateOrder(index, position);
|
||||
}
|
||||
|
||||
void SubsectionSliderReorder::updateOrder(int index, QPoint position) {
|
||||
if (isIndexPinned(index)) {
|
||||
return;
|
||||
}
|
||||
const auto shift = _slider->isVertical()
|
||||
? (position.y() - _currentStart)
|
||||
: (position.x() - _currentStart);
|
||||
auto ¤t = _entries[index];
|
||||
current.shiftAnimation.stop();
|
||||
current.shift = current.finalShift = shift;
|
||||
_slider->setButtonShift(index, shift);
|
||||
|
||||
const auto count = _entries.size();
|
||||
const auto currentSize = _slider->isVertical()
|
||||
? _currentButton->height()
|
||||
: _currentButton->width();
|
||||
const auto currentMiddle = _slider->isVertical()
|
||||
? (_currentButton->y() + currentSize / 2)
|
||||
: (_currentButton->x() + currentSize / 2);
|
||||
_currentDesiredIndex = index;
|
||||
if (shift > 0) {
|
||||
auto top = _slider->isVertical()
|
||||
? (_currentButton->y() - shift)
|
||||
: (_currentButton->x() - shift);
|
||||
for (auto next = index + 1; next != count; ++next) {
|
||||
if (isIndexPinned(next)) {
|
||||
return;
|
||||
}
|
||||
const auto &entry = _entries[next];
|
||||
top += _slider->isVertical()
|
||||
? entry.button->height()
|
||||
: entry.button->width();
|
||||
if (currentMiddle < top) {
|
||||
moveToShift(next, 0);
|
||||
} else {
|
||||
_currentDesiredIndex = next;
|
||||
moveToShift(next, -currentSize);
|
||||
}
|
||||
}
|
||||
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];
|
||||
const auto entryPos = _slider->isVertical()
|
||||
? (entry.button->y() - entry.shift)
|
||||
: (entry.button->x() - entry.shift);
|
||||
if (currentMiddle >= entryPos + currentSize) {
|
||||
moveToShift(prev, 0);
|
||||
} else {
|
||||
_currentDesiredIndex = prev;
|
||||
moveToShift(prev, currentSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SubsectionSliderReorder::mousePress(
|
||||
not_null<SubsectionButton*> button,
|
||||
Qt::MouseButton btn,
|
||||
QPoint position) {
|
||||
if (btn != Qt::LeftButton) {
|
||||
return;
|
||||
}
|
||||
cancelCurrent();
|
||||
_currentButton = button;
|
||||
_currentStart = _slider->isVertical() ? position.y() : position.x();
|
||||
}
|
||||
|
||||
void SubsectionSliderReorder::mouseRelease(Qt::MouseButton button) {
|
||||
if (button != Qt::LeftButton) {
|
||||
return;
|
||||
}
|
||||
finishReordering();
|
||||
}
|
||||
|
||||
void SubsectionSliderReorder::cancelCurrent() {
|
||||
if (_currentButton) {
|
||||
cancelCurrent(indexOf(_currentButton));
|
||||
}
|
||||
}
|
||||
|
||||
void SubsectionSliderReorder::cancelCurrent(int index) {
|
||||
Expects(_currentButton != nullptr);
|
||||
|
||||
if (_currentState == State::Started) {
|
||||
_currentState = State::Cancelled;
|
||||
_updates.fire({ _currentButton, index, index, _currentState });
|
||||
}
|
||||
_currentButton = nullptr;
|
||||
for (auto i = 0, count = int(_entries.size()); i != count; ++i) {
|
||||
moveToShift(i, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void SubsectionSliderReorder::finishReordering() {
|
||||
if (_scroll) {
|
||||
_scrollAnimation.stop();
|
||||
}
|
||||
finishCurrent();
|
||||
}
|
||||
|
||||
void SubsectionSliderReorder::finishCurrent() {
|
||||
if (!_currentButton) {
|
||||
return;
|
||||
}
|
||||
const auto index = indexOf(_currentButton);
|
||||
if (_currentDesiredIndex == index || _currentState != State::Started) {
|
||||
cancelCurrent(index);
|
||||
return;
|
||||
}
|
||||
const auto result = _currentDesiredIndex;
|
||||
const auto button = _currentButton;
|
||||
_currentState = State::Cancelled;
|
||||
_currentButton = nullptr;
|
||||
|
||||
auto ¤t = _entries[index];
|
||||
const auto size = _slider->isVertical()
|
||||
? button->height()
|
||||
: button->width();
|
||||
if (index < result) {
|
||||
auto sum = 0;
|
||||
for (auto i = index; i != result; ++i) {
|
||||
auto &entry = _entries[i + 1];
|
||||
const auto btn = entry.button;
|
||||
entry.deltaShift += size;
|
||||
updateShift(btn, i + 1);
|
||||
sum += _slider->isVertical() ? btn->height() : btn->width();
|
||||
}
|
||||
current.finalShift -= sum;
|
||||
} else if (index > result) {
|
||||
auto sum = 0;
|
||||
for (auto i = result; i != index; ++i) {
|
||||
auto &entry = _entries[i];
|
||||
const auto btn = entry.button;
|
||||
entry.deltaShift -= size;
|
||||
updateShift(btn, i);
|
||||
sum += _slider->isVertical() ? btn->height() : btn->width();
|
||||
}
|
||||
current.finalShift += sum;
|
||||
}
|
||||
if (!(current.finalShift + current.deltaShift)) {
|
||||
current.shift = 0;
|
||||
_slider->setButtonShift(index, 0);
|
||||
}
|
||||
base::reorder(_entries, index, result);
|
||||
_slider->reorderButtons(index, result);
|
||||
for (auto i = 0, count = int(_entries.size()); i != count; ++i) {
|
||||
moveToShift(i, 0);
|
||||
}
|
||||
|
||||
_updates.fire({ button, index, result, State::Applied });
|
||||
}
|
||||
|
||||
void SubsectionSliderReorder::moveToShift(int index, int shift) {
|
||||
auto &entry = _entries[index];
|
||||
if (entry.finalShift + entry.deltaShift == shift) {
|
||||
return;
|
||||
}
|
||||
const auto button = entry.button;
|
||||
entry.shiftAnimation.start(
|
||||
[=] { updateShift(button, index); },
|
||||
entry.finalShift,
|
||||
shift - entry.deltaShift,
|
||||
st::slideWrapDuration);
|
||||
entry.finalShift = shift - entry.deltaShift;
|
||||
}
|
||||
|
||||
void SubsectionSliderReorder::updateShift(
|
||||
not_null<SubsectionButton*> button,
|
||||
int indexHint) {
|
||||
Expects(indexHint >= 0 && indexHint < _entries.size());
|
||||
|
||||
const auto index = (_entries[indexHint].button == button)
|
||||
? indexHint
|
||||
: indexOf(button);
|
||||
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;
|
||||
}
|
||||
_slider->setButtonShift(index, entry.shift);
|
||||
}
|
||||
|
||||
auto SubsectionSliderReorder::updates() const
|
||||
-> rpl::producer<SubsectionSliderReorder::Single> {
|
||||
return _updates.events();
|
||||
}
|
||||
|
||||
int SubsectionSliderReorder::indexOf(
|
||||
not_null<SubsectionButton*> button) const {
|
||||
const auto i = ranges::find(_entries, button, &Entry::button);
|
||||
Assert(i != end(_entries));
|
||||
return i - begin(_entries);
|
||||
}
|
||||
|
||||
void SubsectionSliderReorder::updateScrollCallback() {
|
||||
if (!_scroll) {
|
||||
return;
|
||||
}
|
||||
const auto delta = deltaFromEdge();
|
||||
if (_slider->isVertical()) {
|
||||
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();
|
||||
}
|
||||
} else {
|
||||
const auto oldLeft = _scroll->scrollLeft();
|
||||
_scroll->scrollToX(oldLeft + delta);
|
||||
const auto newLeft = _scroll->scrollLeft();
|
||||
_currentStart += oldLeft - newLeft;
|
||||
if (newLeft == 0 || newLeft == _scroll->scrollLeftMax()) {
|
||||
_scrollAnimation.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SubsectionSliderReorder::checkForScrollAnimation() {
|
||||
if (!_scroll || !deltaFromEdge() || _scrollAnimation.animating()) {
|
||||
return;
|
||||
}
|
||||
_scrollAnimation.start();
|
||||
}
|
||||
|
||||
int SubsectionSliderReorder::deltaFromEdge() {
|
||||
Expects(_currentButton != nullptr);
|
||||
Expects(_scroll);
|
||||
|
||||
const auto globalPosition = _currentButton->mapToGlobal(QPoint(0, 0));
|
||||
const auto localPos = _scroll->mapFromGlobal(globalPosition);
|
||||
const auto localTop = _slider->isVertical() ? localPos.y() : localPos.x();
|
||||
const auto buttonSize = _slider->isVertical()
|
||||
? _currentButton->height()
|
||||
: _currentButton->width();
|
||||
const auto scrollSize = _slider->isVertical()
|
||||
? _scroll->height()
|
||||
: _scroll->width();
|
||||
const auto localBottom = localTop + buttonSize - scrollSize;
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user