mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-03-01 12:00:48 +00:00
parent
7727cdff92
commit
39294a7fe1
@ -7938,10 +7938,12 @@ QPoint HistoryWidget::clampMousePosition(QPoint point) {
|
||||
}
|
||||
|
||||
bool HistoryWidget::touchScroll(const QPoint &delta) {
|
||||
int32 scTop = _scroll->scrollTop(), scMax = _scroll->scrollTopMax();
|
||||
const auto scTop = _scroll->scrollTop();
|
||||
const auto scMax = _scroll->scrollTopMax();
|
||||
const auto scNew = std::clamp(scTop - delta.y(), 0, scMax);
|
||||
if (scNew == scTop) return false;
|
||||
|
||||
if (scNew == scTop) {
|
||||
return false;
|
||||
}
|
||||
_scroll->scrollToY(scNew);
|
||||
return true;
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "base/unixtime.h"
|
||||
#include "base/qt/qt_key_modifiers.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
#include "history/history_message.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "history/history_item_text.h"
|
||||
@ -39,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/window_peer_menu.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/toasts/common_toasts.h"
|
||||
#include "ui/inactive_press.h"
|
||||
@ -291,7 +293,10 @@ ListWidget::ListWidget(
|
||||
, _highlighter(
|
||||
&session().data(),
|
||||
[=](const HistoryItem *item) { return viewForItem(item); },
|
||||
[=](const Element *view) { repaintItem(view); }) {
|
||||
[=](const Element *view) { repaintItem(view); })
|
||||
, _touchSelectTimer([=] { onTouchSelect(); })
|
||||
, _touchScrollTimer([=] { onTouchScrollTimer(); }) {
|
||||
setAttribute(Qt::WA_AcceptTouchEvents);
|
||||
setMouseTracking(true);
|
||||
_scrollDateHideTimer.setCallback([this] { scrollDateHideByTimer(); });
|
||||
session().data().viewRepaintRequest(
|
||||
@ -1904,6 +1909,20 @@ void ListWidget::paintEvent(QPaintEvent *e) {
|
||||
}
|
||||
}
|
||||
|
||||
bool ListWidget::eventHook(QEvent *e) {
|
||||
if (e->type() == QEvent::TouchBegin
|
||||
|| e->type() == QEvent::TouchUpdate
|
||||
|| e->type() == QEvent::TouchEnd
|
||||
|| e->type() == QEvent::TouchCancel) {
|
||||
QTouchEvent *ev = static_cast<QTouchEvent*>(e);
|
||||
if (ev->device()->type() == base::TouchDevice::TouchScreen) {
|
||||
touchEvent(ev);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return RpWidget::eventHook(e);
|
||||
}
|
||||
|
||||
void ListWidget::applyDragSelection() {
|
||||
if (!hasSelectRestriction()) {
|
||||
applyDragSelection(_selected);
|
||||
@ -2224,11 +2243,14 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
request.pointState = _overState.pointState;
|
||||
request.selectedText = _selectedText;
|
||||
request.selectedItems = collectSelectedItems();
|
||||
request.overSelection = showFromTouch
|
||||
|| (_overElement && isInsideSelection(
|
||||
_overElement,
|
||||
_overItemExact ? _overItemExact : _overElement->data().get(),
|
||||
_overState));
|
||||
const auto hasSelection = !request.selectedItems.empty()
|
||||
|| !request.selectedText.empty();
|
||||
request.overSelection = (showFromTouch && hasSelection)
|
||||
|| (_overElement
|
||||
&& isInsideSelection(
|
||||
_overElement,
|
||||
_overItemExact ? _overItemExact : _overElement->data().get(),
|
||||
_overState));
|
||||
|
||||
_menu = FillContextMenu(this, request);
|
||||
|
||||
@ -2301,6 +2323,188 @@ void ListWidget::mousePressEvent(QMouseEvent *e) {
|
||||
mouseActionStart(e->globalPos(), e->button());
|
||||
}
|
||||
|
||||
void ListWidget::onTouchScrollTimer() {
|
||||
auto nowTime = crl::now();
|
||||
if (_touchScrollState == Ui::TouchScrollState::Acceleration && _touchWaitingAcceleration && (nowTime - _touchAccelerationTime) > 40) {
|
||||
_touchScrollState = Ui::TouchScrollState::Manual;
|
||||
touchResetSpeed();
|
||||
} else if (_touchScrollState == Ui::TouchScrollState::Auto || _touchScrollState == Ui::TouchScrollState::Acceleration) {
|
||||
const auto elapsed = int(nowTime - _touchTime);
|
||||
const auto delta = _touchSpeed * elapsed / 1000;
|
||||
const auto hasScrolled = _delegate->listScrollTo(
|
||||
_visibleTop - delta.y());
|
||||
if (_touchSpeed.isNull() || !hasScrolled) {
|
||||
_touchScrollState = Ui::TouchScrollState::Manual;
|
||||
_touchScroll = false;
|
||||
_touchScrollTimer.cancel();
|
||||
} else {
|
||||
_touchTime = nowTime;
|
||||
}
|
||||
touchDeaccelerate(elapsed);
|
||||
}
|
||||
}
|
||||
|
||||
void ListWidget::touchUpdateSpeed() {
|
||||
const auto nowTime = crl::now();
|
||||
if (_touchPrevPosValid) {
|
||||
const int elapsed = nowTime - _touchSpeedTime;
|
||||
if (elapsed) {
|
||||
const QPoint newPixelDiff = (_touchPos - _touchPrevPos);
|
||||
const QPoint pixelsPerSecond = newPixelDiff * (1000 / elapsed);
|
||||
|
||||
// fingers are inacurates, we ignore small changes to avoid stopping the autoscroll because
|
||||
// of a small horizontal offset when scrolling vertically
|
||||
const int newSpeedY = (qAbs(pixelsPerSecond.y()) > Ui::kFingerAccuracyThreshold) ? pixelsPerSecond.y() : 0;
|
||||
const int newSpeedX = (qAbs(pixelsPerSecond.x()) > Ui::kFingerAccuracyThreshold) ? pixelsPerSecond.x() : 0;
|
||||
if (_touchScrollState == Ui::TouchScrollState::Auto) {
|
||||
const int oldSpeedY = _touchSpeed.y();
|
||||
const int oldSpeedX = _touchSpeed.x();
|
||||
if ((oldSpeedY <= 0 && newSpeedY <= 0) || ((oldSpeedY >= 0 && newSpeedY >= 0)
|
||||
&& (oldSpeedX <= 0 && newSpeedX <= 0)) || (oldSpeedX >= 0 && newSpeedX >= 0)) {
|
||||
_touchSpeed.setY(std::clamp(
|
||||
(oldSpeedY + (newSpeedY / 4)),
|
||||
-Ui::kMaxScrollAccelerated,
|
||||
+Ui::kMaxScrollAccelerated));
|
||||
_touchSpeed.setX(std::clamp(
|
||||
(oldSpeedX + (newSpeedX / 4)),
|
||||
-Ui::kMaxScrollAccelerated,
|
||||
+Ui::kMaxScrollAccelerated));
|
||||
} else {
|
||||
_touchSpeed = QPoint();
|
||||
}
|
||||
} else {
|
||||
// we average the speed to avoid strange effects with the last delta
|
||||
if (!_touchSpeed.isNull()) {
|
||||
_touchSpeed.setX(std::clamp(
|
||||
(_touchSpeed.x() / 4) + (newSpeedX * 3 / 4),
|
||||
-Ui::kMaxScrollFlick,
|
||||
+Ui::kMaxScrollFlick));
|
||||
_touchSpeed.setY(std::clamp(
|
||||
(_touchSpeed.y() / 4) + (newSpeedY * 3 / 4),
|
||||
-Ui::kMaxScrollFlick,
|
||||
+Ui::kMaxScrollFlick));
|
||||
} else {
|
||||
_touchSpeed = QPoint(newSpeedX, newSpeedY);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_touchPrevPosValid = true;
|
||||
}
|
||||
_touchSpeedTime = nowTime;
|
||||
_touchPrevPos = _touchPos;
|
||||
}
|
||||
|
||||
void ListWidget::touchResetSpeed() {
|
||||
_touchSpeed = QPoint();
|
||||
_touchPrevPosValid = false;
|
||||
}
|
||||
|
||||
void ListWidget::touchDeaccelerate(int32 elapsed) {
|
||||
int32 x = _touchSpeed.x();
|
||||
int32 y = _touchSpeed.y();
|
||||
_touchSpeed.setX((x == 0) ? x : (x > 0) ? qMax(0, x - elapsed) : qMin(0, x + elapsed));
|
||||
_touchSpeed.setY((y == 0) ? y : (y > 0) ? qMax(0, y - elapsed) : qMin(0, y + elapsed));
|
||||
}
|
||||
|
||||
void ListWidget::touchEvent(QTouchEvent *e) {
|
||||
if (e->type() == QEvent::TouchCancel) { // cancel
|
||||
if (!_touchInProgress) return;
|
||||
_touchInProgress = false;
|
||||
_touchSelectTimer.cancel();
|
||||
_touchScroll = _touchSelect = false;
|
||||
_touchScrollState = Ui::TouchScrollState::Manual;
|
||||
mouseActionCancel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!e->touchPoints().isEmpty()) {
|
||||
_touchPrevPos = _touchPos;
|
||||
_touchPos = e->touchPoints().cbegin()->screenPos().toPoint();
|
||||
}
|
||||
|
||||
switch (e->type()) {
|
||||
case QEvent::TouchBegin: {
|
||||
if (_menu) {
|
||||
e->accept();
|
||||
return; // ignore mouse press, that was hiding context menu
|
||||
}
|
||||
if (_touchInProgress) return;
|
||||
if (e->touchPoints().isEmpty()) return;
|
||||
|
||||
_touchInProgress = true;
|
||||
if (_touchScrollState == Ui::TouchScrollState::Auto) {
|
||||
_touchScrollState = Ui::TouchScrollState::Acceleration;
|
||||
_touchWaitingAcceleration = true;
|
||||
_touchAccelerationTime = crl::now();
|
||||
touchUpdateSpeed();
|
||||
_touchStart = _touchPos;
|
||||
} else {
|
||||
_touchScroll = false;
|
||||
_touchSelectTimer.callOnce(QApplication::startDragTime());
|
||||
}
|
||||
_touchSelect = false;
|
||||
_touchStart = _touchPrevPos = _touchPos;
|
||||
} break;
|
||||
|
||||
case QEvent::TouchUpdate: {
|
||||
if (!_touchInProgress) return;
|
||||
if (_touchSelect) {
|
||||
mouseActionUpdate(_touchPos);
|
||||
} else if (!_touchScroll && (_touchPos - _touchStart).manhattanLength() >= QApplication::startDragDistance()) {
|
||||
_touchSelectTimer.cancel();
|
||||
_touchScroll = true;
|
||||
touchUpdateSpeed();
|
||||
}
|
||||
if (_touchScroll) {
|
||||
if (_touchScrollState == Ui::TouchScrollState::Manual) {
|
||||
touchScrollUpdated(_touchPos);
|
||||
} else if (_touchScrollState == Ui::TouchScrollState::Acceleration) {
|
||||
touchUpdateSpeed();
|
||||
_touchAccelerationTime = crl::now();
|
||||
if (_touchSpeed.isNull()) {
|
||||
_touchScrollState = Ui::TouchScrollState::Manual;
|
||||
}
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case QEvent::TouchEnd: {
|
||||
if (!_touchInProgress) return;
|
||||
_touchInProgress = false;
|
||||
auto weak = Ui::MakeWeak(this);
|
||||
if (_touchSelect) {
|
||||
mouseActionFinish(_touchPos, Qt::RightButton);
|
||||
QContextMenuEvent contextMenu(QContextMenuEvent::Mouse, mapFromGlobal(_touchPos), _touchPos);
|
||||
showContextMenu(&contextMenu, true);
|
||||
_touchScroll = false;
|
||||
} else if (_touchScroll) {
|
||||
if (_touchScrollState == Ui::TouchScrollState::Manual) {
|
||||
_touchScrollState = Ui::TouchScrollState::Auto;
|
||||
_touchPrevPosValid = false;
|
||||
_touchScrollTimer.callEach(15);
|
||||
_touchTime = crl::now();
|
||||
} else if (_touchScrollState == Ui::TouchScrollState::Auto) {
|
||||
_touchScrollState = Ui::TouchScrollState::Manual;
|
||||
_touchScroll = false;
|
||||
touchResetSpeed();
|
||||
} else if (_touchScrollState == Ui::TouchScrollState::Acceleration) {
|
||||
_touchScrollState = Ui::TouchScrollState::Auto;
|
||||
_touchWaitingAcceleration = false;
|
||||
_touchPrevPosValid = false;
|
||||
}
|
||||
} else { // One short tap is like left mouse click.
|
||||
mouseActionStart(_touchPos, Qt::LeftButton);
|
||||
mouseActionFinish(_touchPos, Qt::LeftButton);
|
||||
}
|
||||
if (weak) {
|
||||
_touchSelectTimer.cancel();
|
||||
_touchSelect = false;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void ListWidget::mouseMoveEvent(QMouseEvent *e) {
|
||||
static auto lastGlobalPosition = e->globalPos();
|
||||
auto reallyMoved = (lastGlobalPosition != e->globalPos());
|
||||
@ -2326,6 +2530,12 @@ void ListWidget::mouseReleaseEvent(QMouseEvent *e) {
|
||||
}
|
||||
}
|
||||
|
||||
void ListWidget::touchScrollUpdated(const QPoint &screenPos) {
|
||||
_touchPos = screenPos;
|
||||
_delegate->listScrollTo(_visibleTop - (_touchPos - _touchPrevPos).y());
|
||||
touchUpdateSpeed();
|
||||
}
|
||||
|
||||
void ListWidget::enterEventHook(QEnterEvent *e) {
|
||||
mouseActionUpdate(QCursor::pos());
|
||||
return TWidget::enterEventHook(e);
|
||||
@ -2378,6 +2588,11 @@ void ListWidget::updateDragSelection() {
|
||||
updateDragSelection(fromView, fromState, tillView, tillState);
|
||||
}
|
||||
|
||||
void ListWidget::onTouchSelect() {
|
||||
_touchSelect = true;
|
||||
mouseActionStart(_touchPos, Qt::LeftButton);
|
||||
}
|
||||
|
||||
void ListWidget::updateDragSelection(
|
||||
const Element *fromView,
|
||||
const MouseState &fromState,
|
||||
|
@ -26,6 +26,7 @@ namespace Ui {
|
||||
class PopupMenu;
|
||||
class ChatTheme;
|
||||
struct ChatPaintContext;
|
||||
enum class TouchScrollState;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Window {
|
||||
@ -86,7 +87,7 @@ using SelectedItems = std::vector<SelectedItem>;
|
||||
class ListDelegate {
|
||||
public:
|
||||
virtual Context listContext() = 0;
|
||||
virtual void listScrollTo(int top) = 0;
|
||||
virtual bool listScrollTo(int top) = 0; // true if scroll was changed.
|
||||
virtual void listCancelRequest() = 0;
|
||||
virtual void listDeleteRequest() = 0;
|
||||
virtual rpl::producer<Data::MessagesSlice> listSource(
|
||||
@ -224,6 +225,8 @@ public:
|
||||
void selectItem(not_null<HistoryItem*> item);
|
||||
void selectItemAsGroup(not_null<HistoryItem*> item);
|
||||
|
||||
void touchScrollUpdated(const QPoint &screenPos);
|
||||
|
||||
[[nodiscard]] bool loadedAtTopKnown() const;
|
||||
[[nodiscard]] bool loadedAtTop() const;
|
||||
[[nodiscard]] bool loadedAtBottomKnown() const;
|
||||
@ -312,6 +315,8 @@ protected:
|
||||
int visibleTop,
|
||||
int visibleBottom) override;
|
||||
|
||||
bool eventHook(QEvent *e) override; // calls touchEvent when necessary
|
||||
void touchEvent(QTouchEvent *e);
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void keyPressEvent(QKeyEvent *e) override;
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
@ -381,6 +386,9 @@ private:
|
||||
using CursorState = HistoryView::CursorState;
|
||||
using ChosenReaction = HistoryView::Reactions::ChosenReaction;
|
||||
|
||||
void onTouchSelect();
|
||||
void onTouchScrollTimer();
|
||||
|
||||
void refreshViewer();
|
||||
void updateAroundPositionFromNearest(int nearestIndex);
|
||||
void refreshRows(const Data::MessagesSlice &old);
|
||||
@ -417,6 +425,10 @@ private:
|
||||
void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false);
|
||||
void reactionChosen(ChosenReaction reaction);
|
||||
|
||||
void touchResetSpeed();
|
||||
void touchUpdateSpeed();
|
||||
void touchDeaccelerate(int32 elapsed);
|
||||
|
||||
[[nodiscard]] int findItemIndexByY(int y) const;
|
||||
[[nodiscard]] not_null<Element*> findItemByY(int y) const;
|
||||
[[nodiscard]] Element *strictFindItemByY(int y) const;
|
||||
@ -645,10 +657,26 @@ private:
|
||||
|
||||
ElementHighlighter _highlighter;
|
||||
|
||||
// scroll by touch support (at least Windows Surface tablets)
|
||||
bool _touchScroll = false;
|
||||
bool _touchSelect = false;
|
||||
bool _touchInProgress = false;
|
||||
QPoint _touchStart, _touchPrevPos, _touchPos;
|
||||
base::Timer _touchSelectTimer;
|
||||
|
||||
Ui::DraggingScrollManager _selectScroll;
|
||||
|
||||
InfoTooltip _topToast;
|
||||
|
||||
Ui::TouchScrollState _touchScrollState = Ui::TouchScrollState();
|
||||
bool _touchPrevPosValid = false;
|
||||
bool _touchWaitingAcceleration = false;
|
||||
QPoint _touchSpeed;
|
||||
crl::time _touchSpeedTime = 0;
|
||||
crl::time _touchAccelerationTime = 0;
|
||||
crl::time _touchTime = 0;
|
||||
base::Timer _touchScrollTimer;
|
||||
|
||||
rpl::event_stream<FullMsgId> _requestedToEditMessage;
|
||||
rpl::event_stream<FullMsgId> _requestedToReplyToMessage;
|
||||
rpl::event_stream<FullMsgId> _requestedToReadMessage;
|
||||
|
@ -517,12 +517,14 @@ Context PinnedWidget::listContext() {
|
||||
return Context::Pinned;
|
||||
}
|
||||
|
||||
void PinnedWidget::listScrollTo(int top) {
|
||||
if (_scroll->scrollTop() != top) {
|
||||
_scroll->scrollToY(top);
|
||||
} else {
|
||||
bool PinnedWidget::listScrollTo(int top) {
|
||||
top = std::clamp(top, 0, _scroll->scrollTopMax());
|
||||
if (_scroll->scrollTop() == top) {
|
||||
updateInnerVisibleArea();
|
||||
return false;
|
||||
}
|
||||
_scroll->scrollToY(top);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PinnedWidget::listCancelRequest() {
|
||||
|
@ -77,7 +77,7 @@ public:
|
||||
|
||||
// ListDelegate interface.
|
||||
Context listContext() override;
|
||||
void listScrollTo(int top) override;
|
||||
bool listScrollTo(int top) override;
|
||||
void listCancelRequest() override;
|
||||
void listDeleteRequest() override;
|
||||
rpl::producer<Data::MessagesSlice> listSource(
|
||||
|
@ -1425,11 +1425,7 @@ bool RepliesWidget::showAtPositionNow(
|
||||
: (std::abs(fullDelta) > limit)
|
||||
? AnimatedScroll::Part
|
||||
: AnimatedScroll::Full;
|
||||
_inner->scrollTo(
|
||||
wanted,
|
||||
use,
|
||||
scrollDelta,
|
||||
type);
|
||||
_inner->scrollTo(wanted, use, scrollDelta, type);
|
||||
if (use != Data::MaxMessagePosition
|
||||
&& use != Data::UnreadMessagePosition) {
|
||||
_inner->highlightMessage(use.fullId);
|
||||
@ -1839,12 +1835,14 @@ Context RepliesWidget::listContext() {
|
||||
return Context::Replies;
|
||||
}
|
||||
|
||||
void RepliesWidget::listScrollTo(int top) {
|
||||
if (_scroll->scrollTop() != top) {
|
||||
_scroll->scrollToY(top);
|
||||
} else {
|
||||
bool RepliesWidget::listScrollTo(int top) {
|
||||
top = std::clamp(top, 0, _scroll->scrollTopMax());
|
||||
if (_scroll->scrollTop() == top) {
|
||||
updateInnerVisibleArea();
|
||||
return false;
|
||||
}
|
||||
_scroll->scrollToY(top);
|
||||
return true;
|
||||
}
|
||||
|
||||
void RepliesWidget::listCancelRequest() {
|
||||
|
@ -114,7 +114,7 @@ public:
|
||||
|
||||
// ListDelegate interface.
|
||||
Context listContext() override;
|
||||
void listScrollTo(int top) override;
|
||||
bool listScrollTo(int top) override;
|
||||
void listCancelRequest() override;
|
||||
void listDeleteRequest() override;
|
||||
rpl::producer<Data::MessagesSlice> listSource(
|
||||
|
@ -1160,12 +1160,14 @@ Context ScheduledWidget::listContext() {
|
||||
return Context::History;
|
||||
}
|
||||
|
||||
void ScheduledWidget::listScrollTo(int top) {
|
||||
if (_scroll->scrollTop() != top) {
|
||||
_scroll->scrollToY(top);
|
||||
} else {
|
||||
bool ScheduledWidget::listScrollTo(int top) {
|
||||
top = std::clamp(top, 0, _scroll->scrollTopMax());
|
||||
if (_scroll->scrollTop() == top) {
|
||||
updateInnerVisibleArea();
|
||||
return false;
|
||||
}
|
||||
_scroll->scrollToY(top);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScheduledWidget::listCancelRequest() {
|
||||
|
@ -99,7 +99,7 @@ public:
|
||||
|
||||
// ListDelegate interface.
|
||||
Context listContext() override;
|
||||
void listScrollTo(int top) override;
|
||||
bool listScrollTo(int top) override;
|
||||
void listCancelRequest() override;
|
||||
void listDeleteRequest() override;
|
||||
rpl::producer<Data::MessagesSlice> listSource(
|
||||
|
Loading…
Reference in New Issue
Block a user