From b55383efe792583ad68e5db0a12e0f9bf08dd824 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 6 Aug 2021 04:08:30 +0300 Subject: [PATCH] Moved time input widgets to lib_ui. --- .../SourceFiles/ui/boxes/calendar_box.cpp | 11 +- .../SourceFiles/ui/boxes/choose_date_time.cpp | 510 +----------------- Telegram/lib_ui | 2 +- 3 files changed, 12 insertions(+), 511 deletions(-) diff --git a/Telegram/SourceFiles/ui/boxes/calendar_box.cpp b/Telegram/SourceFiles/ui/boxes/calendar_box.cpp index 8c4095f922..bf30631fa9 100644 --- a/Telegram/SourceFiles/ui/boxes/calendar_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/calendar_box.cpp @@ -597,16 +597,11 @@ void CalendarBox::keyPressEvent(QKeyEvent *e) { } void CalendarBox::wheelEvent(QWheelEvent *e) { - // Only a mouse wheel is accepted. - constexpr auto step = static_cast(QWheelEvent::DefaultDeltasPerStep); - const auto delta = e->angleDelta().y(); - if (std::abs(delta) != step) { - return; - } + const auto direction = Ui::WheelDirection(e); - if (delta < 0) { + if (direction < 0) { goPreviousMonth(); - } else { + } else if (direction > 0) { goNextMonth(); } } diff --git a/Telegram/SourceFiles/ui/boxes/choose_date_time.cpp b/Telegram/SourceFiles/ui/boxes/choose_date_time.cpp index 482826e551..f1532f8a20 100644 --- a/Telegram/SourceFiles/ui/boxes/choose_date_time.cpp +++ b/Telegram/SourceFiles/ui/boxes/choose_date_time.cpp @@ -12,12 +12,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/boxes/calendar_box.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" +#include "ui/widgets/time_input.h" +#include "ui/ui_utility.h" #include "lang/lang_keys.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" -#include - namespace Ui { namespace { @@ -56,504 +56,6 @@ QString TimeString(QTime time) { ).arg(time.minute(), 2, 10, QLatin1Char('0')); } -int ProcessWheelEvent(not_null e) { - // Only a mouse wheel is accepted. - constexpr auto step = static_cast(QWheelEvent::DefaultDeltasPerStep); - const auto delta = e->angleDelta().y(); - const auto absDelta = std::abs(delta); - if (absDelta != step) { - return 0; - } - return (delta / absDelta); -} - -class TimePart final : public MaskedInputField { -public: - using MaskedInputField::MaskedInputField; - - void setMaxValue(int value); - void setWheelStep(int value); - - rpl::producer<> erasePrevious() const; - rpl::producer putNext() const; - -protected: - void keyPressEvent(QKeyEvent *e) override; - void wheelEvent(QWheelEvent *e) override; - - void correctValue( - const QString &was, - int wasCursor, - QString &now, - int &nowCursor) override; - -private: - int _maxValue = 0; - int _maxDigits = 0; - int _wheelStep = 0; - rpl::event_stream<> _erasePrevious; - rpl::event_stream _putNext; - -}; - -int Number(not_null field) { - const auto text = field->getLastText(); - auto ref = text.midRef(0); - while (!ref.isEmpty() && ref.at(0) == '0') { - ref = ref.mid(1); - } - return ref.toInt(); -} - -class TimeInput final : public RpWidget { -public: - TimeInput(QWidget *parent, const QString &value); - - bool setFocusFast(); - rpl::producer value() const; - rpl::producer<> submitRequests() const; - QString valueCurrent() const; - void showError(); - - int resizeGetHeight(int width) override; - -protected: - void paintEvent(QPaintEvent *e) override; - void mousePressEvent(QMouseEvent *e) override; - void mouseMoveEvent(QMouseEvent *e) override; - -private: - void setInnerFocus(); - void putNext(const object_ptr &field, QChar ch); - void erasePrevious(const object_ptr &field); - void setErrorShown(bool error); - void setFocused(bool focused); - void startBorderAnimation(); - template - bool insideSeparator(QPoint position, const Widget &widget) const; - - int hour() const; - int minute() const; - - object_ptr _hour; - object_ptr> _separator1; - object_ptr _minute; - rpl::variable _value; - rpl::event_stream<> _submitRequests; - - style::cursor _cursor = style::cur_default; - Animations::Simple _a_borderShown; - int _borderAnimationStart = 0; - Animations::Simple _a_borderOpacity; - bool _borderVisible = false; - - Animations::Simple _a_error; - bool _error = false; - Animations::Simple _a_focused; - bool _focused = false; - -}; - -QTime ValidateTime(const QString &value) { - const auto match = QRegularExpression( - "^(\\d{1,2})\\:(\\d\\d)$").match(value); - if (!match.hasMatch()) { - return QTime(); - } - const auto readInt = [](const QString &value) { - auto ref = value.midRef(0); - while (!ref.isEmpty() && ref.at(0) == '0') { - ref = ref.mid(1); - } - return ref.toInt(); - }; - return QTime(readInt(match.captured(1)), readInt(match.captured(2))); -} - -QString GetHour(const QString &value) { - if (const auto time = ValidateTime(value); time.isValid()) { - return QString::number(time.hour()); - } - return QString(); -} - -QString GetMinute(const QString &value) { - if (const auto time = ValidateTime(value); time.isValid()) { - return QString("%1").arg(time.minute(), 2, 10, QChar('0')); - } - return QString(); -} - -void TimePart::setMaxValue(int value) { - _maxValue = value; - _maxDigits = 0; - while (value > 0) { - ++_maxDigits; - value /= 10; - } -} - -void TimePart::setWheelStep(int value) { - _wheelStep = value; -} - -rpl::producer<> TimePart::erasePrevious() const { - return _erasePrevious.events(); -} - -rpl::producer TimePart::putNext() const { - return _putNext.events(); -} - -void TimePart::keyPressEvent(QKeyEvent *e) { - const auto isBackspace = (e->key() == Qt::Key_Backspace); - const auto isBeginning = (cursorPosition() == 0); - if (isBackspace && isBeginning && !hasSelectedText()) { - _erasePrevious.fire({}); - } else { - MaskedInputField::keyPressEvent(e); - } -} - -void TimePart::wheelEvent(QWheelEvent *e) { - const auto direction = ProcessWheelEvent(e); - auto time = Number(this) + (direction * _wheelStep); - const auto max = _maxValue + 1; - if (time < 0) { - time += max; - } else if (time >= max) { - time -= max; - } - setText(QString::number(time)); -} - -void TimePart::correctValue( - const QString &was, - int wasCursor, - QString &now, - int &nowCursor) { - auto newText = QString(); - auto newCursor = -1; - const auto oldCursor = nowCursor; - const auto oldLength = now.size(); - auto accumulated = 0; - auto limit = 0; - for (; limit != oldLength; ++limit) { - if (now[limit].isDigit()) { - accumulated *= 10; - accumulated += (now[limit].unicode() - '0'); - if (accumulated > _maxValue || limit == _maxDigits) { - break; - } - } - } - for (auto i = 0; i != limit;) { - if (now[i].isDigit()) { - newText += now[i]; - } - if (++i == oldCursor) { - newCursor = newText.size(); - } - } - if (newCursor < 0) { - newCursor = newText.size(); - } - if (newText != now) { - now = newText; - setText(now); - startPlaceholderAnimation(); - } - if (newCursor != nowCursor) { - nowCursor = newCursor; - setCursorPosition(nowCursor); - } - if (accumulated > _maxValue - || (limit == _maxDigits && oldLength > _maxDigits)) { - if (oldCursor > limit) { - _putNext.fire('0' + (accumulated % 10)); - } else { - _putNext.fire(0); - } - } -} - -TimeInput::TimeInput(QWidget *parent, const QString &value) -: RpWidget(parent) -, _hour( - this, - st::scheduleTimeField, - rpl::never(), - GetHour(value)) -, _separator1( - this, - object_ptr( - this, - QString(":"), - st::scheduleTimeSeparator), - st::scheduleTimeSeparatorPadding) -, _minute( - this, - st::scheduleTimeField, - rpl::never(), - GetMinute(value)) -, _value(valueCurrent()) { - const auto focused = [=](const object_ptr &field) { - return [this, pointer = MakeWeak(field.data())]{ - _borderAnimationStart = pointer->borderAnimationStart() - + pointer->x() - - _hour->x(); - setFocused(true); - }; - }; - const auto blurred = [=] { - setFocused(false); - }; - const auto changed = [=] { - _value = valueCurrent(); - }; - connect(_hour, &MaskedInputField::focused, focused(_hour)); - connect(_minute, &MaskedInputField::focused, focused(_minute)); - connect(_hour, &MaskedInputField::blurred, blurred); - connect(_minute, &MaskedInputField::blurred, blurred); - connect(_hour, &MaskedInputField::changed, changed); - connect(_minute, &MaskedInputField::changed, changed); - _hour->setMaxValue(23); - _hour->setWheelStep(1); - _hour->putNext() | rpl::start_with_next([=](QChar ch) { - putNext(_minute, ch); - }, lifetime()); - _minute->setMaxValue(59); - _minute->setWheelStep(10); - _minute->erasePrevious() | rpl::start_with_next([=] { - erasePrevious(_hour); - }, lifetime()); - _separator1->setAttribute(Qt::WA_TransparentForMouseEvents); - setMouseTracking(true); - - _value.changes( - ) | rpl::start_with_next([=] { - setErrorShown(false); - }, lifetime()); - - const auto submitHour = [=] { - if (hour()) { - _minute->setFocus(); - } - }; - const auto submitMinute = [=] { - if (minute()) { - if (hour()) { - _submitRequests.fire({}); - } else { - _hour->setFocus(); - } - } - }; - connect( - _hour, - &MaskedInputField::submitted, - submitHour); - connect( - _minute, - &MaskedInputField::submitted, - submitMinute); -} - -void TimeInput::putNext(const object_ptr &field, QChar ch) { - field->setCursorPosition(0); - if (ch.unicode()) { - field->setText(ch + field->getLastText()); - field->setCursorPosition(1); - } - field->setFocus(); -} - -void TimeInput::erasePrevious(const object_ptr &field) { - const auto text = field->getLastText(); - if (!text.isEmpty()) { - field->setCursorPosition(text.size() - 1); - field->setText(text.mid(0, text.size() - 1)); - } - field->setFocus(); -} - -bool TimeInput::setFocusFast() { - if (hour()) { - _minute->setFocusFast(); - } else { - _hour->setFocusFast(); - } - return true; -} - -int TimeInput::hour() const { - return Number(_hour); -} - -int TimeInput::minute() const { - return Number(_minute); -} - -QString TimeInput::valueCurrent() const { - const auto result = QString("%1:%2" - ).arg(hour() - ).arg(minute(), 2, 10, QChar('0')); - return ValidateTime(result).isValid() ? result : QString(); -} - -rpl::producer TimeInput::value() const { - return _value.value(); -} - -rpl::producer<> TimeInput::submitRequests() const { - return _submitRequests.events(); -} - -void TimeInput::paintEvent(QPaintEvent *e) { - Painter p(this); - - const auto &_st = st::scheduleDateField; - const auto height = _st.heightMin; - if (_st.border) { - p.fillRect(0, height - _st.border, width(), _st.border, _st.borderFg); - } - auto errorDegree = _a_error.value(_error ? 1. : 0.); - auto borderShownDegree = _a_borderShown.value(1.); - auto borderOpacity = _a_borderOpacity.value(_borderVisible ? 1. : 0.); - if (_st.borderActive && (borderOpacity > 0.)) { - auto borderStart = std::clamp(_borderAnimationStart, 0, width()); - auto borderFrom = qRound(borderStart * (1. - borderShownDegree)); - auto borderTo = borderStart + qRound((width() - borderStart) * borderShownDegree); - if (borderTo > borderFrom) { - auto borderFg = anim::brush(_st.borderFgActive, _st.borderFgError, errorDegree); - p.setOpacity(borderOpacity); - p.fillRect(borderFrom, height - _st.borderActive, borderTo - borderFrom, _st.borderActive, borderFg); - p.setOpacity(1); - } - } -} - -template -bool TimeInput::insideSeparator(QPoint position, const Widget &widget) const { - const auto x = position.x(); - const auto y = position.y(); - return (x >= widget->x() && x < widget->x() + widget->width()) - && (y >= _hour->y() && y < _hour->y() + _hour->height()); -} - -void TimeInput::mouseMoveEvent(QMouseEvent *e) { - const auto cursor = insideSeparator(e->pos(), _separator1) - ? style::cur_text - : style::cur_default; - if (_cursor != cursor) { - _cursor = cursor; - setCursor(_cursor); - } -} - -void TimeInput::mousePressEvent(QMouseEvent *e) { - const auto x = e->pos().x(); - const auto focus1 = [&] { - if (_hour->getLastText().size() > 1) { - _minute->setFocus(); - } else { - _hour->setFocus(); - } - }; - if (insideSeparator(e->pos(), _separator1)) { - focus1(); - _borderAnimationStart = x - _hour->x(); - } -} - -int TimeInput::resizeGetHeight(int width) { - const auto &_st = st::scheduleTimeField; - const auto &font = _st.placeholderFont; - const auto addToWidth = st::scheduleTimeSeparatorPadding.left(); - const auto hourWidth = _st.textMargins.left() - + _st.placeholderMargins.left() - + font->width(QString("23")) - + _st.placeholderMargins.right() - + _st.textMargins.right() - + addToWidth; - const auto minuteWidth = _st.textMargins.left() - + _st.placeholderMargins.left() - + font->width(QString("59")) - + _st.placeholderMargins.right() - + _st.textMargins.right() - + addToWidth; - const auto full = hourWidth - - addToWidth - + _separator1->width() - + minuteWidth - - addToWidth; - auto left = (width - full) / 2; - auto top = 0; - _hour->setGeometry(left, top, hourWidth, _hour->height()); - left += hourWidth - addToWidth; - _separator1->resizeToNaturalWidth(width); - _separator1->move(left, top); - left += _separator1->width(); - _minute->setGeometry(left, top, minuteWidth, _minute->height()); - return st::scheduleDateField.heightMin; -} - -void TimeInput::showError() { - setErrorShown(true); - if (!_focused) { - setInnerFocus(); - } -} - -void TimeInput::setInnerFocus() { - if (hour()) { - _minute->setFocus(); - } else { - _hour->setFocus(); - } -} - -void TimeInput::setErrorShown(bool error) { - if (_error != error) { - _error = error; - _a_error.start( - [=] { update(); }, - _error ? 0. : 1., - _error ? 1. : 0., - st::scheduleDateField.duration); - startBorderAnimation(); - } -} - -void TimeInput::setFocused(bool focused) { - if (_focused != focused) { - _focused = focused; - _a_focused.start( - [=] { update(); }, - _focused ? 0. : 1., - _focused ? 1. : 0., - st::scheduleDateField.duration); - startBorderAnimation(); - } -} - -void TimeInput::startBorderAnimation() { - auto borderVisible = (_error || _focused); - if (_borderVisible != borderVisible) { - _borderVisible = borderVisible; - const auto duration = st::scheduleDateField.duration; - if (_borderVisible) { - if (_a_borderOpacity.animating()) { - _a_borderOpacity.start([=] { update(); }, 0., 1., duration); - } else { - _a_borderShown.start([=] { update(); }, 0., 1., duration); - } - } else { - _a_borderOpacity.start([=] { update(); }, 1., 0., duration); - } - } -} - } // namespace ChooseDateTimeBoxDescriptor ChooseDateTimeBox( @@ -584,7 +86,11 @@ ChooseDateTimeBoxDescriptor ChooseDateTimeBox( st::scheduleDateField), .time = CreateChild( content, - TimeString(parsed.time())), + TimeString(parsed.time()), + st::scheduleTimeField, + st::scheduleDateField, + st::scheduleTimeSeparator, + st::scheduleTimeSeparatorPadding), .at = CreateChild( content, tr::lng_schedule_at(), @@ -615,7 +121,7 @@ ChooseDateTimeBoxDescriptor ChooseDateTimeBox( base::install_event_filter(dayViewport, [=](not_null event) { if (event->type() == QEvent::Wheel) { const auto e = static_cast(event.get()); - const auto direction = ProcessWheelEvent(e); + const auto direction = Ui::WheelDirection(e); if (!direction) { return base::EventFilterResult::Continue; } diff --git a/Telegram/lib_ui b/Telegram/lib_ui index dd88f8fa41..4fcddb5bfe 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit dd88f8fa41a06bdf3128276d8084cfa4f087dee7 +Subproject commit 4fcddb5bfeb15350a4e749e4ead302a89b283106