mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-07-25 06:54:43 +00:00
Added initial support of auto-scroll with middle click to history view.
Fixed #24518.
This commit is contained in:
@@ -333,6 +333,11 @@ HistoryInner::HistoryInner(
|
||||
[=](QRect updated) { update(updated); }))
|
||||
, _touchSelectTimer([=] { onTouchSelect(); })
|
||||
, _touchScrollTimer([=] { onTouchScrollTimer(); })
|
||||
, _middleClickAutoscroll(
|
||||
[=](int d) { _scroll->scrollToY(_scroll->scrollTop() + d); },
|
||||
[=](const QCursor &cursor) { setCursor(cursor); },
|
||||
[=] { mouseActionUpdate(QCursor::pos()); setCursor(_cursor); },
|
||||
[=] { return window()->isActiveWindow(); })
|
||||
, _scrollDateCheck([this] { scrollDateCheck(); })
|
||||
, _scrollDateHideTimer([this] { scrollDateHideByTimer(); }) {
|
||||
_history->delegateMixin()->setCurrent(this);
|
||||
@@ -1734,6 +1739,14 @@ void HistoryInner::touchEvent(QTouchEvent *e) {
|
||||
void HistoryInner::mouseMoveEvent(QMouseEvent *e) {
|
||||
static auto lastGlobalPosition = e->globalPos();
|
||||
auto reallyMoved = (lastGlobalPosition != e->globalPos());
|
||||
if (_middleClickAutoscroll.active()) {
|
||||
if (reallyMoved) {
|
||||
_mouseActive = true;
|
||||
lastGlobalPosition = e->globalPos();
|
||||
}
|
||||
e->accept();
|
||||
return;
|
||||
}
|
||||
auto buttonsPressed = (e->buttons() & (Qt::LeftButton | Qt::MiddleButton));
|
||||
if (!buttonsPressed && _mouseAction != MouseAction::None) {
|
||||
mouseReleaseEvent(e);
|
||||
@@ -1786,6 +1799,18 @@ void HistoryInner::mousePressEvent(QMouseEvent *e) {
|
||||
e->accept();
|
||||
return; // ignore mouse press, that was hiding context menu
|
||||
}
|
||||
if (_middleClickAutoscroll.active()) {
|
||||
_middleClickAutoscroll.stop();
|
||||
e->accept();
|
||||
return;
|
||||
}
|
||||
if (e->button() == Qt::MiddleButton) {
|
||||
mouseActionCancel();
|
||||
ClickHandler::unpressed();
|
||||
_middleClickAutoscroll.start(e->globalPos());
|
||||
e->accept();
|
||||
return;
|
||||
}
|
||||
_mouseActive = true;
|
||||
mouseActionStart(e->globalPos(), e->button());
|
||||
}
|
||||
@@ -2196,6 +2221,10 @@ void HistoryInner::mouseActionFinish(
|
||||
}
|
||||
|
||||
void HistoryInner::mouseReleaseEvent(QMouseEvent *e) {
|
||||
if (_middleClickAutoscroll.active()) {
|
||||
e->accept();
|
||||
return;
|
||||
}
|
||||
mouseActionFinish(e->globalPos(), e->button());
|
||||
if (!rect().contains(e->pos())) {
|
||||
leaveEvent(e);
|
||||
@@ -3503,6 +3532,10 @@ TextForMimeData HistoryInner::getSelectedText() const {
|
||||
}
|
||||
|
||||
void HistoryInner::keyPressEvent(QKeyEvent *e) {
|
||||
if (_middleClickAutoscroll.active() && e->key() == Qt::Key_Escape) {
|
||||
_middleClickAutoscroll.stop();
|
||||
return;
|
||||
}
|
||||
if (e->key() == Qt::Key_Escape) {
|
||||
_widget->escape();
|
||||
} else if (e == QKeySequence::Copy && !_selected.empty()) {
|
||||
|
||||
@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/controls/swipe_handler_data.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/dragging_scroll_manager.h"
|
||||
#include "ui/widgets/middle_click_autoscroll.h"
|
||||
#include "ui/widgets/tooltip.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/userpic_view.h"
|
||||
@@ -560,6 +561,7 @@ private:
|
||||
crl::time _touchAccelerationTime = 0;
|
||||
crl::time _touchTime = 0;
|
||||
base::Timer _touchScrollTimer;
|
||||
Ui::MiddleClickAutoscroll _middleClickAutoscroll;
|
||||
|
||||
Ui::Controls::SwipeContextData _gestureHorizontal;
|
||||
Ui::Controls::SwipeBackResult _swipeBackData;
|
||||
|
||||
@@ -413,7 +413,12 @@ ListWidget::ListWidget(
|
||||
[=](const HistoryItem *item) { return viewForItem(item); },
|
||||
[=](const Element *view) { repaintItem(view); })
|
||||
, _touchSelectTimer([=] { onTouchSelect(); })
|
||||
, _touchScrollTimer([=] { onTouchScrollTimer(); }) {
|
||||
, _touchScrollTimer([=] { onTouchScrollTimer(); })
|
||||
, _middleClickAutoscroll(
|
||||
[=](int d) { _delegate->listScrollTo(_visibleTop + d, false); },
|
||||
[=](const QCursor &cursor) { setCursor(cursor); },
|
||||
[=] { mouseActionUpdate(QCursor::pos()); setCursor(_cursor); },
|
||||
[=] { return window()->isActiveWindow(); }) {
|
||||
setAttribute(Qt::WA_AcceptTouchEvents);
|
||||
setMouseTracking(true);
|
||||
_scrollDateHideTimer.setCallback([this] { scrollDateHideByTimer(); });
|
||||
@@ -2699,6 +2704,10 @@ void ListWidget::keyPressEvent(QKeyEvent *e) {
|
||||
const auto hasModifiers = (Qt::NoModifier !=
|
||||
(e->modifiers()
|
||||
& ~(Qt::KeypadModifier | Qt::GroupSwitchModifier)));
|
||||
if (_middleClickAutoscroll.active() && key == Qt::Key_Escape) {
|
||||
_middleClickAutoscroll.stop();
|
||||
return;
|
||||
}
|
||||
if (key == Qt::Key_Escape || key == Qt::Key_Back) {
|
||||
if (hasSelectedText() || hasSelectedItems()) {
|
||||
cancelSelection();
|
||||
@@ -3003,6 +3012,18 @@ void ListWidget::mousePressEvent(QMouseEvent *e) {
|
||||
e->accept();
|
||||
return; // ignore mouse press, that was hiding context menu
|
||||
}
|
||||
if (_middleClickAutoscroll.active()) {
|
||||
_middleClickAutoscroll.stop();
|
||||
e->accept();
|
||||
return;
|
||||
}
|
||||
if (e->button() == Qt::MiddleButton) {
|
||||
mouseActionCancel();
|
||||
ClickHandler::unpressed();
|
||||
_middleClickAutoscroll.start(e->globalPos());
|
||||
e->accept();
|
||||
return;
|
||||
}
|
||||
_mouseActive = true;
|
||||
mouseActionStart(e->globalPos(), e->button());
|
||||
}
|
||||
@@ -3206,6 +3227,14 @@ void ListWidget::touchEvent(QTouchEvent *e) {
|
||||
void ListWidget::mouseMoveEvent(QMouseEvent *e) {
|
||||
static auto lastGlobalPosition = e->globalPos();
|
||||
auto reallyMoved = (lastGlobalPosition != e->globalPos());
|
||||
if (_middleClickAutoscroll.active()) {
|
||||
if (reallyMoved) {
|
||||
_mouseActive = true;
|
||||
lastGlobalPosition = e->globalPos();
|
||||
}
|
||||
e->accept();
|
||||
return;
|
||||
}
|
||||
auto buttonsPressed = (e->buttons() & (Qt::LeftButton | Qt::MiddleButton));
|
||||
if (!buttonsPressed && _mouseAction != MouseAction::None) {
|
||||
mouseReleaseEvent(e);
|
||||
@@ -3223,6 +3252,10 @@ void ListWidget::mouseMoveEvent(QMouseEvent *e) {
|
||||
}
|
||||
|
||||
void ListWidget::mouseReleaseEvent(QMouseEvent *e) {
|
||||
if (_middleClickAutoscroll.active()) {
|
||||
e->accept();
|
||||
return;
|
||||
}
|
||||
mouseActionFinish(e->globalPos(), e->button());
|
||||
if (!rect().contains(e->pos())) {
|
||||
leaveEvent(e);
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/rp_widget.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/dragging_scroll_manager.h"
|
||||
#include "ui/widgets/middle_click_autoscroll.h"
|
||||
#include "ui/widgets/tooltip.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "data/data_messages.h"
|
||||
@@ -867,6 +868,7 @@ private:
|
||||
crl::time _touchAccelerationTime = 0;
|
||||
crl::time _touchTime = 0;
|
||||
base::Timer _touchScrollTimer;
|
||||
Ui::MiddleClickAutoscroll _middleClickAutoscroll;
|
||||
|
||||
rpl::event_stream<FullMsgId> _requestedToEditMessage;
|
||||
rpl::event_stream<ReplyToMessageRequest> _requestedToReplyToMessage;
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
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/widgets/middle_click_autoscroll.h"
|
||||
|
||||
#include "styles/style_widgets.h"
|
||||
#include "ui/effects/animation_value.h"
|
||||
#include "ui/rect.h"
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
constexpr auto kDelay = 15;
|
||||
|
||||
[[nodiscard]] int Deadzone() {
|
||||
return std::max(1, int(std::lround(style::ConvertFloatScale(6.))));
|
||||
}
|
||||
|
||||
[[nodiscard]] float64 SpeedScale() {
|
||||
return style::ConvertFloatScale(30.);
|
||||
}
|
||||
|
||||
[[nodiscard]] float64 MaxSpeed() {
|
||||
return style::ConvertFloatScale(3600.);
|
||||
}
|
||||
|
||||
[[nodiscard]] QImage PaintCursorImage(
|
||||
int size,
|
||||
int factor,
|
||||
bool drawOuter,
|
||||
bool drawTopArrow,
|
||||
bool drawBottomArrow,
|
||||
const QColor &inner,
|
||||
const QColor &indicator,
|
||||
const QColor &topArrow,
|
||||
const QColor &bottomArrow) {
|
||||
auto image = QImage(
|
||||
QSize(size, size) * factor,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
image.setDevicePixelRatio(factor);
|
||||
image.fill(Qt::transparent);
|
||||
auto p = QPainter(&image);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
p.setPen(Qt::NoPen);
|
||||
|
||||
const auto outerRect = QRectF(QPointF(0.5, 0.5), Size(size - 1.));
|
||||
if (drawOuter) {
|
||||
const auto outerStroke = std::max(1., style::ConvertFloatScale(1.5));
|
||||
auto pen = QPen(indicator);
|
||||
pen.setWidthF(outerStroke);
|
||||
p.setPen(pen);
|
||||
p.setBrush(inner);
|
||||
p.drawEllipse(outerRect.marginsRemoved(Margins(outerStroke * 0.5)));
|
||||
}
|
||||
p.setPen(Qt::NoPen);
|
||||
const auto centerSkip = style::ConvertFloatScale(10.5);
|
||||
const auto centerRect = outerRect - Margins(centerSkip);
|
||||
p.setBrush(indicator);
|
||||
p.drawEllipse(centerRect);
|
||||
|
||||
const auto middle = size * 0.5;
|
||||
const auto arrowGap = style::ConvertFloatScale(5.);
|
||||
const auto headHalfWidth = style::ConvertFloatScale(4.5);
|
||||
const auto headHeight = style::ConvertFloatScale(3.5);
|
||||
const auto stroke = std::max(1., style::ConvertFloatScale(2.));
|
||||
const auto drawArrow = [&](bool up, const QColor &color) {
|
||||
auto pen = QPen(color);
|
||||
pen.setWidthF(stroke);
|
||||
pen.setCapStyle(Qt::RoundCap);
|
||||
pen.setJoinStyle(Qt::RoundJoin);
|
||||
p.setPen(pen);
|
||||
const auto dir = up ? -1. : 1.;
|
||||
const auto tipY = middle + dir * (arrowGap + headHeight);
|
||||
const auto neckY = tipY - dir * headHeight;
|
||||
p.drawLine(
|
||||
QPointF(middle, tipY),
|
||||
QPointF(middle - headHalfWidth, neckY));
|
||||
p.drawLine(
|
||||
QPointF(middle, tipY),
|
||||
QPointF(middle + headHalfWidth, neckY));
|
||||
};
|
||||
if (drawTopArrow) {
|
||||
drawArrow(true, topArrow);
|
||||
}
|
||||
if (drawBottomArrow) {
|
||||
drawArrow(false, bottomArrow);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MiddleClickAutoscroll::MiddleClickAutoscroll(
|
||||
Fn<void(int)> scrollBy,
|
||||
Fn<void(const QCursor &)> applyCursor,
|
||||
Fn<void()> restoreCursor,
|
||||
Fn<bool()> shouldContinue)
|
||||
: _scrollBy(std::move(scrollBy))
|
||||
, _applyCursor(std::move(applyCursor))
|
||||
, _restoreCursor(std::move(restoreCursor))
|
||||
, _shouldContinue(std::move(shouldContinue))
|
||||
, _timer([=] { onTimer(); }) {
|
||||
}
|
||||
|
||||
void MiddleClickAutoscroll::start(const QPoint &globalPosition) {
|
||||
if (_active) {
|
||||
stop();
|
||||
}
|
||||
_active = true;
|
||||
_startPosition = globalPosition;
|
||||
_time = crl::now();
|
||||
updateCursor(QCursor::pos().y() - _startPosition.y());
|
||||
_timer.callEach(kDelay);
|
||||
}
|
||||
|
||||
void MiddleClickAutoscroll::stop() {
|
||||
if (!_active) {
|
||||
return;
|
||||
}
|
||||
_active = false;
|
||||
_timer.cancel();
|
||||
if (_restoreCursor) {
|
||||
_restoreCursor();
|
||||
}
|
||||
}
|
||||
|
||||
void MiddleClickAutoscroll::updateCursor(int delta) {
|
||||
if (!_applyCursor) {
|
||||
return;
|
||||
}
|
||||
const auto mode = (std::abs(delta) <= Deadzone())
|
||||
? CursorMode::Neutral
|
||||
: (delta < 0)
|
||||
? CursorMode::Up
|
||||
: CursorMode::Down;
|
||||
_applyCursor(makeCursor(mode));
|
||||
}
|
||||
|
||||
QCursor MiddleClickAutoscroll::makeCursor(CursorMode mode) {
|
||||
struct CachedCursor {
|
||||
CursorMode mode = CursorMode::Neutral;
|
||||
int size = 0;
|
||||
int factor = 0;
|
||||
QColor inner;
|
||||
QColor indicator;
|
||||
QCursor cursor;
|
||||
};
|
||||
const auto size = std::max(
|
||||
1,
|
||||
int(std::lround(style::ConvertFloatScale(27.))));
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto inner = anim::with_alpha(st::windowBg->c, (240. / 255.));
|
||||
const auto active = anim::with_alpha(st::windowFg->c, (210. / 255.));
|
||||
const auto passive = anim::with_alpha(
|
||||
st::windowSubTextFg->c,
|
||||
(210. / 255.));
|
||||
const auto neutral = (mode == CursorMode::Neutral);
|
||||
const auto indicator = neutral ? passive : active;
|
||||
static auto cache = std::vector<CachedCursor>();
|
||||
if (const auto i = ranges::find_if(
|
||||
cache,
|
||||
[&](const CachedCursor &entry) {
|
||||
return (entry.mode == mode)
|
||||
&& (entry.size == size)
|
||||
&& (entry.factor == factor)
|
||||
&& (entry.inner == inner)
|
||||
&& (entry.indicator == indicator);
|
||||
});
|
||||
i != cache.end()) {
|
||||
return i->cursor;
|
||||
}
|
||||
const auto image = PaintCursorImage(
|
||||
size,
|
||||
factor,
|
||||
neutral,
|
||||
mode != CursorMode::Down,
|
||||
mode != CursorMode::Up,
|
||||
inner,
|
||||
indicator,
|
||||
indicator,
|
||||
indicator);
|
||||
const auto hot = int(std::floor(size * 0.5));
|
||||
auto cursor = QCursor(QPixmap::fromImage(image), hot, hot);
|
||||
cache.push_back(CachedCursor{
|
||||
.mode = mode,
|
||||
.size = size,
|
||||
.factor = factor,
|
||||
.inner = inner,
|
||||
.indicator = indicator,
|
||||
.cursor = cursor,
|
||||
});
|
||||
return cursor;
|
||||
}
|
||||
|
||||
void MiddleClickAutoscroll::onTimer() {
|
||||
if (!_active) {
|
||||
return;
|
||||
} else if (_shouldContinue && !_shouldContinue()) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
const auto now = crl::now();
|
||||
const auto elapsed = std::max(now - _time, 1LL);
|
||||
_time = now;
|
||||
const auto delta = QCursor::pos().y() - _startPosition.y();
|
||||
updateCursor(delta);
|
||||
const auto absolute = std::abs(delta) - Deadzone();
|
||||
if (absolute <= 0) {
|
||||
return;
|
||||
}
|
||||
const auto speed = std::min(
|
||||
absolute * SpeedScale(),
|
||||
MaxSpeed());
|
||||
auto scroll = int(std::lround((speed * elapsed) / 1000.));
|
||||
if (scroll <= 0) {
|
||||
scroll = 1;
|
||||
}
|
||||
if (delta < 0) {
|
||||
scroll = -scroll;
|
||||
}
|
||||
_scrollBy(scroll);
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
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
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/timer.h"
|
||||
|
||||
class QCursor;
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class MiddleClickAutoscroll final {
|
||||
public:
|
||||
explicit MiddleClickAutoscroll(
|
||||
Fn<void(int)> scrollBy,
|
||||
Fn<void(const QCursor &)> applyCursor = nullptr,
|
||||
Fn<void()> restoreCursor = nullptr,
|
||||
Fn<bool()> shouldContinue = nullptr);
|
||||
|
||||
[[nodiscard]] bool active() const {
|
||||
return _active;
|
||||
}
|
||||
|
||||
void start(const QPoint &globalPosition);
|
||||
void stop();
|
||||
|
||||
private:
|
||||
enum class CursorMode {
|
||||
Neutral,
|
||||
Up,
|
||||
Down,
|
||||
};
|
||||
|
||||
void updateCursor(int delta);
|
||||
void onTimer();
|
||||
[[nodiscard]] static QCursor makeCursor(CursorMode mode);
|
||||
|
||||
Fn<void(int)> _scrollBy;
|
||||
Fn<void(const QCursor &)> _applyCursor;
|
||||
Fn<void()> _restoreCursor;
|
||||
Fn<bool()> _shouldContinue;
|
||||
bool _active = false;
|
||||
QPoint _startPosition;
|
||||
crl::time _time = 0;
|
||||
base::Timer _timer;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui
|
||||
@@ -513,6 +513,8 @@ PRIVATE
|
||||
ui/widgets/horizontal_fit_container.h
|
||||
ui/widgets/level_meter.cpp
|
||||
ui/widgets/level_meter.h
|
||||
ui/widgets/middle_click_autoscroll.cpp
|
||||
ui/widgets/middle_click_autoscroll.h
|
||||
ui/widgets/multi_select.cpp
|
||||
ui/widgets/multi_select.h
|
||||
ui/widgets/sent_code_field.cpp
|
||||
|
||||
Reference in New Issue
Block a user