Implement both touch and wheel swipe-to-reply.

This commit is contained in:
John Preston 2024-09-03 17:39:19 +04:00
parent 2aa5849997
commit 300f35e78f

View file

@ -7,11 +7,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "history/history_view_swipe.h" #include "history/history_view_swipe.h"
#include "base/event_filter.h"
#include "base/platform/base_platform_haptic.h" #include "base/platform/base_platform_haptic.h"
#include "base/platform/base_platform_info.h"
#include "base/qt/qt_common_adapters.h"
#include "base/event_filter.h"
#include "history/history_view_swipe_data.h" #include "history/history_view_swipe_data.h"
#include "ui/chat/chat_style.h" #include "ui/chat/chat_style.h"
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
#include "ui/widgets/elastic_scroll.h"
#include "ui/widgets/scroll_area.h" #include "ui/widgets/scroll_area.h"
#include <QtWidgets/QApplication> #include <QtWidgets/QApplication>
@ -32,9 +35,11 @@ void SetupSwipeHandler(
SwipeHandlerFinishData finishByTopData; SwipeHandlerFinishData finishByTopData;
std::optional<Qt::Orientation> orientation; std::optional<Qt::Orientation> orientation;
QPointF startAt; QPointF startAt;
QPointF lastAt; QPointF delta;
int cursorTop = 0; int cursorTop = 0;
bool started = false;
bool reached = false; bool reached = false;
bool touch = false;
rpl::lifetime lifetime; rpl::lifetime lifetime;
}; };
@ -48,27 +53,23 @@ void SetupSwipeHandler(
.cursorTop = state->cursorTop, .cursorTop = state->cursorTop,
}); });
}; };
const auto setOrientation = [=](const std::optional<Qt::Orientation> &o) { const auto setOrientation = [=](std::optional<Qt::Orientation> o) {
state->orientation = o; state->orientation = o;
const auto isHorizontal = o.value_or(Qt::Vertical) == Qt::Horizontal; const auto isHorizontal = (o == Qt::Horizontal);
scroll->viewport()->setAttribute( scroll->viewport()->setAttribute(
Qt::WA_AcceptTouchEvents, Qt::WA_AcceptTouchEvents,
!isHorizontal); !isHorizontal);
scroll->disableScroll(isHorizontal); scroll->disableScroll(isHorizontal);
}; };
const auto processEnd = [=](QTouchEvent *t) { const auto processEnd = [=](std::optional<QPointF> delta = {}) {
if (state->orientation) { if (state->orientation == Qt::Horizontal) {
if ((*state->orientation) == Qt::Horizontal) { const auto ratio = delta.value_or(state->delta).x() / threshold;
if (t && t->touchPoints().size() > 0) {
state->lastAt = t->touchPoints().at(0).pos();
}
const auto delta = state->startAt - state->lastAt;
const auto ratio = delta.x() / threshold;
if ((ratio >= 1) && state->finishByTopData.callback) { if ((ratio >= 1) && state->finishByTopData.callback) {
Ui::PostponeCall( Ui::PostponeCall(
widget, widget,
state->finishByTopData.callback); state->finishByTopData.callback);
} }
state->animationReach.stop();
state->animationEnd.stop(); state->animationEnd.stop();
state->animationEnd.start( state->animationEnd.start(
updateRatio, updateRatio,
@ -76,45 +77,27 @@ void SetupSwipeHandler(
0., 0.,
st::slideWrapDuration); st::slideWrapDuration);
} }
}
setOrientation(std::nullopt); setOrientation(std::nullopt);
state->startAt = QPointF(); state->started = false;
state->reached = false; state->reached = false;
}; };
scroll->scrolls() | rpl::start_with_next([=] { scroll->scrolls() | rpl::start_with_next([=] {
processEnd(nullptr); processEnd();
}, state->lifetime); }, state->lifetime);
const auto animationReachCallback = [=] { const auto animationReachCallback = [=] {
updateRatio((state->startAt - state->lastAt).x() / threshold); updateRatio(state->delta.x() / threshold);
}; };
const auto filter = [=](not_null<QEvent*> e) { struct UpdateArgs {
if (e->type() == QEvent::Leave && state->orientation) { QPointF position;
processEnd(nullptr); QPointF delta;
} bool touch = false;
if (e->type() == QEvent::MouseMove && state->orientation) { };
const auto m = static_cast<QMouseEvent*>(e.get()); const auto updateWith = [=](UpdateArgs &&args) {
if (std::abs(m->pos().y() - state->cursorTop) if (!state->started || state->touch != args.touch) {
> QApplication::startDragDistance()) { state->started = true;
processEnd(nullptr); state->touch = args.touch;
} state->startAt = args.position;
} state->delta = QPointF();
if (e->type() == QEvent::TouchBegin
|| e->type() == QEvent::TouchUpdate
|| e->type() == QEvent::TouchEnd
|| e->type() == QEvent::TouchCancel) {
const auto t = static_cast<QTouchEvent*>(e.get());
const auto &touches = t->touchPoints();
const auto anyReleased = (touches.size() == 2)
? ((touches.at(0).state() & Qt::TouchPointReleased)
+ (touches.at(1).state() & Qt::TouchPointReleased))
: (touches.size() == 1)
? (touches.at(0).state() & Qt::TouchPointReleased)
: 0;
if (touches.size() == 2) {
if ((e->type() == QEvent::TouchBegin)
|| (e->type() == QEvent::TouchUpdate)) {
if (state->startAt.isNull()) {
state->startAt = touches.at(0).pos();
state->cursorTop = widget->mapFromGlobal( state->cursorTop = widget->mapFromGlobal(
QCursor::pos()).y(); QCursor::pos()).y();
state->finishByTopData = generateFinishByTop( state->finishByTopData = generateFinishByTop(
@ -122,11 +105,20 @@ void SetupSwipeHandler(
if (!state->finishByTopData.callback) { if (!state->finishByTopData.callback) {
setOrientation(Qt::Vertical); setOrientation(Qt::Vertical);
} }
} else if (state->orientation) { } else if (!state->orientation) {
if ((*state->orientation) == Qt::Horizontal) { state->delta = args.delta;
state->lastAt = touches.at(0).pos(); const auto diffXtoY = std::abs(args.delta.x())
const auto delta = state->startAt - state->lastAt; - std::abs(args.delta.y());
const auto ratio = delta.x() / threshold; if (diffXtoY > 0) {
setOrientation(Qt::Horizontal);
} else if (diffXtoY < 0) {
setOrientation(Qt::Vertical);
} else {
setOrientation(std::nullopt);
}
} else if (*state->orientation == Qt::Horizontal) {
state->delta = args.delta;
const auto ratio = args.delta.x() / threshold;
updateRatio(ratio); updateRatio(ratio);
constexpr auto kResetReachedOn = 0.95; constexpr auto kResetReachedOn = 0.95;
constexpr auto kBounceDuration = crl::time(500); constexpr auto kBounceDuration = crl::time(500);
@ -144,28 +136,75 @@ void SetupSwipeHandler(
state->reached = false; state->reached = false;
} }
} }
};
const auto filter = [=](not_null<QEvent*> e) {
const auto type = e->type();
switch (type) {
case QEvent::Leave:
if (state->orientation) {
processEnd();
}
break;
case QEvent::MouseMove:
if (state->orientation) {
const auto m = static_cast<QMouseEvent*>(e.get());
if (std::abs(m->pos().y() - state->cursorTop)
> QApplication::startDragDistance()) {
processEnd();
}
}
break;
case QEvent::TouchBegin:
case QEvent::TouchUpdate:
case QEvent::TouchEnd:
case QEvent::TouchCancel: {
const auto t = static_cast<QTouchEvent*>(e.get());
const auto touchscreen = t->device()
&& (t->device()->type() != base::TouchDevice::TouchScreen);
if (!Platform::IsMac() && !touchscreen) {
break;
}
const auto &touches = t->touchPoints();
const auto released = [&](int index) {
return (touches.size() > index)
&& (touches.at(index).state() & Qt::TouchPointReleased);
};
const auto cancel = released(0)
|| released(1)
|| (touches.size() != (touchscreen ? 1 : 2))
|| (type == QEvent::TouchEnd)
|| (type == QEvent::TouchCancel);
if (cancel) {
processEnd(touches.empty()
? std::optional<QPointF>()
: (state->startAt - touches[0].pos()));
} else { } else {
state->lastAt = touches.at(0).pos(); updateWith({
const auto delta = state->startAt - state->lastAt; .position = touches[0].pos(),
const auto diffXtoY = std::abs(delta.x()) .delta = state->startAt - touches[0].pos(),
- std::abs(delta.y()); .touch = true,
if (diffXtoY > 0) { });
setOrientation(Qt::Horizontal); }
} else if (diffXtoY < 0) { } break;
setOrientation(Qt::Vertical); case QEvent::Wheel: {
const auto w = static_cast<QWheelEvent*>(e.get());
const auto phase = w->phase();
if (Platform::IsMac() || phase == Qt::NoScrollPhase) {
break;
}
const auto cancel = w->buttons()
|| (phase == Qt::ScrollEnd)
|| (phase == Qt::ScrollMomentum);
if (cancel) {
processEnd();
} else { } else {
setOrientation(std::nullopt); updateWith({
.position = QPointF(),
.delta = state->delta - Ui::ScrollDelta(w),
.touch = false,
});
} }
} } break;
}
}
if ((e->type() == QEvent::TouchEnd)
|| touches.empty()
|| anyReleased
|| (touches.size() > 2)) {
processEnd(t);
}
return base::EventFilterResult::Cancel;
} }
return base::EventFilterResult::Continue; return base::EventFilterResult::Continue;
}; };