elastic_scroll.cpp 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338
  1. // This file is part of Desktop App Toolkit,
  2. // a set of libraries for developing nice desktop applications.
  3. //
  4. // For license and copyright information please follow this link:
  5. // https://github.com/desktop-app/legal/blob/master/LEGAL
  6. //
  7. #include "ui/widgets/elastic_scroll.h"
  8. #include "ui/painter.h"
  9. #include "ui/ui_utility.h"
  10. #include "ui/qt_weak_factory.h"
  11. #include "base/platform/base_platform_info.h"
  12. #include "base/qt/qt_common_adapters.h"
  13. #include "styles/style_widgets.h"
  14. #include <QtGui/QWindow>
  15. #include <QtCore/QtMath>
  16. #include <QtWidgets/QApplication>
  17. namespace Ui {
  18. namespace {
  19. constexpr auto kOverscrollReturnDuration = crl::time(250);
  20. //constexpr auto kOverscrollPower = 0.6;
  21. constexpr auto kOverscrollFromThreshold = -(1 << 30);
  22. constexpr auto kOverscrollTillThreshold = (1 << 30);
  23. constexpr auto kTouchOverscrollMultiplier = 2;
  24. constexpr auto kLogA = 16.;
  25. constexpr auto kLogB = 10.;
  26. [[nodiscard]] float64 RawFrom(float64 value) {
  27. const auto scale = style::Scale() / 100.;
  28. value /= scale;
  29. const auto result = kLogA * log(1. + value / kLogB);
  30. //const auto result = pow(value, kOverscrollPower);
  31. return result * scale;
  32. }
  33. [[nodiscard]] float64 RawTo(float64 value) {
  34. const auto scale = style::Scale() / 100.;
  35. value /= scale;
  36. const auto result = (exp(value / kLogA) - 1.) * kLogB;
  37. //const auto result = pow(value, 1. / kOverscrollPower);
  38. return result * scale;
  39. }
  40. } // namespace
  41. // Flick scroll taken from
  42. // http://qt-project.org/doc/qt-4.8
  43. // /demos-embedded-anomaly-src-flickcharm-cpp.html
  44. ElasticScrollBar::ElasticScrollBar(
  45. QWidget *parent,
  46. const style::ScrollArea &st,
  47. Qt::Orientation orientation)
  48. : RpWidget(parent)
  49. , _st(st)
  50. , _hideTimer([=] { toggle(false); })
  51. , _shown(!_st.hiding)
  52. , _vertical(orientation == Qt::Vertical) {
  53. setAttribute(Qt::WA_NoMousePropagation);
  54. }
  55. void ElasticScrollBar::refreshGeometry() {
  56. update();
  57. const auto skip = _st.deltax;
  58. const auto fullSkip = _st.deltat + _st.deltab;
  59. const auto extSize = _vertical ? height() : width();
  60. const auto thickness = (_vertical ? width() : height()) - 2 * skip;
  61. const auto minSize = fullSkip + 2 * thickness;
  62. if (_state.fullSize <= 0
  63. || _state.visibleFrom >= _state.visibleTill
  64. || extSize < minSize) {
  65. _bar = _area = QRect();
  66. hide();
  67. return;
  68. }
  69. const auto available = extSize - fullSkip;
  70. _area = _vertical
  71. ? QRect(skip, _st.deltat, thickness, available)
  72. : QRect(_st.deltat, skip, available, thickness);
  73. const auto barMin = std::min(st::scrollBarMin, available / 2);
  74. const auto visibleHeight = _state.visibleTill - _state.visibleFrom;
  75. const auto scrollableHeight = _state.fullSize - visibleHeight;
  76. const auto barWanted = (available * visibleHeight) / _state.fullSize;
  77. if (barWanted >= available) {
  78. _bar = _area = QRect();
  79. hide();
  80. return;
  81. }
  82. const auto bar = std::max(barMin, barWanted);
  83. const auto outsideBar = available - bar;
  84. const auto scale = [&](int value) {
  85. return (outsideBar * value) / scrollableHeight;
  86. };
  87. const auto barFrom = scale(_state.visibleFrom);
  88. const auto barTill = barFrom + bar;
  89. const auto cutFrom = std::clamp(barFrom, 0, available - thickness);
  90. const auto cutTill = std::clamp(barTill, thickness, available);
  91. const auto cutBar = cutTill - cutFrom;
  92. _bar = _vertical
  93. ? QRect(_area.x(), _area.y() + cutFrom, _area.width(), cutBar)
  94. : QRect(_area.x() + cutFrom, _area.y(), cutBar, _area.height());
  95. if (isHidden()) {
  96. show();
  97. }
  98. }
  99. bool ElasticScrollBar::barHighlighted() const {
  100. return _overBar || _dragging;
  101. }
  102. void ElasticScrollBar::toggle(bool shown, anim::type animated) {
  103. const auto instant = (animated == anim::type::instant);
  104. const auto changed = (_shown != shown);
  105. _shown = shown;
  106. if (instant) {
  107. _shownAnimation.stop();
  108. }
  109. if (_shown && _st.hiding) {
  110. _hideTimer.callOnce(_st.hiding);
  111. }
  112. if (changed && !instant) {
  113. _shownAnimation.start(
  114. [=] { update(); },
  115. _shown ? 0. : 1.,
  116. _shown ? 1. : 0.,
  117. _st.duration);
  118. }
  119. update();
  120. }
  121. void ElasticScrollBar::toggleOver(bool over, anim::type animated) {
  122. const auto instant = (animated == anim::type::instant);
  123. const auto changed = (_over != over);
  124. _over = over;
  125. if (instant) {
  126. _overAnimation.stop();
  127. }
  128. if (!instant && changed) {
  129. _overAnimation.start(
  130. [=] { update(); },
  131. _over ? 0. : 1.,
  132. _over ? 1. : 0.,
  133. _st.duration);
  134. }
  135. update();
  136. }
  137. void ElasticScrollBar::toggleOverBar(bool over, anim::type animated) {
  138. const auto instant = (animated == anim::type::instant);
  139. const auto wasHighlight = barHighlighted();
  140. _overBar = over;
  141. if (instant) {
  142. _barHighlightAnimation.stop();
  143. } else {
  144. startBarHighlightAnimation(wasHighlight);
  145. }
  146. update();
  147. }
  148. void ElasticScrollBar::toggleDragging(bool dragging, anim::type animated) {
  149. const auto instant = (animated == anim::type::instant);
  150. const auto wasHighlight = barHighlighted();
  151. _dragging = dragging;
  152. if (instant) {
  153. _barHighlightAnimation.stop();
  154. } else {
  155. startBarHighlightAnimation(wasHighlight);
  156. }
  157. update();
  158. }
  159. void ElasticScrollBar::startBarHighlightAnimation(bool wasHighlighted) {
  160. if (barHighlighted() == wasHighlighted) {
  161. return;
  162. }
  163. const auto highlighted = !wasHighlighted;
  164. _barHighlightAnimation.start(
  165. [=] { update(); },
  166. highlighted ? 0. : 1.,
  167. highlighted ? 1. : 0.,
  168. _st.duration);
  169. }
  170. rpl::producer<int> ElasticScrollBar::visibleFromDragged() const {
  171. return _visibleFromDragged.events();
  172. }
  173. void ElasticScrollBar::updateState(ScrollState state) {
  174. if (_state != state) {
  175. _state = state;
  176. refreshGeometry();
  177. toggle(true);
  178. }
  179. }
  180. void ElasticScrollBar::paintEvent(QPaintEvent *e) {
  181. if (_bar.isEmpty()) {
  182. hide();
  183. return;
  184. }
  185. const auto barHighlight = _barHighlightAnimation.value(
  186. barHighlighted() ? 1. : 0.);
  187. const auto over = std::max(
  188. _overAnimation.value(_over ? 1. : 0.),
  189. barHighlight);
  190. const auto shown = std::max(
  191. _shownAnimation.value(_shown ? 1. : 0.),
  192. over);
  193. if (shown < 1. / 255) {
  194. return;
  195. }
  196. QPainter p(this);
  197. p.setPen(Qt::NoPen);
  198. auto bg = anim::color(_st.bg, _st.bgOver, over);
  199. bg.setAlpha(anim::interpolate(0, bg.alpha(), shown));
  200. auto bar = anim::color(_st.barBg, _st.barBgOver, barHighlight);
  201. bar.setAlpha(anim::interpolate(0, bar.alpha(), shown));
  202. const auto radius = (_st.round < 0)
  203. ? (std::min(_area.width(), _area.height()) / 2.)
  204. : _st.round;
  205. if (radius) {
  206. PainterHighQualityEnabler hq(p);
  207. p.setBrush(bg);
  208. p.drawRoundedRect(_area, radius, radius);
  209. p.setBrush(bar);
  210. p.drawRoundedRect(_bar, radius, radius);
  211. } else {
  212. p.fillRect(_area, bg);
  213. p.fillRect(_bar, bar);
  214. }
  215. }
  216. void ElasticScrollBar::enterEventHook(QEnterEvent *e) {
  217. _hideTimer.cancel();
  218. setMouseTracking(true);
  219. toggleOver(true);
  220. }
  221. void ElasticScrollBar::leaveEventHook(QEvent *e) {
  222. if (!_dragging) {
  223. setMouseTracking(false);
  224. }
  225. toggleOver(false);
  226. toggleOverBar(false);
  227. if (_st.hiding && _shown) {
  228. _hideTimer.callOnce(_st.hiding);
  229. }
  230. }
  231. int ElasticScrollBar::scaleToBar(int change) const {
  232. const auto scrollable = _state.fullSize
  233. - (_state.visibleTill - _state.visibleFrom);
  234. const auto outsideBar = (_vertical ? _area.height() : _area.width())
  235. - (_vertical ? _bar.height() : _bar.width());
  236. return (outsideBar <= 0 || scrollable <= outsideBar)
  237. ? change
  238. : (change * scrollable / outsideBar);
  239. }
  240. void ElasticScrollBar::mouseMoveEvent(QMouseEvent *e) {
  241. toggleOverBar(_bar.contains(e->pos()));
  242. if (_dragging && !_bar.isEmpty()) {
  243. const auto position = e->globalPos();
  244. const auto delta = position - _dragPosition;
  245. _dragPosition = position;
  246. if (auto change = scaleToBar(_vertical ? delta.y() : delta.x())) {
  247. if (base::OppositeSigns(_dragOverscrollAccumulated, change)) {
  248. const auto overscroll = (change < 0)
  249. ? std::max(_dragOverscrollAccumulated + change, 0)
  250. : std::min(_dragOverscrollAccumulated + change, 0);
  251. const auto delta = overscroll - _dragOverscrollAccumulated;
  252. _dragOverscrollAccumulated = overscroll;
  253. change -= delta;
  254. }
  255. if (change) {
  256. const auto now = std::clamp(
  257. _state.visibleFrom + change,
  258. std::min(_state.visibleFrom, 0),
  259. std::max(
  260. _state.visibleFrom,
  261. (_state.visibleFrom
  262. + (_state.fullSize - _state.visibleTill))));
  263. const auto delta = now - _state.visibleFrom;
  264. if (change != delta) {
  265. _dragOverscrollAccumulated
  266. = (base::OppositeSigns(
  267. _dragOverscrollAccumulated,
  268. change)
  269. ? change
  270. : (_dragOverscrollAccumulated + change));
  271. }
  272. _visibleFromDragged.fire_copy(now);
  273. }
  274. }
  275. }
  276. }
  277. void ElasticScrollBar::mousePressEvent(QMouseEvent *e) {
  278. if (_bar.isEmpty()) {
  279. return;
  280. }
  281. toggleDragging(true);
  282. _dragPosition = e->globalPos();
  283. _dragOverscrollAccumulated = 0;
  284. if (!_overBar) {
  285. const auto start = _vertical ? _area.y() : _area.x();
  286. const auto full = _vertical ? _area.height() : _area.width();
  287. const auto bar = _vertical ? _bar.height() : _bar.width();
  288. const auto half = bar / 2;
  289. const auto middle = std::clamp(
  290. _vertical ? e->pos().y() : e->pos().x(),
  291. start + half,
  292. start + full + half - bar);
  293. const auto range = _state.visibleFrom
  294. + (_state.fullSize - _state.visibleTill);
  295. const auto from = range * (middle - half - start) / (full - bar);
  296. _visibleFromDragged.fire_copy(from);
  297. }
  298. }
  299. void ElasticScrollBar::mouseReleaseEvent(QMouseEvent *e) {
  300. toggleDragging(false);
  301. if (!_over) {
  302. setMouseTracking(false);
  303. }
  304. }
  305. void ElasticScrollBar::resizeEvent(QResizeEvent *e) {
  306. refreshGeometry();
  307. }
  308. bool ElasticScrollBar::eventHook(QEvent *e) {
  309. setAttribute(Qt::WA_NoMousePropagation, e->type() != QEvent::Wheel);
  310. return RpWidget::eventHook(e);
  311. }
  312. ElasticScroll::ElasticScroll(
  313. QWidget *parent,
  314. const style::ScrollArea &st,
  315. Qt::Orientation orientation)
  316. : RpWidget(parent)
  317. , _st(st)
  318. , _bar(std::make_unique<ElasticScrollBar>(this, _st, orientation))
  319. , _touchTimer([=] { _touchRightButton = true; })
  320. , _touchScrollTimer([=] { touchScrollTimer(); })
  321. , _vertical(orientation == Qt::Vertical)
  322. , _position(Position{ 0, 0 })
  323. , _movement(Movement::None) {
  324. setAttribute(Qt::WA_AcceptTouchEvents);
  325. _bar->visibleFromDragged(
  326. ) | rpl::start_with_next([=](int from) {
  327. tryScrollTo(from, false);
  328. }, _bar->lifetime());
  329. }
  330. ElasticScroll::~ElasticScroll() {
  331. // Destroy the _bar cleanly (keeping _bar == nullptr) to avoid a crash:
  332. //
  333. // _bar destructor may send LeaveEvent to ElasticScroll,
  334. // which will try to toggle(false) the _bar in leaveEventHook.
  335. base::take(_bar);
  336. }
  337. void ElasticScroll::setHandleTouch(bool handle) {
  338. if (_touchDisabled != handle) {
  339. return;
  340. }
  341. _touchDisabled = !handle;
  342. constexpr auto attribute = Qt::WA_AcceptTouchEvents;
  343. setAttribute(attribute, handle);
  344. if (_widget) {
  345. if (handle) {
  346. _widgetAcceptsTouch = _widget->testAttribute(attribute);
  347. if (!_widgetAcceptsTouch) {
  348. _widget->setAttribute(attribute);
  349. }
  350. } else {
  351. if (!_widgetAcceptsTouch) {
  352. _widget->setAttribute(attribute, false);
  353. }
  354. }
  355. }
  356. }
  357. bool ElasticScroll::viewportEvent(QEvent *e) {
  358. const auto type = e->type();
  359. if (type == QEvent::Wheel) {
  360. return handleWheelEvent(static_cast<QWheelEvent*>(e));
  361. } else if (type == QEvent::TouchBegin
  362. || type == QEvent::TouchUpdate
  363. || type == QEvent::TouchEnd
  364. || type == QEvent::TouchCancel) {
  365. handleTouchEvent(static_cast<QTouchEvent*>(e));
  366. return true;
  367. }
  368. return false;
  369. }
  370. QWidget *ElasticScroll::viewport() const {
  371. return _widget;
  372. }
  373. void ElasticScroll::touchDeaccelerate(int32 elapsed) {
  374. int32 x = _touchSpeed.x();
  375. int32 y = _touchSpeed.y();
  376. _touchSpeed.setX((x == 0) ? x : (x > 0) ? qMax(0, x - elapsed) : qMin(0, x + elapsed));
  377. _touchSpeed.setY((y == 0) ? y : (y > 0) ? qMax(0, y - elapsed) : qMin(0, y + elapsed));
  378. }
  379. void ElasticScroll::overscrollReturn() {
  380. _overscrollReturning = true;
  381. _ignoreMomentumFromOverscroll = _overscroll;
  382. if (overscrollFinish()) {
  383. _overscrollReturnAnimation.stop();
  384. return;
  385. } else if (_overscrollReturnAnimation.animating()) {
  386. return;
  387. }
  388. _movement = Movement::Returning;
  389. _overscrollReturnAnimation.start(
  390. [=] { applyAccumulatedScroll(); },
  391. 0.,
  392. 1.,
  393. kOverscrollReturnDuration,
  394. anim::sineInOut);
  395. }
  396. auto ElasticScroll::computeAccumulatedParts() const ->AccumulatedParts {
  397. const auto baseAccumulated = currentOverscrollDefaultAccumulated();
  398. const auto returnProgress = _overscrollReturnAnimation.value(
  399. _overscrollReturning ? 1. : 0.);
  400. const auto relativeAccumulated = (1. - returnProgress)
  401. * (_overscrollAccumulated - baseAccumulated);
  402. return {
  403. .base = baseAccumulated,
  404. .relative = int(base::SafeRound(relativeAccumulated)),
  405. };
  406. }
  407. void ElasticScroll::overscrollReturnCancel() {
  408. _movement = Movement::Progress;
  409. if (_overscrollReturning) {
  410. const auto parts = computeAccumulatedParts();
  411. _overscrollAccumulated = parts.base + parts.relative;
  412. _overscrollReturnAnimation.stop();
  413. _overscrollReturning = false;
  414. applyAccumulatedScroll();
  415. }
  416. }
  417. int ElasticScroll::currentOverscrollDefault() const {
  418. return (_overscroll < 0)
  419. ? _overscrollDefaultFrom
  420. : (_overscroll > 0)
  421. ? _overscrollDefaultTill
  422. : 0;
  423. }
  424. int ElasticScroll::currentOverscrollDefaultAccumulated() const {
  425. return (_overscrollAccumulated < 0)
  426. ? (_overscrollDefaultFrom ? kOverscrollFromThreshold : 0)
  427. : (_overscrollAccumulated > 0)
  428. ? (_overscrollDefaultTill ? kOverscrollTillThreshold : 0)
  429. : 0;
  430. }
  431. void ElasticScroll::overscrollCheckReturnFinish() {
  432. if (!_overscrollReturning) {
  433. return;
  434. } else if (!_overscrollReturnAnimation.animating()) {
  435. _overscrollReturning = false;
  436. _overscrollAccumulated = currentOverscrollDefaultAccumulated();
  437. _movement = Movement::None;
  438. } else if (overscrollFinish()) {
  439. _overscrollReturnAnimation.stop();
  440. }
  441. }
  442. bool ElasticScroll::overscrollFinish() {
  443. if (_overscroll != currentOverscrollDefault()) {
  444. return false;
  445. }
  446. _overscrollReturning = false;
  447. _overscrollAccumulated = currentOverscrollDefaultAccumulated();
  448. _movement = Movement::None;
  449. return true;
  450. }
  451. void ElasticScroll::innerResized() {
  452. _innerResizes.fire({});
  453. }
  454. int ElasticScroll::scrollWidth() const {
  455. return (_vertical || !_widget)
  456. ? width()
  457. : std::max(_widget->width(), width());
  458. }
  459. int ElasticScroll::scrollHeight() const {
  460. return (!_vertical || !_widget)
  461. ? height()
  462. : std::max(_widget->height(), height());
  463. }
  464. int ElasticScroll::scrollLeftMax() const {
  465. return scrollWidth() - width();
  466. }
  467. int ElasticScroll::scrollTopMax() const {
  468. return scrollHeight() - height();
  469. }
  470. int ElasticScroll::scrollLeft() const {
  471. return _vertical ? 0 : _state.visibleFrom;
  472. }
  473. int ElasticScroll::scrollTop() const {
  474. return _vertical ? _state.visibleFrom : 0;
  475. }
  476. void ElasticScroll::touchScrollTimer() {
  477. auto nowTime = crl::now();
  478. if (_touchScrollState == TouchScrollState::Acceleration && _touchWaitingAcceleration && (nowTime - _touchAccelerationTime) > 40) {
  479. _touchScrollState = TouchScrollState::Manual;
  480. sendWheelEvent(Qt::ScrollEnd);
  481. touchResetSpeed();
  482. } else if (_touchScrollState == TouchScrollState::Auto || _touchScrollState == TouchScrollState::Acceleration) {
  483. int32 elapsed = int32(nowTime - _touchTime);
  484. QPoint delta = _touchSpeed * elapsed / 1000;
  485. sendWheelEvent(
  486. _touchPress ? Qt::ScrollUpdate : Qt::ScrollMomentum,
  487. delta);
  488. if (_touchSpeed.isNull()) {
  489. _touchScrollState = TouchScrollState::Manual;
  490. sendWheelEvent(Qt::ScrollEnd);
  491. _touchScroll = false;
  492. _touchScrollTimer.cancel();
  493. } else {
  494. _touchTime = nowTime;
  495. }
  496. touchDeaccelerate(elapsed);
  497. }
  498. }
  499. void ElasticScroll::touchUpdateSpeed() {
  500. const auto nowTime = crl::now();
  501. if (_touchPreviousPositionValid) {
  502. const int elapsed = nowTime - _touchSpeedTime;
  503. if (elapsed) {
  504. const QPoint newPixelDiff = (_touchPosition - _touchPreviousPosition);
  505. const QPoint pixelsPerSecond = newPixelDiff * (1000 / elapsed);
  506. // fingers are inacurates, we ignore small changes to avoid stopping the autoscroll because
  507. // of a small horizontal offset when scrolling vertically
  508. const int newSpeedY = (qAbs(pixelsPerSecond.y()) > kFingerAccuracyThreshold) ? pixelsPerSecond.y() : 0;
  509. const int newSpeedX = (qAbs(pixelsPerSecond.x()) > kFingerAccuracyThreshold) ? pixelsPerSecond.x() : 0;
  510. if (_touchScrollState == TouchScrollState::Auto) {
  511. const int oldSpeedY = _touchSpeed.y();
  512. const int oldSpeedX = _touchSpeed.x();
  513. if ((oldSpeedY <= 0 && newSpeedY <= 0) || ((oldSpeedY >= 0 && newSpeedY >= 0)
  514. && (oldSpeedX <= 0 && newSpeedX <= 0)) || (oldSpeedX >= 0 && newSpeedX >= 0)) {
  515. _touchSpeed.setY(std::clamp((oldSpeedY + (newSpeedY / 4)), -kMaxScrollAccelerated, +kMaxScrollAccelerated));
  516. _touchSpeed.setX(std::clamp((oldSpeedX + (newSpeedX / 4)), -kMaxScrollAccelerated, +kMaxScrollAccelerated));
  517. } else {
  518. _touchSpeed = QPoint();
  519. }
  520. } else {
  521. // we average the speed to avoid strange effects with the last delta
  522. if (!_touchSpeed.isNull()) {
  523. _touchSpeed.setX(std::clamp((_touchSpeed.x() / 4) + (newSpeedX * 3 / 4), -kMaxScrollFlick, +kMaxScrollFlick));
  524. _touchSpeed.setY(std::clamp((_touchSpeed.y() / 4) + (newSpeedY * 3 / 4), -kMaxScrollFlick, +kMaxScrollFlick));
  525. } else {
  526. _touchSpeed = QPoint(newSpeedX, newSpeedY);
  527. }
  528. }
  529. }
  530. } else {
  531. _touchPreviousPositionValid = true;
  532. }
  533. _touchSpeedTime = nowTime;
  534. _touchPreviousPosition = _touchPosition;
  535. }
  536. void ElasticScroll::touchResetSpeed() {
  537. _touchSpeed = QPoint();
  538. _touchPreviousPositionValid = false;
  539. }
  540. bool ElasticScroll::eventHook(QEvent *e) {
  541. if (filterOutTouchEvent(e)) {
  542. if (e->type() == QEvent::TouchCancel
  543. && !_touchDisabled
  544. && _widget
  545. && _widget->testAttribute(Qt::WA_AcceptTouchEvents)) {
  546. // If touch was cancelled for me, send the cancel
  547. // event to the widget as well.
  548. //
  549. // In case scroll owner handled touch himself using
  550. // _customTouchProcess hook and wants to cancel any
  551. // actions here, we want to cancel them in widget too.
  552. QTouchEvent ev(QEvent::TouchCancel);
  553. ev.setTimestamp(crl::now());
  554. QGuiApplication::sendEvent(_widget, &ev);
  555. }
  556. return true;
  557. }
  558. return RpWidget::eventHook(e);
  559. }
  560. void ElasticScroll::wheelEvent(QWheelEvent *e) {
  561. if (handleWheelEvent(e)) {
  562. e->accept();
  563. } else {
  564. e->ignore();
  565. }
  566. }
  567. void ElasticScroll::paintEvent(QPaintEvent *e) {
  568. if (!_overscrollBg) {
  569. return;
  570. }
  571. const auto fillFrom = std::max(-_state.visibleFrom, 0);
  572. const auto content = _widget
  573. ? (_vertical ? _widget->height() : _widget->width())
  574. : 0;
  575. const auto fillTill = content
  576. ? std::max(_state.visibleTill - content, 0)
  577. : (_vertical ? height() : width());
  578. if (!fillFrom && !fillTill) {
  579. return;
  580. }
  581. auto p = QPainter(this);
  582. if (fillFrom) {
  583. p.fillRect(
  584. 0,
  585. 0,
  586. _vertical ? width() : fillFrom,
  587. _vertical ? fillFrom : height(),
  588. *_overscrollBg);
  589. }
  590. if (fillTill) {
  591. p.fillRect(
  592. _vertical ? 0 : (width() - fillTill),
  593. _vertical ? (height() - fillTill) : 0,
  594. _vertical ? width() : fillTill,
  595. _vertical ? fillTill : height(),
  596. *_overscrollBg);
  597. }
  598. }
  599. bool ElasticScroll::handleWheelEvent(not_null<QWheelEvent*> e, bool touch) {
  600. if (_customWheelProcess
  601. && _customWheelProcess(static_cast<QWheelEvent*>(e.get()))) {
  602. return true;
  603. }
  604. const auto phase = e->phase();
  605. const auto momentum = (phase == Qt::ScrollMomentum)
  606. || (phase == Qt::ScrollEnd);
  607. const auto now = crl::now();
  608. const auto guard = gsl::finally([&] {
  609. _lastScroll = now;
  610. });
  611. const auto unmultiplied = ScrollDelta(e, touch);
  612. const auto multiply = e->modifiers()
  613. & (Qt::ControlModifier | Qt::ShiftModifier);
  614. const auto pixels = multiply
  615. ? QPoint(
  616. (unmultiplied.x() * std::max(width(), 120) / 120.),
  617. (unmultiplied.y() * std::max(height(), 120) / 120.))
  618. : unmultiplied;
  619. auto ignore = false;
  620. auto delta = _vertical ? -pixels.y() : pixels.x();
  621. if (std::abs(_vertical ? pixels.x() : pixels.y()) >= std::abs(delta)) {
  622. ignore = true;
  623. delta = 0;
  624. }
  625. if (_ignoreMomentumFromOverscroll) {
  626. if (!momentum) {
  627. _ignoreMomentumFromOverscroll = 0;
  628. } else if (!_overscrollReturnAnimation.animating()
  629. && !base::OppositeSigns(_ignoreMomentumFromOverscroll, delta)) {
  630. return true;
  631. }
  632. }
  633. if (phase == Qt::NoScrollPhase) {
  634. if (_overscroll == currentOverscrollDefault()) {
  635. tryScrollTo(_state.visibleFrom + delta);
  636. _movement = Movement::None;
  637. } else if (!_overscrollReturnAnimation.animating()) {
  638. overscrollReturn();
  639. }
  640. return true;
  641. }
  642. if (!momentum) {
  643. overscrollReturnCancel();
  644. } else if (_overscroll != currentOverscrollDefault()
  645. && !_overscrollReturnAnimation.animating()) {
  646. overscrollReturn();
  647. } else if (!_overscrollReturnAnimation.animating()) {
  648. _movement = (phase == Qt::ScrollEnd)
  649. ? Movement::None
  650. : Movement::Momentum;
  651. }
  652. if (!_overscroll) {
  653. const auto normalTo = willScrollTo(_state.visibleFrom + delta);
  654. delta -= normalTo - _state.visibleFrom;
  655. applyScrollTo(normalTo);
  656. }
  657. if (!delta) {
  658. return !ignore;
  659. }
  660. if (touch) {
  661. delta *= kTouchOverscrollMultiplier;
  662. }
  663. const auto accumulated = _overscrollAccumulated + delta;
  664. const auto type = (accumulated < 0)
  665. ? _overscrollTypeFrom
  666. : (accumulated > 0)
  667. ? _overscrollTypeTill
  668. : OverscrollType::None;
  669. if (type == OverscrollType::None
  670. || base::OppositeSigns(_overscrollAccumulated, accumulated)) {
  671. _overscrollAccumulated = 0;
  672. } else {
  673. _overscrollAccumulated = accumulated;
  674. }
  675. applyAccumulatedScroll();
  676. return true;
  677. }
  678. void ElasticScroll::applyAccumulatedScroll() {
  679. overscrollCheckReturnFinish();
  680. const auto parts = computeAccumulatedParts();
  681. const auto baseOverscroll = (_overscrollAccumulated < 0)
  682. ? _overscrollDefaultFrom
  683. : (_overscrollAccumulated > 0)
  684. ? _overscrollDefaultTill
  685. : 0;
  686. applyOverscroll(baseOverscroll
  687. + OverscrollFromAccumulated(parts.relative));
  688. }
  689. bool ElasticScroll::eventFilter(QObject *obj, QEvent *e) {
  690. const auto result = RpWidget::eventFilter(obj, e);
  691. if (obj == _widget.data()) {
  692. if (filterOutTouchEvent(e)) {
  693. return true;
  694. } else if (e->type() == QEvent::Resize) {
  695. const auto weak = Ui::MakeWeak(this);
  696. updateState();
  697. if (weak) {
  698. _innerResizes.fire({});
  699. }
  700. } else if (e->type() == QEvent::Move) {
  701. updateState();
  702. }
  703. return result;
  704. }
  705. return false;
  706. }
  707. bool ElasticScroll::filterOutTouchEvent(QEvent *e) {
  708. const auto type = e->type();
  709. if (type == QEvent::TouchBegin
  710. || type == QEvent::TouchUpdate
  711. || type == QEvent::TouchEnd
  712. || type == QEvent::TouchCancel) {
  713. const auto ev = static_cast<QTouchEvent*>(e);
  714. if ((ev->type() == QEvent::TouchCancel && !ev->device())
  715. || (ev->device()->type() == base::TouchDevice::TouchScreen)) {
  716. if (_customTouchProcess && _customTouchProcess(ev)) {
  717. return true;
  718. } else if (!_touchDisabled) {
  719. handleTouchEvent(ev);
  720. return true;
  721. }
  722. }
  723. }
  724. return false;
  725. }
  726. void ElasticScroll::handleTouchEvent(QTouchEvent *e) {
  727. if (!e->touchPoints().isEmpty()) {
  728. _touchPreviousPosition = _touchPosition;
  729. _touchPosition = e->touchPoints().cbegin()->screenPos().toPoint();
  730. }
  731. switch (e->type()) {
  732. case QEvent::TouchBegin: {
  733. if (_touchPress || e->touchPoints().isEmpty()) {
  734. return;
  735. }
  736. _touchPress = true;
  737. if (_touchScrollState == TouchScrollState::Auto) {
  738. _touchScrollState = TouchScrollState::Acceleration;
  739. _touchMaybePressing = false;
  740. _touchWaitingAcceleration = true;
  741. _touchAccelerationTime = crl::now();
  742. touchUpdateSpeed();
  743. _touchStart = _touchPosition;
  744. } else {
  745. _touchScroll = false;
  746. _touchMaybePressing = true;
  747. _touchTimer.callOnce(QApplication::startDragTime());
  748. }
  749. _touchStart = _touchPreviousPosition = _touchPosition;
  750. _touchRightButton = false;
  751. sendWheelEvent(Qt::ScrollBegin);
  752. } break;
  753. case QEvent::TouchUpdate: {
  754. if (!_touchPress) {
  755. return;
  756. }
  757. if (!_touchScroll
  758. && ((_touchPosition - _touchStart).manhattanLength()
  759. >= QApplication::startDragDistance())) {
  760. _touchTimer.cancel();
  761. _touchScroll = true;
  762. _touchMaybePressing = false;
  763. touchUpdateSpeed();
  764. }
  765. if (_touchScroll) {
  766. if (_touchScrollState == TouchScrollState::Manual) {
  767. touchScrollUpdated();
  768. } else if (_touchScrollState == TouchScrollState::Acceleration) {
  769. touchUpdateSpeed();
  770. _touchAccelerationTime = crl::now();
  771. if (_touchSpeed.isNull()) {
  772. _touchScrollState = TouchScrollState::Manual;
  773. }
  774. }
  775. }
  776. } break;
  777. case QEvent::TouchEnd: {
  778. if (!_touchPress) {
  779. return;
  780. }
  781. _touchPress = false;
  782. auto weak = MakeWeak(this);
  783. if (_touchScroll) {
  784. if (_touchScrollState == TouchScrollState::Manual) {
  785. _touchScrollState = TouchScrollState::Auto;
  786. _touchPreviousPositionValid = false;
  787. _touchScrollTimer.callEach(15);
  788. _touchTime = crl::now();
  789. } else if (_touchScrollState == TouchScrollState::Auto) {
  790. _touchScrollState = TouchScrollState::Manual;
  791. _touchScroll = false;
  792. touchResetSpeed();
  793. } else if (_touchScrollState == TouchScrollState::Acceleration) {
  794. _touchScrollState = TouchScrollState::Auto;
  795. _touchWaitingAcceleration = false;
  796. _touchPreviousPositionValid = false;
  797. }
  798. } else if (window()) { // one short tap -- like left mouse click, one long tap -- like right mouse click
  799. Qt::MouseButton btn(_touchRightButton ? Qt::RightButton : Qt::LeftButton);
  800. if (weak) SendSynteticMouseEvent(this, QEvent::MouseMove, Qt::NoButton, _touchStart);
  801. if (weak) SendSynteticMouseEvent(this, QEvent::MouseButtonPress, btn, _touchStart);
  802. if (weak) SendSynteticMouseEvent(this, QEvent::MouseButtonRelease, btn, _touchStart);
  803. if (weak && _touchRightButton) {
  804. auto windowHandle = window()->windowHandle();
  805. auto localPoint = windowHandle->mapFromGlobal(_touchStart);
  806. QContextMenuEvent ev(QContextMenuEvent::Mouse, localPoint, _touchStart, QGuiApplication::keyboardModifiers());
  807. ev.setTimestamp(crl::now());
  808. QGuiApplication::sendEvent(windowHandle, &ev);
  809. }
  810. }
  811. if (weak) {
  812. _touchTimer.cancel();
  813. _touchRightButton = false;
  814. _touchMaybePressing = false;
  815. }
  816. } break;
  817. case QEvent::TouchCancel: {
  818. _touchPress = false;
  819. _touchScroll = false;
  820. _touchMaybePressing = false;
  821. _touchScrollState = TouchScrollState::Manual;
  822. _touchTimer.cancel();
  823. } break;
  824. }
  825. }
  826. void ElasticScroll::touchScrollUpdated() {
  827. //touchScroll(_touchPosition - _touchPreviousPosition);
  828. const auto phase = !_touchPress
  829. ? Qt::ScrollMomentum
  830. : Qt::ScrollUpdate;
  831. sendWheelEvent(phase, _touchPosition - _touchPreviousPosition);
  832. touchUpdateSpeed();
  833. }
  834. void ElasticScroll::disableScroll(bool dis) {
  835. _disabled = dis;
  836. if (_disabled && _st.hiding) {
  837. _bar->toggle(false);
  838. }
  839. }
  840. void ElasticScroll::updateState() {
  841. _dirtyState = false;
  842. if (!_widget) {
  843. setState({});
  844. return;
  845. }
  846. auto from = _vertical ? -_widget->y() : -_widget->x();
  847. auto till = from + (_vertical ? height() : width());
  848. const auto wasFullSize = _state.fullSize;
  849. const auto nowFullSize = _vertical ? scrollHeight() : scrollWidth();
  850. if (wasFullSize > nowFullSize) {
  851. const auto wasOverscroll = std::max(
  852. _state.visibleTill - wasFullSize,
  853. 0);
  854. const auto nowOverscroll = std::max(till - nowFullSize, 0);
  855. const auto delta = std::max(
  856. std::min(nowOverscroll - wasOverscroll, from),
  857. 0);
  858. if (delta) {
  859. applyScrollTo(from - delta);
  860. return;
  861. }
  862. }
  863. setState({
  864. .visibleFrom = from,
  865. .visibleTill = till,
  866. .fullSize = nowFullSize,
  867. });
  868. }
  869. void ElasticScroll::setState(ScrollState state) {
  870. if (_overscroll < 0
  871. && (state.visibleFrom > 0
  872. || (!state.visibleFrom
  873. && _overscrollTypeFrom == OverscrollType::Real))) {
  874. _overscroll = _overscrollDefaultFrom = 0;
  875. overscrollFinish();
  876. _overscrollReturnAnimation.stop();
  877. } else if (_overscroll > 0
  878. && (state.visibleTill < state.fullSize
  879. || (state.visibleTill == state.fullSize
  880. && _overscrollTypeTill == OverscrollType::Real))) {
  881. _overscroll = _overscrollDefaultTill = 0;
  882. overscrollFinish();
  883. _overscrollReturnAnimation.stop();
  884. }
  885. if (_state == state) {
  886. _position = Position{ _state.visibleFrom, _overscroll };
  887. return;
  888. }
  889. const auto weak = Ui::MakeWeak(this);
  890. const auto old = _state.visibleFrom;
  891. _state = state;
  892. _bar->updateState(state);
  893. if (weak) {
  894. _position = Position{ _state.visibleFrom, _overscroll };
  895. }
  896. if (weak && _state.visibleFrom != old) {
  897. if (_vertical) {
  898. _scrollTopUpdated.fire_copy(_state.visibleFrom);
  899. }
  900. if (weak) {
  901. _scrolls.fire({});
  902. }
  903. }
  904. }
  905. void ElasticScroll::applyScrollTo(int position, bool synthMouseMove) {
  906. if (_disabled) {
  907. return;
  908. }
  909. const auto weak = Ui::MakeWeak(this);
  910. _dirtyState = true;
  911. const auto was = _widget->geometry();
  912. _widget->move(
  913. _vertical ? _widget->x() : -position,
  914. _vertical ? -position : _widget->y());
  915. if (weak) {
  916. const auto now = _widget->geometry();
  917. const auto wasFrom = _vertical ? was.y() : was.x();
  918. const auto wasTill = wasFrom
  919. + (_vertical ? was.height() : was.width());
  920. const auto nowFrom = _vertical ? now.y() : now.x();
  921. const auto nowTill = nowFrom
  922. + (_vertical ? now.height() : now.width());
  923. const auto mySize = _vertical ? height() : width();
  924. if ((wasFrom > 0 && wasFrom < mySize)
  925. || (wasTill > 0 && wasTill < mySize)
  926. || (nowFrom > 0 && nowFrom < mySize)
  927. || (nowTill > 0 && nowTill < mySize)) {
  928. update();
  929. }
  930. if (_dirtyState) {
  931. updateState();
  932. }
  933. if (weak && synthMouseMove) {
  934. SendSynteticMouseEvent(this, QEvent::MouseMove, Qt::NoButton);
  935. }
  936. }
  937. }
  938. void ElasticScroll::applyOverscroll(int overscroll) {
  939. if (_overscroll == overscroll) {
  940. return;
  941. }
  942. _overscroll = overscroll;
  943. const auto max = _state.fullSize
  944. - (_state.visibleTill - _state.visibleFrom);
  945. if (_overscroll > 0) {
  946. const auto added = (_overscrollTypeTill == OverscrollType::Real)
  947. ? _overscroll
  948. : 0;
  949. applyScrollTo(max + added);
  950. } else if (_overscroll < 0) {
  951. applyScrollTo((_overscrollTypeFrom == OverscrollType::Real)
  952. ? _overscroll
  953. : 0);
  954. } else {
  955. applyScrollTo(std::clamp(_state.visibleFrom, 0, max));
  956. }
  957. }
  958. int ElasticScroll::willScrollTo(int position) const {
  959. return std::clamp(
  960. position,
  961. std::min(_state.visibleFrom, 0),
  962. std::max(
  963. _state.visibleFrom,
  964. (_state.visibleFrom
  965. + (_state.fullSize - _state.visibleTill))));
  966. }
  967. void ElasticScroll::tryScrollTo(int position, bool synthMouseMove) {
  968. applyScrollTo(willScrollTo(position), synthMouseMove);
  969. }
  970. void ElasticScroll::sendWheelEvent(Qt::ScrollPhase phase, QPoint delta) {
  971. auto e = QWheelEvent(
  972. mapFromGlobal(_touchPosition),
  973. _touchPosition,
  974. delta,
  975. delta,
  976. Qt::NoButton,
  977. Qt::KeyboardModifiers(), // Ignore Ctrl/Shift fast scroll on touch.
  978. phase,
  979. false,
  980. Qt::MouseEventSynthesizedByApplication);
  981. handleWheelEvent(&e, true);
  982. }
  983. void ElasticScroll::resizeEvent(QResizeEvent *e) {
  984. const auto rtl = (layoutDirection() == Qt::RightToLeft);
  985. _bar->setGeometry(_vertical
  986. ? QRect(
  987. (rtl ? 0 : (width() - _st.width)),
  988. 0,
  989. _st.width,
  990. height())
  991. : QRect(0, height() - _st.width, width(), _st.width));
  992. _geometryChanged.fire({});
  993. updateState();
  994. }
  995. void ElasticScroll::moveEvent(QMoveEvent *e) {
  996. _geometryChanged.fire({});
  997. }
  998. void ElasticScroll::keyPressEvent(QKeyEvent *e) {
  999. const auto key = e->key();
  1000. if (!_widget) {
  1001. e->ignore();
  1002. return;
  1003. } else if ((key == Qt::Key_Up || key == Qt::Key_Down)
  1004. && (e->modifiers().testFlag(Qt::AltModifier)
  1005. || e->modifiers().testFlag(Qt::ControlModifier))) {
  1006. e->ignore();
  1007. } else if (_widget && (key == Qt::Key_Escape || key == Qt::Key_Back)) {
  1008. ((QObject*)_widget.data())->event(e);
  1009. } else if (key == Qt::Key_Up
  1010. || key == Qt::Key_Down
  1011. || key == Qt::Key_PageUp
  1012. || key == Qt::Key_PageDown) {
  1013. const auto up = (key == Qt::Key_Up) || (key == Qt::Key_PageUp);
  1014. const auto step = (key == Qt::Key_Up || key == Qt::Key_Down)
  1015. ? style::ConvertScale(20)
  1016. : height();
  1017. tryScrollTo(_state.visibleFrom + (up ? -step : step));
  1018. }
  1019. }
  1020. void ElasticScroll::enterEventHook(QEnterEvent *e) {
  1021. if (_bar && !_disabled) {
  1022. _bar->toggle(true);
  1023. }
  1024. }
  1025. void ElasticScroll::leaveEventHook(QEvent *e) {
  1026. if (_bar) {
  1027. _bar->toggle(false);
  1028. }
  1029. }
  1030. void ElasticScroll::scrollTo(ScrollToRequest request) {
  1031. scrollToY(request.ymin, request.ymax);
  1032. }
  1033. void ElasticScroll::scrollToWidget(not_null<QWidget*> widget) {
  1034. if (const auto local = _widget.data()) {
  1035. const auto position = Ui::MapFrom(
  1036. local,
  1037. widget.get(),
  1038. QPoint(0, 0));
  1039. const auto from = _vertical ? position.y() : position.x();
  1040. const auto till = _vertical
  1041. ? (position.y() + widget->height())
  1042. : (position.x() + widget->width());
  1043. scrollToY(from, till);
  1044. }
  1045. }
  1046. void ElasticScroll::scrollToY(int toTop, int toBottom) {
  1047. if (_vertical) {
  1048. scrollTo(toTop, toBottom);
  1049. }
  1050. }
  1051. void ElasticScroll::scrollTo(int toFrom, int toTill) {
  1052. if (const auto inner = _widget.data()) {
  1053. SendPendingMoveResizeEvents(inner);
  1054. }
  1055. SendPendingMoveResizeEvents(this);
  1056. int toMin = std::min(_state.visibleFrom, 0);
  1057. int toMax = std::max(
  1058. _state.visibleFrom,
  1059. _state.visibleFrom + _state.fullSize - _state.visibleTill);
  1060. if (toFrom < toMin) {
  1061. toFrom = toMin;
  1062. } else if (toFrom > toMax) {
  1063. toFrom = toMax;
  1064. }
  1065. bool exact = (toTill < 0);
  1066. int curFrom = _state.visibleFrom, curRange = _state.visibleTill - _state.visibleFrom, curTill = curFrom + curRange, scTo = toFrom;
  1067. if (!exact && toFrom >= curFrom) {
  1068. if (toTill < toFrom) toTill = toFrom;
  1069. if (toTill <= curTill) return;
  1070. scTo = toTill - curRange;
  1071. if (scTo > toFrom) scTo = toFrom;
  1072. if (scTo == curFrom) return;
  1073. } else {
  1074. scTo = toFrom;
  1075. }
  1076. applyScrollTo(scTo);
  1077. }
  1078. void ElasticScroll::doSetOwnedWidget(object_ptr<QWidget> w) {
  1079. constexpr auto attribute = Qt::WA_AcceptTouchEvents;
  1080. if (_widget) {
  1081. _widget->removeEventFilter(this);
  1082. if (!_touchDisabled && !_widgetAcceptsTouch) {
  1083. _widget->setAttribute(attribute, false);
  1084. }
  1085. }
  1086. _widget = std::move(w);
  1087. if (_widget) {
  1088. if (_widget->parentWidget() != this) {
  1089. _widget->setParent(this);
  1090. _widget->show();
  1091. }
  1092. _bar->raise();
  1093. _widget->installEventFilter(this);
  1094. if (!_touchDisabled) {
  1095. _widgetAcceptsTouch = _widget->testAttribute(attribute);
  1096. if (!_widgetAcceptsTouch) {
  1097. _widget->setAttribute(attribute);
  1098. }
  1099. }
  1100. updateState();
  1101. }
  1102. }
  1103. object_ptr<QWidget> ElasticScroll::doTakeWidget() {
  1104. return std::move(_widget);
  1105. }
  1106. void ElasticScroll::updateBars() {
  1107. _bar->update();
  1108. }
  1109. void ElasticScroll::setOverscrollTypes(
  1110. OverscrollType from,
  1111. OverscrollType till) {
  1112. const auto fromChanged = (_overscroll < 0)
  1113. && (_overscrollTypeFrom != from);
  1114. const auto tillChanged = (_overscroll > 0)
  1115. && (_overscrollTypeTill != till);
  1116. _overscrollTypeFrom = from;
  1117. _overscrollTypeTill = till;
  1118. if (fromChanged) {
  1119. switch (_overscrollTypeFrom) {
  1120. case OverscrollType::None:
  1121. _overscroll = _overscrollAccumulated = 0;
  1122. applyScrollTo(0);
  1123. break;
  1124. case OverscrollType::Virtual:
  1125. applyScrollTo(0);
  1126. break;
  1127. case OverscrollType::Real:
  1128. applyScrollTo(_overscroll);
  1129. break;
  1130. }
  1131. } else if (tillChanged) {
  1132. const auto max = _state.fullSize
  1133. - (_state.visibleTill - _state.visibleFrom);
  1134. switch (_overscrollTypeTill) {
  1135. case OverscrollType::None:
  1136. _overscroll = _overscrollAccumulated = 0;
  1137. applyScrollTo(max);
  1138. break;
  1139. case OverscrollType::Virtual:
  1140. applyScrollTo(max);
  1141. break;
  1142. case OverscrollType::Real:
  1143. applyScrollTo(max + _overscroll);
  1144. break;
  1145. }
  1146. }
  1147. }
  1148. void ElasticScroll::setOverscrollDefaults(int from, int till, bool shift) {
  1149. Expects(from <= 0 && till >= 0);
  1150. if (_state.visibleFrom > 0
  1151. || (!_state.visibleFrom
  1152. && _overscrollTypeFrom != OverscrollType::Virtual)) {
  1153. from = 0;
  1154. }
  1155. if (_state.visibleTill < _state.fullSize
  1156. || (_state.visibleTill == _state.fullSize
  1157. && _overscrollTypeTill != OverscrollType::Virtual)) {
  1158. till = 0;
  1159. }
  1160. const auto fromChanged = (_overscrollDefaultFrom != from);
  1161. const auto tillChanged = (_overscrollDefaultTill != till);
  1162. const auto changed = (fromChanged && _overscroll < 0)
  1163. || (tillChanged && _overscroll > 0);
  1164. const auto movement = _movement.current();
  1165. if (_overscrollReturnAnimation.animating()) {
  1166. overscrollReturnCancel();
  1167. }
  1168. _overscrollDefaultFrom = from;
  1169. _overscrollDefaultTill = till;
  1170. if (changed) {
  1171. const auto delta = (_overscroll < 0)
  1172. ? (_overscroll - (shift ? 0 : _overscrollDefaultFrom))
  1173. : (_overscroll - (shift ? 0 : _overscrollDefaultTill));
  1174. _overscrollAccumulated = currentOverscrollDefaultAccumulated()
  1175. + OverscrollToAccumulated(delta);
  1176. }
  1177. if (movement == Movement::Momentum || movement == Movement::Returning) {
  1178. if (_overscroll != currentOverscrollDefault()) {
  1179. overscrollReturn();
  1180. }
  1181. }
  1182. }
  1183. void ElasticScroll::setOverscrollBg(QColor bg) {
  1184. _overscrollBg = bg;
  1185. update();
  1186. }
  1187. rpl::producer<> ElasticScroll::scrolls() const {
  1188. return _scrolls.events();
  1189. }
  1190. rpl::producer<> ElasticScroll::innerResizes() const {
  1191. return _innerResizes.events();
  1192. }
  1193. rpl::producer<> ElasticScroll::geometryChanged() const {
  1194. return _geometryChanged.events();
  1195. }
  1196. ElasticScrollPosition ElasticScroll::position() const {
  1197. return _position.current();
  1198. }
  1199. rpl::producer<ElasticScrollPosition> ElasticScroll::positionValue() const {
  1200. return _position.value();
  1201. }
  1202. ElasticScrollMovement ElasticScroll::movement() const {
  1203. return _movement.current();
  1204. }
  1205. rpl::producer<ElasticScrollMovement> ElasticScroll::movementValue() const {
  1206. return _movement.value();
  1207. }
  1208. rpl::producer<bool> ElasticScroll::touchMaybePressing() const {
  1209. return _touchMaybePressing.value();
  1210. }
  1211. int OverscrollFromAccumulated(int accumulated) {
  1212. if (!accumulated) {
  1213. return 0;
  1214. }
  1215. return (accumulated > 0 ? 1. : -1.)
  1216. * int(base::SafeRound(RawFrom(std::abs(accumulated))));
  1217. }
  1218. int OverscrollToAccumulated(int overscroll) {
  1219. if (!overscroll) {
  1220. return 0;
  1221. }
  1222. return (overscroll > 0 ? 1. : -1.)
  1223. * int(base::SafeRound(RawTo(std::abs(overscroll))));
  1224. }
  1225. } // namespace Ui