mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 22:54:01 +02:00
Implement double-drumroll time picker.
This commit is contained in:
parent
bef26cf9d2
commit
4975cf2ec1
4 changed files with 136 additions and 48 deletions
|
@ -571,7 +571,7 @@ bool AttachWebView::botHandleLocalUri(QString uri, bool keepOpen) {
|
||||||
Core::App().domain().activate(&bot->session().account());
|
Core::App().domain().activate(&bot->session().account());
|
||||||
}
|
}
|
||||||
const auto window = !bot->session().windows().empty()
|
const auto window = !bot->session().windows().empty()
|
||||||
? bot->session().windows().front()
|
? bot->session().windows().front().get()
|
||||||
: nullptr;
|
: nullptr;
|
||||||
const auto variant = QVariant::fromValue(ClickHandlerContext{
|
const auto variant = QVariant::fromValue(ClickHandlerContext{
|
||||||
.attachBotWebviewUrl = shownUrl,
|
.attachBotWebviewUrl = shownUrl,
|
||||||
|
|
|
@ -86,6 +86,34 @@ private:
|
||||||
: wrap(time == kDay ? 0 : time);
|
: 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) {
|
[[nodiscard]] QString JoinIntervals(const Data::WorkingIntervals &data) {
|
||||||
auto result = QStringList();
|
auto result = QStringList();
|
||||||
result.reserve(data.list.size());
|
result.reserve(data.list.size());
|
||||||
|
@ -105,49 +133,97 @@ void EditTimeBox(
|
||||||
Fn<void(TimeId)> save) {
|
Fn<void(TimeId)> save) {
|
||||||
Expects(low <= high);
|
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>(
|
const auto content = box->addRow(object_ptr<Ui::FixedHeightWidget>(
|
||||||
box,
|
box,
|
||||||
st::settingsWorkingHoursPicker));
|
st::settingsWorkingHoursPicker));
|
||||||
|
|
||||||
const auto font = st::boxTextFont;
|
const auto font = st::boxTextFont;
|
||||||
const auto itemHeight = st::settingsWorkingHoursPickerItemHeight;
|
const auto itemHeight = st::settingsWorkingHoursPickerItemHeight;
|
||||||
auto paintCallback = [=](
|
const auto picker = [=](
|
||||||
QPainter &p,
|
int count,
|
||||||
int index,
|
int startIndex,
|
||||||
float64 y,
|
Fn<void(QPainter &p, QRectF rect, int index)> paint) {
|
||||||
float64 distanceFromCenter,
|
auto paintCallback = [=](
|
||||||
int outerWidth) {
|
QPainter &p,
|
||||||
const auto r = QRectF(0, y, outerWidth, itemHeight);
|
int index,
|
||||||
const auto progress = std::abs(distanceFromCenter);
|
float64 y,
|
||||||
const auto revProgress = 1. - progress;
|
float64 distanceFromCenter,
|
||||||
p.save();
|
int outerWidth) {
|
||||||
p.translate(r.center());
|
const auto r = QRectF(0, y, outerWidth, itemHeight);
|
||||||
constexpr auto kMinYScale = 0.2;
|
const auto progress = std::abs(distanceFromCenter);
|
||||||
const auto yScale = kMinYScale
|
const auto revProgress = 1. - progress;
|
||||||
+ (1. - kMinYScale) * anim::easeOutCubic(1., revProgress);
|
p.save();
|
||||||
p.scale(1., yScale);
|
p.translate(r.center());
|
||||||
p.translate(-r.center());
|
constexpr auto kMinYScale = 0.2;
|
||||||
p.setOpacity(revProgress);
|
const auto yScale = kMinYScale
|
||||||
p.setFont(font);
|
+ (1. - kMinYScale) * anim::easeOutCubic(1., revProgress);
|
||||||
p.setPen(st::defaultFlatLabel.textFg);
|
p.scale(1., yScale);
|
||||||
p.drawText(r, FormatDayTime(low + index * 60, true), style::al_center);
|
p.translate(-r.center());
|
||||||
p.restore();
|
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>(
|
const auto hoursCount = (high - low + 3600) / 3600;
|
||||||
content,
|
const auto hoursStartIndex = (value - low) / 3600;
|
||||||
std::move(paintCallback),
|
const auto hoursPaint = [=](QPainter &p, QRectF rect, int index) {
|
||||||
values,
|
p.drawText(
|
||||||
itemHeight,
|
rect,
|
||||||
startIndex);
|
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(
|
const auto minutesPaint = [=](QPainter &p, QRectF rect, int index) {
|
||||||
) | rpl::start_with_next([=](const QSize &s) {
|
p.drawText(
|
||||||
picker->resize(s.width(), s.height());
|
rect,
|
||||||
picker->moveToLeft((s.width() - picker->width()) / 2, 0);
|
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->lifetime());
|
||||||
|
|
||||||
content->paintRequest(
|
content->paintRequest(
|
||||||
|
@ -163,28 +239,22 @@ void EditTimeBox(
|
||||||
st::defaultInputField.borderActive);
|
st::defaultInputField.borderActive);
|
||||||
p.fillRect(lineRect.translated(0, itemHeight / 2), st::activeLineFg);
|
p.fillRect(lineRect.translated(0, itemHeight / 2), st::activeLineFg);
|
||||||
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());
|
}, 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) {
|
base::install_event_filter(box, [=](not_null<QEvent*> e) {
|
||||||
if (e->type() == QEvent::KeyPress) {
|
if (e->type() == QEvent::KeyPress) {
|
||||||
picker->handleKeyEvent(static_cast<QKeyEvent*>(e.get()));
|
hours->handleKeyEvent(static_cast<QKeyEvent*>(e.get()));
|
||||||
}
|
}
|
||||||
return base::EventFilterResult::Continue;
|
return base::EventFilterResult::Continue;
|
||||||
});
|
});
|
||||||
|
|
||||||
box->addButton(tr::lng_settings_save(), [=] {
|
box->addButton(tr::lng_settings_save(), [=] {
|
||||||
const auto weak = Ui::MakeWeak(box);
|
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()) {
|
if (const auto strong = weak.data()) {
|
||||||
strong->closeBox();
|
strong->closeBox();
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,6 +92,8 @@ VerticalDrumPicker::VerticalDrumPicker(
|
||||||
_loopData.minIndex = -_itemsVisible.centerOffset;
|
_loopData.minIndex = -_itemsVisible.centerOffset;
|
||||||
_loopData.maxIndex = _itemsCount - 1 - _itemsVisible.centerOffset;
|
_loopData.maxIndex = _itemsCount - 1 - _itemsVisible.centerOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_changes.fire({});
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
paintRequest(
|
paintRequest(
|
||||||
|
@ -144,7 +146,9 @@ void VerticalDrumPicker::increaseShift(float64 by) {
|
||||||
index++;
|
index++;
|
||||||
index = normalizedIndex(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);
|
_shift = std::min(0., shift);
|
||||||
_index = _loopData.minIndex;
|
_index = _loopData.minIndex;
|
||||||
} else if (!_loopData.looped && (index >= _loopData.maxIndex)) {
|
} else if (!_loopData.looped && (index >= _loopData.maxIndex)) {
|
||||||
|
@ -154,6 +158,7 @@ void VerticalDrumPicker::increaseShift(float64 by) {
|
||||||
_shift = shift;
|
_shift = shift;
|
||||||
_index = index;
|
_index = index;
|
||||||
}
|
}
|
||||||
|
_changes.fire({});
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,4 +275,14 @@ int VerticalDrumPicker::index() const {
|
||||||
return normalizedIndex(_index + _itemsVisible.centerOffset);
|
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
|
} // namespace Ui
|
||||||
|
|
|
@ -52,6 +52,8 @@ public:
|
||||||
bool looped = false);
|
bool looped = false);
|
||||||
|
|
||||||
[[nodiscard]] int index() const;
|
[[nodiscard]] int index() const;
|
||||||
|
[[nodiscard]] rpl::producer<int> changes() const;
|
||||||
|
[[nodiscard]] rpl::producer<int> value() const;
|
||||||
|
|
||||||
void handleWheelEvent(not_null<QWheelEvent*> e);
|
void handleWheelEvent(not_null<QWheelEvent*> e);
|
||||||
void handleMouseEvent(not_null<QMouseEvent*> e);
|
void handleMouseEvent(not_null<QMouseEvent*> e);
|
||||||
|
@ -84,6 +86,7 @@ private:
|
||||||
|
|
||||||
int _index = 0;
|
int _index = 0;
|
||||||
float64 _shift = 0.;
|
float64 _shift = 0.;
|
||||||
|
rpl::event_stream<> _changes;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
const bool looped;
|
const bool looped;
|
||||||
|
|
Loading…
Add table
Reference in a new issue