Fix swipe gestures on touchscreens.

This commit is contained in:
John Preston 2025-03-14 14:36:01 +04:00
parent 044ef3447c
commit 34858b36c1
12 changed files with 140 additions and 51 deletions

View file

@ -523,8 +523,6 @@ TabbedSelector::TabbedSelector(
if (hasEmojiTab()) {
emoji()->refreshEmoji();
}
setupSwipe();
//setAttribute(Qt::WA_AcceptTouchEvents);
setAttribute(Qt::WA_OpaquePaintEvent, false);
showAll();
hide();
@ -532,8 +530,10 @@ TabbedSelector::TabbedSelector(
TabbedSelector::~TabbedSelector() = default;
void TabbedSelector::setupSwipe() {
Ui::Controls::SetupSwipeHandler(this, _scroll.data(), [=](
void TabbedSelector::reinstallSwipe(not_null<Ui::RpWidget*> widget) {
_swipeLifetime.destroy();
Ui::Controls::SetupSwipeHandler(widget, _scroll.data(), [=](
Ui::Controls::SwipeContextData data) {
if (data.translation != 0) {
if (!_swipeBackData.callback) {
@ -571,7 +571,7 @@ void TabbedSelector::setupSwipe() {
});
}
return Ui::Controls::SwipeHandlerFinishData();
}, nullptr);
}, nullptr, &_swipeLifetime);
}
const style::EmojiPan &TabbedSelector::st() const {
@ -1345,6 +1345,10 @@ void TabbedSelector::setWidgetToScrollArea() {
inner->moveToLeft(0, 0);
inner->show();
if (_tabs.size() > 1) {
reinstallSwipe(inner);
}
_scroll->disableScroll(false);
scrollToY(currentTab()->getScrollTop());
handleScroll();

View file

@ -288,7 +288,7 @@ private:
not_null<GifsListWidget*> gifs() const;
not_null<StickersListWidget*> masks() const;
void setupSwipe();
void reinstallSwipe(not_null<Ui::RpWidget*> widget);
const style::EmojiPan &_st;
const ComposeFeatures _features;
@ -334,6 +334,8 @@ private:
rpl::event_stream<> _showRequests;
rpl::event_stream<> _slideFinished;
rpl::lifetime _swipeLifetime;
};
class TabbedSelector::Inner : public Ui::RpWidget {

View file

@ -2972,7 +2972,7 @@ bool InnerWidget::processTouchEvent(not_null<QTouchEvent*> e) {
}
if (_chatPreviewTouchGlobal) {
const auto delta = (*_chatPreviewTouchGlobal - *point);
if (delta.manhattanLength() > _st->photoSize) {
if (delta.manhattanLength() >= QApplication::startDragDistance()) {
cancelChatPreview();
}
}
@ -2981,7 +2981,7 @@ bool InnerWidget::processTouchEvent(not_null<QTouchEvent*> e) {
return _dragging != nullptr;
} else if (_touchDragStartGlobal) {
const auto delta = (*_touchDragStartGlobal - *point);
if (delta.manhattanLength() > QApplication::startDragDistance()) {
if (delta.manhattanLength() >= QApplication::startDragDistance()) {
if (_touchDragPinnedTimer.isActive()) {
_touchDragPinnedTimer.cancel();
_touchDragStartGlobal = {};

View file

@ -685,7 +685,7 @@ void Widget::setupSwipeBack() {
}
return !current;
};
Ui::Controls::SetupSwipeHandler(_scroll.data(), _scroll.data(), [=](
Ui::Controls::SetupSwipeHandler(_inner, _scroll.data(), [=](
Ui::Controls::SwipeContextData data) {
if (data.translation != 0) {
if (!_swipeBackData.callback) {
@ -765,7 +765,7 @@ void Widget::setupSwipeBack() {
}
}
return Ui::Controls::SwipeHandlerFinishData();
}, nullptr);
});
}

View file

@ -420,7 +420,7 @@ void Widget::setupShortcuts() {
}
void Widget::setupSwipeReply() {
Ui::Controls::SetupSwipeHandler(this, _scroll.data(), [=](
Ui::Controls::SetupSwipeHandler(_inner.data(), _scroll.data(), [=](
Ui::Controls::SwipeContextData data) {
if (data.translation > 0) {
if (!_swipeBackData.callback) {
@ -446,7 +446,7 @@ void Widget::setupSwipeReply() {
});
}
return Ui::Controls::SwipeHandlerFinishData();
}, nullptr);
});
}
std::shared_ptr<Window::SectionMemento> Widget::createMemento() {

View file

@ -1525,6 +1525,11 @@ void HistoryInner::touchEvent(QTouchEvent *e) {
} break;
case QEvent::TouchUpdate: {
LOG(("UPDATE: %1,%2 -> %3,%4"
).arg(_touchStart.x()
).arg(_touchStart.y()
).arg(_touchPos.x()
).arg(_touchPos.y()));
if (!_touchInProgress) {
return;
} else if (_touchSelect) {

View file

@ -21,7 +21,7 @@ void SetupSwipeBackSection(
not_null<HistoryView::ListWidget*> list) {
const auto swipeBackData
= list->lifetime().make_state<Ui::Controls::SwipeBackResult>();
Ui::Controls::SetupSwipeHandler(parent, scroll, [=](
Ui::Controls::SetupSwipeHandler(list, scroll, [=](
Ui::Controls::SwipeContextData data) {
if (data.translation > 0) {
if (!swipeBackData->callback) {

View file

@ -77,8 +77,6 @@ ContentWidget::ContentWidget(
) | rpl::start_with_next([this] {
updateControlsGeometry();
}, lifetime());
setupSwipeReply();
}
void ContentWidget::resizeEvent(QResizeEvent *e) {
@ -157,6 +155,17 @@ Ui::RpWidget *ContentWidget::doSetInnerWidget(
object_ptr<RpWidget> inner) {
using namespace rpl::mappers;
const auto tmp = new Ui::RpWidget(this);
tmp->raise();
tmp->show();
tmp->setAttribute(Qt::WA_TransparentForMouseEvents);
tmp->paintRequest() | rpl::start_with_next([=] {
QPainter(tmp).fillRect(tmp->rect(), QColor(255, 0, 0, 64));
}, tmp->lifetime());
_scroll->geometryValue() | rpl::start_with_next([=] {
tmp->setGeometry(_scroll->geometry());
}, tmp->lifetime());
_innerWrap = _scroll->setOwnedWidget(
object_ptr<Ui::PaddingWrap<Ui::RpWidget>>(
this,
@ -164,6 +173,8 @@ Ui::RpWidget *ContentWidget::doSetInnerWidget(
_innerWrap ? _innerWrap->padding() : style::margins()));
_innerWrap->move(0, 0);
setupSwipeHandler(_innerWrap);
// MSVC BUG + REGRESSION rpl::mappers::tuple :(
rpl::combine(
_scroll->scrollTopValue(),
@ -179,6 +190,24 @@ Ui::RpWidget *ContentWidget::doSetInnerWidget(
_scrollTillBottomChanges.fire_copy(std::max(desired - bottom, 0));
}, _innerWrap->lifetime());
rpl::combine(
_scroll->heightValue(),
_innerWrap->entity()->heightValue(),
_controller->wrapValue()
) | rpl::start_with_next([=](
int scrollHeight,
int innerHeight,
Wrap wrap) {
const auto added = (wrap == Wrap::Layer)
? 0
: std::max(scrollHeight - innerHeight, 0);
if (_addedHeight != added) {
_addedHeight = added;
updateInnerPadding();
}
}, _innerWrap->lifetime());
updateInnerPadding();
return _innerWrap->entity();
}
@ -208,11 +237,19 @@ rpl::producer<int> ContentWidget::scrollHeightValue() const {
}
void ContentWidget::applyAdditionalScroll(int additionalScroll) {
if (_innerWrap) {
_innerWrap->setPadding({ 0, 0, 0, additionalScroll });
if (_additionalScroll != additionalScroll) {
_additionalScroll = additionalScroll;
if (_innerWrap) {
updateInnerPadding();
}
}
}
void ContentWidget::updateInnerPadding() {
const auto addedToBottom = std::max(_additionalScroll, _addedHeight);
_innerWrap->setPadding({ 0, 0, 0, addedToBottom });
}
void ContentWidget::applyMaxVisibleHeight(int maxVisibleHeight) {
if (_maxVisibleHeight != maxVisibleHeight) {
_maxVisibleHeight = maxVisibleHeight;
@ -384,8 +421,8 @@ not_null<Ui::ScrollArea*> ContentWidget::scroll() const {
return _scroll.data();
}
void ContentWidget::setupSwipeReply() {
Ui::Controls::SetupSwipeHandler(this, _scroll.data(), [=](
void ContentWidget::setupSwipeHandler(not_null<Ui::RpWidget*> widget) {
Ui::Controls::SetupSwipeHandler(widget, _scroll.data(), [=](
Ui::Controls::SwipeContextData data) {
if (data.translation > 0) {
if (!_swipeBackData.callback) {
@ -412,7 +449,7 @@ void ContentWidget::setupSwipeReply() {
}));
})
: Ui::Controls::SwipeHandlerFinishData();
}, nullptr);
});
}
Key ContentMemento::key() const {

View file

@ -168,7 +168,8 @@ private:
RpWidget *doSetInnerWidget(object_ptr<RpWidget> inner);
void updateControlsGeometry();
void refreshSearchField(bool shown);
void setupSwipeReply();
void setupSwipeHandler(not_null<Ui::RpWidget*> widget);
void updateInnerPadding();
virtual std::shared_ptr<ContentMemento> doCreateMemento() = 0;
@ -183,6 +184,8 @@ private:
base::unique_qptr<Ui::RpWidget> _searchWrap = nullptr;
QPointer<Ui::InputField> _searchField;
int _innerDesiredHeight = 0;
int _additionalScroll = 0;
int _addedHeight = 0;
int _maxVisibleHeight = 0;
bool _isStackBottom = false;

View file

@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "ui/controls/swipe_handler.h"
#include "base/debug_log.h"
#include "base/platform/base_platform_haptic.h"
#include "base/platform/base_platform_info.h"
#include "base/qt/qt_common_adapters.h"
@ -64,7 +66,8 @@ void SetupSwipeHandler(
Scroll scroll,
Fn<void(SwipeContextData)> update,
Fn<SwipeHandlerFinishData(int, Qt::LayoutDirection)> generateFinish,
rpl::producer<bool> dontStart) {
rpl::producer<bool> dontStart,
rpl::lifetime *onLifetime) {
static constexpr auto kThresholdWidth = 50;
static constexpr auto kMaxRatio = 1.5;
@ -95,12 +98,23 @@ void SetupSwipeHandler(
rpl::lifetime lifetime;
};
const auto state = widget->lifetime().make_state<State>();
std::move(
dontStart
) | rpl::start_with_next([=](bool dontStart) {
state->dontStart = dontStart;
}, state->lifetime);
auto &useLifetime = onLifetime ? *onLifetime : widget->lifetime();
const auto state = useLifetime.make_state<State>();
if (dontStart) {
std::move(
dontStart
) | rpl::start_with_next([=](bool dontStart) {
state->dontStart = dontStart;
}, state->lifetime);
} else {
v::match(scroll, [](v::null_t) {
}, [&](const auto &scroll) {
scroll->touchMaybePressing(
) | rpl::start_with_next([=](bool maybePressing) {
state->dontStart = maybePressing;
}, state->lifetime);
});
}
const auto updateRatio = [=](float64 ratio) {
ratio = std::max(ratio, 0.);
@ -123,9 +137,11 @@ void SetupSwipeHandler(
v::match(scroll, [](v::null_t) {
}, [&](const auto &scroll) {
if (const auto viewport = scroll->viewport()) {
viewport->setAttribute(
Qt::WA_AcceptTouchEvents,
!isHorizontal);
if (viewport != widget) {
viewport->setAttribute(
Qt::WA_AcceptTouchEvents,
!isHorizontal);
}
}
scroll->disableScroll(isHorizontal);
});
@ -173,37 +189,52 @@ void SetupSwipeHandler(
update(state->data);
};
const auto updateWith = [=](UpdateArgs args) {
if (!state->started
|| state->touch != args.touch
|| !state->direction) {
state->direction = (args.delta.x() == 0)
? std::nullopt
: args.delta.x() < 0
? std::make_optional(Qt::RightToLeft)
: std::make_optional(Qt::LeftToRight);
state->directionInt = (!state->direction
|| (*state->direction) == Qt::LeftToRight)
const auto fillFinishByTop = [&] {
if (!args.delta.x()) {
LOG(("SKIPPING fillFinishByTop."));
return;
}
LOG(("SETTING DIRECTION"));
state->direction = (args.delta.x() < 0)
? Qt::RightToLeft
: Qt::LeftToRight;
state->directionInt = (state->direction == Qt::LeftToRight)
? 1
: -1;
state->started = true;
state->touch = args.touch;
state->startAt = args.position;
state->delta = QPointF();
state->cursorTop = widget->mapFromGlobal(args.globalCursor).y();
state->finishByTopData = generateFinish(
state->cursorTop,
state->direction.value_or(Qt::RightToLeft));
*state->direction);
state->threshold = style::ConvertFloatScale(kThresholdWidth)
* state->finishByTopData.speedRatio;
if (!state->finishByTopData.callback) {
setOrientation(Qt::Vertical);
}
};
if (!state->started || state->touch != args.touch) {
LOG(("STARTING"));
state->started = true;
state->touch = args.touch;
state->startAt = args.position;
state->cursorTop = widget->mapFromGlobal(args.globalCursor).y();
if (!state->touch) {
// args.delta already is valid.
fillFinishByTop();
} else {
// args.delta depends on state->startAt, so it's invalid.
state->direction = std::nullopt;
}
state->delta = QPointF();
} else if (!state->direction) {
fillFinishByTop();
} else if (!state->orientation) {
state->delta = args.delta;
const auto diffXtoY = std::abs(args.delta.x())
- std::abs(args.delta.y());
constexpr auto kOrientationThreshold = 1.;
LOG(("SETTING ORIENTATION WITH: %1,%2, diff %3"
).arg(args.delta.x()
).arg(args.delta.y()
).arg(diffXtoY));
if (diffXtoY > kOrientationThreshold) {
if (!state->dontStart) {
setOrientation(Qt::Horizontal);
@ -239,6 +270,9 @@ void SetupSwipeHandler(
}
};
const auto filter = [=](not_null<QEvent*> e) {
if (!widget->testAttribute(Qt::WA_AcceptTouchEvents)) {
[[maybe_unused]] int a = 0;
}
const auto type = e->type();
switch (type) {
case QEvent::Leave: {
@ -262,7 +296,7 @@ void SetupSwipeHandler(
const auto t = static_cast<QTouchEvent*>(e.get());
const auto touchscreen = t->device()
&& (t->device()->type() == base::TouchDevice::TouchScreen);
if (!touchscreen) {
if (!touchscreen && type != QEvent::TouchCancel) {
break;
} else if (type == QEvent::TouchBegin) {
// Reset state in case we lost some TouchEnd.
@ -292,8 +326,10 @@ void SetupSwipeHandler(
.delta = state->startAt - touches[0].pos(),
.touch = true,
};
LOG(("ORIENTATION UPDATING WITH: %1, %2").arg(args.delta.x()).arg(args.delta.y()));
updateWith(args);
}
LOG(("ORIENTATION: %1").arg(!state->orientation ? "none" : (state->orientation == Qt::Horizontal) ? "horizontal" : "vertical"));
return (touchscreen && state->orientation != Qt::Horizontal)
? base::EventFilterResult::Continue
: base::EventFilterResult::Cancel;
@ -326,6 +362,7 @@ void SetupSwipeHandler(
}
return base::EventFilterResult::Continue;
};
widget->setAttribute(Qt::WA_AcceptTouchEvents);
state->filter = base::make_unique_q<QObject>(
base::install_event_filter(widget, filter));
}

View file

@ -35,7 +35,8 @@ void SetupSwipeHandler(
Scroll scroll,
Fn<void(SwipeContextData)> update,
Fn<SwipeHandlerFinishData(int, Qt::LayoutDirection)> generateFinishByTop,
rpl::producer<bool> dontStart = nullptr);
rpl::producer<bool> dontStart = nullptr,
rpl::lifetime *onLifetime = nullptr);
[[nodiscard]] SwipeBackResult SetupSwipeBack(
not_null<Ui::RpWidget*> widget,

@ -1 +1 @@
Subproject commit ba969667301ae4d8da2c2f6c4528bea63443f607
Subproject commit 107729e446e8a2037ceb68374f90978fec937504