init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Close stale issues and PRs / stale (push) Successful in 13s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Close stale issues and PRs / stale (push) Successful in 13s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
This commit is contained in:
320
Telegram/SourceFiles/ui/widgets/vertical_drum_picker.cpp
Normal file
320
Telegram/SourceFiles/ui/widgets/vertical_drum_picker.cpp
Normal file
@@ -0,0 +1,320 @@
|
||||
/*
|
||||
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"
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
constexpr auto kAlmostIndex = float64(.99);
|
||||
constexpr auto kMinYScale = 0.2;
|
||||
|
||||
using PaintItemCallback = VerticalDrumPicker::PaintItemCallback;
|
||||
|
||||
} // namespace
|
||||
|
||||
PaintItemCallback VerticalDrumPicker::DefaultPaintCallback(
|
||||
const style::font &font,
|
||||
int itemHeight,
|
||||
Fn<void(QPainter&, QRectF, int)> paintContent) {
|
||||
return [=](
|
||||
QPainter &p,
|
||||
int index,
|
||||
float64 y,
|
||||
float64 distanceFromCenter,
|
||||
int outerWidth) {
|
||||
const auto r = QRectF(0, y, outerWidth, itemHeight);
|
||||
const auto progress = std::abs(distanceFromCenter);
|
||||
const auto revProgress = 1. - progress;
|
||||
p.save();
|
||||
p.translate(r.center());
|
||||
const auto yScale = kMinYScale
|
||||
+ (1. - kMinYScale) * anim::easeOutCubic(1., revProgress);
|
||||
p.scale(1., yScale);
|
||||
p.translate(-r.center());
|
||||
p.setOpacity(revProgress);
|
||||
p.setFont(font);
|
||||
p.setPen(st::defaultFlatLabel.textFg);
|
||||
paintContent(p, r, index);
|
||||
p.restore();
|
||||
};
|
||||
}
|
||||
|
||||
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::on_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::on_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::on_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
|
||||
Reference in New Issue
Block a user