| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- /*
- 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/vertical_drum_picker.h"
- #include "ui/effects/animation_value_f.h"
- #include "ui/ui_utility.h"
- #include "styles/style_basic.h"
- namespace Ui {
- namespace {
- constexpr auto kAlmostIndex = float64(.99);
- } // namespace
- PickerAnimation::PickerAnimation() = default;
- void PickerAnimation::jumpToOffset(int offset) {
- _result.from = _result.current;
- _result.to += offset;
- _animation.stop();
- auto callback = [=](float64 value) {
- const auto was = _result.current;
- _result.current = anim::interpolateF(
- _result.from,
- _result.to,
- value);
- _updates.fire(_result.current - was);
- };
- if (anim::Disabled()) {
- auto value = float64(0.);
- const auto diff = _result.to - _result.from;
- const auto step = std::min(
- kAlmostIndex,
- 1. / (std::max(1. - kAlmostIndex, std::abs(diff) + 1)));
- while (true) {
- value += step;
- if (value >= 1.) {
- callback(1.);
- break;
- } else {
- callback(value);
- }
- }
- return;
- }
- _animation.start(
- std::move(callback),
- 0.,
- 1.,
- st::fadeWrapDuration);
- }
- void PickerAnimation::setResult(float64 from, float64 current, float64 to) {
- _result = { from, current, to };
- }
- rpl::producer<PickerAnimation::Shift> PickerAnimation::updates() const {
- return _updates.events();
- }
- VerticalDrumPicker::VerticalDrumPicker(
- not_null<Ui::RpWidget*> parent,
- PaintItemCallback &&paintCallback,
- int itemsCount,
- int itemHeight,
- int startIndex,
- bool looped)
- : RpWidget(parent)
- , _itemsCount(itemsCount)
- , _itemHeight(itemHeight)
- , _paintCallback(std::move(paintCallback))
- , _pendingStartIndex(startIndex)
- , _loopData({ .looped = looped }) {
- Expects(_paintCallback != nullptr);
- sizeValue(
- ) | rpl::start_with_next([=](const QSize &s) {
- _itemsVisible.count = std::ceil(float64(s.height()) / _itemHeight);
- _itemsVisible.centerOffset = _itemsVisible.count / 2;
- if ((_pendingStartIndex >= 0) && _itemsVisible.count) {
- _index = normalizedIndex(_pendingStartIndex
- - _itemsVisible.centerOffset);
- _pendingStartIndex = -1;
- }
- if (!_loopData.looped) {
- _loopData.minIndex = -_itemsVisible.centerOffset;
- _loopData.maxIndex = _itemsCount - 1 - _itemsVisible.centerOffset;
- }
- _changes.fire({});
- }, lifetime());
- paintRequest(
- ) | rpl::start_with_next([=] {
- auto p = QPainter(this);
- const auto outerWidth = width();
- const auto centerY = height() / 2.;
- const auto shiftedY = _itemHeight * _shift;
- for (auto i = -1; i < (_itemsVisible.count + 1); i++) {
- const auto index = normalizedIndex(i + _index);
- if (!isIndexInRange(index)) {
- continue;
- }
- const auto y = (_itemHeight * i + shiftedY);
- _paintCallback(
- p,
- index,
- y,
- ((y + _itemHeight / 2.) - centerY) / centerY,
- outerWidth);
- }
- }, lifetime());
- _animation.updates(
- ) | rpl::start_with_next([=](PickerAnimation::Shift shift) {
- increaseShift(shift);
- }, lifetime());
- }
- void VerticalDrumPicker::increaseShift(float64 by) {
- {
- // Guard input.
- if (by >= 1.) {
- by = kAlmostIndex;
- } else if (by <= -1.) {
- by = -kAlmostIndex;
- }
- }
- auto shift = _shift;
- auto index = _index;
- shift += by;
- if (shift >= 1.) {
- shift -= 1.;
- index--;
- index = normalizedIndex(index);
- } else if (shift <= -1.) {
- shift += 1.;
- index++;
- index = normalizedIndex(index);
- }
- if (_loopData.minIndex == _loopData.maxIndex) {
- _shift = 0.;
- } else if (!_loopData.looped && (index <= _loopData.minIndex)) {
- _shift = std::min(0., shift);
- _index = _loopData.minIndex;
- } else if (!_loopData.looped && (index >= _loopData.maxIndex)) {
- _shift = std::max(0., shift);
- _index = _loopData.maxIndex;
- } else {
- _shift = shift;
- _index = index;
- }
- _changes.fire({});
- update();
- }
- void VerticalDrumPicker::handleWheelEvent(not_null<QWheelEvent*> e) {
- const auto direction = Ui::WheelDirection(e);
- if (direction) {
- _animation.jumpToOffset(direction);
- } else {
- if (const auto delta = e->pixelDelta().y(); delta) {
- increaseShift(delta / float64(_itemHeight));
- } else if (e->phase() == Qt::ScrollEnd) {
- animationDataFromIndex();
- _animation.jumpToOffset(0);
- } else {
- constexpr auto step = int(QWheelEvent::DefaultDeltasPerStep);
- _touch.verticalDelta += e->angleDelta().y();
- while (std::abs(_touch.verticalDelta) >= step) {
- if (_touch.verticalDelta < 0) {
- _touch.verticalDelta += step;
- _animation.jumpToOffset(1);
- } else {
- _touch.verticalDelta -= step;
- _animation.jumpToOffset(-1);
- }
- }
- }
- }
- }
- void VerticalDrumPicker::handleKeyEvent(not_null<QKeyEvent*> e) {
- if (e->key() == Qt::Key_Left || e->key() == Qt::Key_Up) {
- _animation.jumpToOffset(1);
- } else if (e->key() == Qt::Key_PageUp && !e->isAutoRepeat()) {
- _animation.jumpToOffset(_itemsVisible.count);
- } else if (e->key() == Qt::Key_Right || e->key() == Qt::Key_Down) {
- _animation.jumpToOffset(-1);
- } else if (e->key() == Qt::Key_PageDown && !e->isAutoRepeat()) {
- _animation.jumpToOffset(-_itemsVisible.count);
- }
- }
- void VerticalDrumPicker::handleMouseEvent(not_null<QMouseEvent*> e) {
- if (e->type() == QEvent::MouseButtonPress) {
- _mouse.pressed = true;
- _mouse.lastPositionY = e->pos().y();
- } else if (e->type() == QEvent::MouseMove) {
- if (_mouse.pressed) {
- const auto was = _mouse.lastPositionY;
- _mouse.lastPositionY = e->pos().y();
- const auto diff = _mouse.lastPositionY - was;
- increaseShift(float64(diff) / _itemHeight);
- _mouse.clickDisabled = true;
- }
- } else if (e->type() == QEvent::MouseButtonRelease) {
- if (_mouse.clickDisabled) {
- animationDataFromIndex();
- _animation.jumpToOffset(0);
- } else {
- _mouse.lastPositionY = e->pos().y();
- const auto toOffset = _itemsVisible.centerOffset
- - (_mouse.lastPositionY / _itemHeight);
- _animation.jumpToOffset(toOffset);
- }
- _mouse = {};
- }
- }
- void VerticalDrumPicker::wheelEvent(QWheelEvent *e) {
- handleWheelEvent(e);
- }
- void VerticalDrumPicker::mousePressEvent(QMouseEvent *e) {
- handleMouseEvent(e);
- }
- void VerticalDrumPicker::mouseMoveEvent(QMouseEvent *e) {
- handleMouseEvent(e);
- }
- void VerticalDrumPicker::mouseReleaseEvent(QMouseEvent *e) {
- handleMouseEvent(e);
- }
- void VerticalDrumPicker::keyPressEvent(QKeyEvent *e) {
- handleKeyEvent(e);
- }
- void VerticalDrumPicker::animationDataFromIndex() {
- _animation.setResult(
- _index,
- _index + _shift,
- std::round(_index + _shift));
- }
- bool VerticalDrumPicker::isIndexInRange(int index) const {
- return (index >= 0) && (index < _itemsCount);
- }
- int VerticalDrumPicker::normalizedIndex(int index) const {
- if (!_loopData.looped) {
- return index;
- }
- if (index < 0) {
- index += _itemsCount;
- } else if (index >= _itemsCount) {
- index -= _itemsCount;
- }
- return index;
- }
- int VerticalDrumPicker::index() const {
- return normalizedIndex(_index + _itemsVisible.centerOffset);
- }
- rpl::producer<int> VerticalDrumPicker::changes() const {
- return _changes.events() | rpl::map([=] { return index(); });
- }
- rpl::producer<int> VerticalDrumPicker::value() const {
- return rpl::single(index())
- | rpl::then(changes())
- | rpl::distinct_until_changed();
- }
- } // namespace Ui
|