mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-02-25 10:01:00 +00:00
Implement double-drumroll time picker.
This commit is contained in:
parent
bef26cf9d2
commit
4975cf2ec1
@ -571,7 +571,7 @@ bool AttachWebView::botHandleLocalUri(QString uri, bool keepOpen) {
|
||||
Core::App().domain().activate(&bot->session().account());
|
||||
}
|
||||
const auto window = !bot->session().windows().empty()
|
||||
? bot->session().windows().front()
|
||||
? bot->session().windows().front().get()
|
||||
: nullptr;
|
||||
const auto variant = QVariant::fromValue(ClickHandlerContext{
|
||||
.attachBotWebviewUrl = shownUrl,
|
||||
|
@ -86,6 +86,34 @@ private:
|
||||
: wrap(time == kDay ? 0 : time);
|
||||
}
|
||||
|
||||
[[nodiscard]] QString FormatTimeHour(TimeId time) {
|
||||
const auto wrap = [](TimeId value) {
|
||||
return QString::number(value / 3600).rightJustified(2, u'0');
|
||||
};
|
||||
if (time < kDay) {
|
||||
return wrap(time);
|
||||
}
|
||||
const auto wrapped = wrap(time - kDay);
|
||||
const auto result = tr::lng_hours_next_day(tr::now, lt_time, wrapped);
|
||||
const auto i = result.indexOf(wrapped);
|
||||
return (i >= 0) ? (result.left(i) + wrapped) : result;
|
||||
}
|
||||
|
||||
[[nodiscard]] QString FormatTimeMinute(TimeId time) {
|
||||
const auto wrap = [](TimeId value) {
|
||||
return QString::number(value / 60).rightJustified(2, u'0');
|
||||
};
|
||||
if (time < kDay) {
|
||||
return wrap(time);
|
||||
}
|
||||
const auto wrapped = wrap(time - kDay);
|
||||
const auto result = tr::lng_hours_next_day(tr::now, lt_time, wrapped);
|
||||
const auto i = result.indexOf(wrapped);
|
||||
return (i >= 0)
|
||||
? (wrapped + result.right(result.size() - i - wrapped.size()))
|
||||
: result;
|
||||
}
|
||||
|
||||
[[nodiscard]] QString JoinIntervals(const Data::WorkingIntervals &data) {
|
||||
auto result = QStringList();
|
||||
result.reserve(data.list.size());
|
||||
@ -105,49 +133,97 @@ void EditTimeBox(
|
||||
Fn<void(TimeId)> save) {
|
||||
Expects(low <= high);
|
||||
|
||||
const auto values = (high - low + 60) / 60;
|
||||
const auto startIndex = (value - low) / 60;
|
||||
|
||||
const auto content = box->addRow(object_ptr<Ui::FixedHeightWidget>(
|
||||
box,
|
||||
st::settingsWorkingHoursPicker));
|
||||
|
||||
const auto font = st::boxTextFont;
|
||||
const auto itemHeight = st::settingsWorkingHoursPickerItemHeight;
|
||||
auto paintCallback = [=](
|
||||
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());
|
||||
constexpr auto kMinYScale = 0.2;
|
||||
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);
|
||||
p.drawText(r, FormatDayTime(low + index * 60, true), style::al_center);
|
||||
p.restore();
|
||||
const auto picker = [=](
|
||||
int count,
|
||||
int startIndex,
|
||||
Fn<void(QPainter &p, QRectF rect, int index)> paint) {
|
||||
auto paintCallback = [=](
|
||||
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());
|
||||
constexpr auto kMinYScale = 0.2;
|
||||
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);
|
||||
paint(p, r, index);
|
||||
p.restore();
|
||||
};
|
||||
return Ui::CreateChild<Ui::VerticalDrumPicker>(
|
||||
content,
|
||||
std::move(paintCallback),
|
||||
count,
|
||||
itemHeight,
|
||||
startIndex);
|
||||
};
|
||||
|
||||
const auto picker = Ui::CreateChild<Ui::VerticalDrumPicker>(
|
||||
content,
|
||||
std::move(paintCallback),
|
||||
values,
|
||||
itemHeight,
|
||||
startIndex);
|
||||
const auto hoursCount = (high - low + 3600) / 3600;
|
||||
const auto hoursStartIndex = (value - low) / 3600;
|
||||
const auto hoursPaint = [=](QPainter &p, QRectF rect, int index) {
|
||||
p.drawText(
|
||||
rect,
|
||||
FormatTimeHour(((low / 3600) + index) * 3600),
|
||||
style::al_right);
|
||||
};
|
||||
const auto hours = picker(hoursCount, hoursStartIndex, hoursPaint);
|
||||
const auto minutes = content->lifetime().make_state<
|
||||
rpl::variable<Ui::VerticalDrumPicker*>
|
||||
>(nullptr);
|
||||
const auto minutesStart = content->lifetime().make_state<TimeId>();
|
||||
hours->value() | rpl::start_with_next([=](int hoursIndex) {
|
||||
const auto start = std::max(low, (hoursIndex + (low / 3600)) * 3600);
|
||||
const auto end = std::min(high, ((start / 3600) * 60 + 59) * 60);
|
||||
const auto minutesCount = (end - start + 60) / 60;
|
||||
const auto minutesStartIndex = minutes->current()
|
||||
? std::clamp(
|
||||
((((*minutesStart) / 60 + minutes->current()->index()) % 60)
|
||||
- ((start / 60) % 60)),
|
||||
0,
|
||||
(minutesCount - 1))
|
||||
: std::clamp((value - start) / 60, 0, minutesCount - 1);
|
||||
*minutesStart = start;
|
||||
|
||||
content->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &s) {
|
||||
picker->resize(s.width(), s.height());
|
||||
picker->moveToLeft((s.width() - picker->width()) / 2, 0);
|
||||
const auto minutesPaint = [=](QPainter &p, QRectF rect, int index) {
|
||||
p.drawText(
|
||||
rect,
|
||||
FormatTimeMinute((((start / 60) + index) % 60) * 60),
|
||||
style::al_left);
|
||||
};
|
||||
const auto updated = picker(
|
||||
minutesCount,
|
||||
minutesStartIndex,
|
||||
minutesPaint);
|
||||
delete minutes->current();
|
||||
*minutes = updated;
|
||||
minutes->current()->show();
|
||||
}, hours->lifetime());
|
||||
|
||||
const auto separator = u":"_q;
|
||||
const auto separatorWidth = st::boxTextFont->width(separator);
|
||||
|
||||
rpl::combine(
|
||||
content->sizeValue(),
|
||||
minutes->value()
|
||||
) | rpl::start_with_next([=](QSize s, Ui::VerticalDrumPicker *minutes) {
|
||||
const auto half = (s.width() - separatorWidth) / 2;
|
||||
hours->setGeometry(0, 0, half, s.height());
|
||||
minutes->setGeometry(half + separatorWidth, 0, half, s.height());
|
||||
}, content->lifetime());
|
||||
|
||||
content->paintRequest(
|
||||
@ -163,28 +239,22 @@ void EditTimeBox(
|
||||
st::defaultInputField.borderActive);
|
||||
p.fillRect(lineRect.translated(0, itemHeight / 2), st::activeLineFg);
|
||||
p.fillRect(lineRect.translated(0, -itemHeight / 2), st::activeLineFg);
|
||||
p.drawText(QRectF(content->rect()), separator, style::al_center);
|
||||
}, content->lifetime());
|
||||
|
||||
base::install_event_filter(content, [=](not_null<QEvent*> e) {
|
||||
if ((e->type() == QEvent::MouseButtonPress)
|
||||
|| (e->type() == QEvent::MouseButtonRelease)
|
||||
|| (e->type() == QEvent::MouseMove)) {
|
||||
picker->handleMouseEvent(static_cast<QMouseEvent*>(e.get()));
|
||||
} else if (e->type() == QEvent::Wheel) {
|
||||
picker->handleWheelEvent(static_cast<QWheelEvent*>(e.get()));
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
base::install_event_filter(box, [=](not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::KeyPress) {
|
||||
picker->handleKeyEvent(static_cast<QKeyEvent*>(e.get()));
|
||||
hours->handleKeyEvent(static_cast<QKeyEvent*>(e.get()));
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
|
||||
box->addButton(tr::lng_settings_save(), [=] {
|
||||
const auto weak = Ui::MakeWeak(box);
|
||||
save(std::clamp(low + picker->index() * 60, low, high));
|
||||
save(std::clamp(
|
||||
((*minutesStart) / 60 + minutes->current()->index()) * 60,
|
||||
low,
|
||||
high));
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
|
@ -92,6 +92,8 @@ VerticalDrumPicker::VerticalDrumPicker(
|
||||
_loopData.minIndex = -_itemsVisible.centerOffset;
|
||||
_loopData.maxIndex = _itemsCount - 1 - _itemsVisible.centerOffset;
|
||||
}
|
||||
|
||||
_changes.fire({});
|
||||
}, lifetime());
|
||||
|
||||
paintRequest(
|
||||
@ -144,7 +146,9 @@ void VerticalDrumPicker::increaseShift(float64 by) {
|
||||
index++;
|
||||
index = normalizedIndex(index);
|
||||
}
|
||||
if (!_loopData.looped && (index <= _loopData.minIndex)) {
|
||||
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)) {
|
||||
@ -154,6 +158,7 @@ void VerticalDrumPicker::increaseShift(float64 by) {
|
||||
_shift = shift;
|
||||
_index = index;
|
||||
}
|
||||
_changes.fire({});
|
||||
update();
|
||||
}
|
||||
|
||||
@ -270,4 +275,14 @@ 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
|
||||
|
@ -52,6 +52,8 @@ public:
|
||||
bool looped = false);
|
||||
|
||||
[[nodiscard]] int index() const;
|
||||
[[nodiscard]] rpl::producer<int> changes() const;
|
||||
[[nodiscard]] rpl::producer<int> value() const;
|
||||
|
||||
void handleWheelEvent(not_null<QWheelEvent*> e);
|
||||
void handleMouseEvent(not_null<QMouseEvent*> e);
|
||||
@ -84,6 +86,7 @@ private:
|
||||
|
||||
int _index = 0;
|
||||
float64 _shift = 0.;
|
||||
rpl::event_stream<> _changes;
|
||||
|
||||
struct {
|
||||
const bool looped;
|
||||
|
Loading…
Reference in New Issue
Block a user