/* 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 "intro/introwidget.h" #include "lang/lang_keys.h" #include "storage/localstorage.h" #include "lang/lang_file_parser.h" #include "intro/introstart.h" #include "intro/introphone.h" #include "intro/introcode.h" #include "intro/introsignup.h" #include "intro/intropwdcheck.h" #include "mainwidget.h" #include "apiwrap.h" #include "mainwindow.h" #include "messenger.h" #include "application.h" #include "boxes/confirm_box.h" #include "ui/text/text.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/wrap/fade_wrap.h" #include "ui/effects/slide_animation.h" #include "core/update_checker.h" #include "window/window_slide_animation.h" #include "window/window_connecting_widget.h" #include "window/window_lock_widgets.h" #include "styles/style_boxes.h" #include "styles/style_intro.h" #include "styles/style_window.h" #include "window/themes/window_theme.h" #include "lang/lang_cloud_manager.h" #include "auth_session.h" namespace Intro { namespace { constexpr str_const kDefaultCountry = "US"; void PrepareSupportMode() { anim::SetDisabled(true); Local::writeSettings(); Global::SetDesktopNotify(false); Global::SetSoundNotify(false); cSetAutoDownloadAudio(dbiadNoPrivate | dbiadNoGroups); cSetAutoDownloadGif(dbiadNoPrivate | dbiadNoGroups); cSetAutoDownloadPhoto(dbiadNoPrivate | dbiadNoGroups); cSetAutoPlayGif(false); Local::writeUserSettings(); } } // namespace Widget::Widget(QWidget *parent) : RpWidget(parent) , _back(this, object_ptr(this, st::introBackButton)) , _settings( this, object_ptr( this, langFactory(lng_menu_settings), st::defaultBoxButton)) , _next(this, Fn(), st::introNextButton) { auto country = Platform::SystemCountry(); if (country.isEmpty()) { country = str_const_toString(kDefaultCountry); } getData()->country = country; _back->entity()->setClickedCallback([this] { historyMove(Direction::Back); }); _back->hide(anim::type::instant); _next->setClickedCallback([this] { getStep()->submit(); }); _settings->entity()->setClickedCallback([] { App::wnd()->showSettings(); }); getNearestDC(); setupConnectingWidget(); appendStep(new StartWidget(this, getData())); fixOrder(); subscribe(Lang::CurrentCloudManager().firstLanguageSuggestion(), [this] { createLanguageLink(); }); createLanguageLink(); if (_changeLanguage) _changeLanguage->finishAnimating(); subscribe(Lang::Current().updated(), [this] { refreshLang(); }); show(); showControls(); getStep()->showFast(); cSetPasswordRecovered(false); if (!Core::UpdaterDisabled()) { Core::UpdateChecker checker; checker.isLatest() | rpl::start_with_next([=] { onCheckUpdateStatus(); }, lifetime()); checker.failed() | rpl::start_with_next([=] { onCheckUpdateStatus(); }, lifetime()); checker.ready() | rpl::start_with_next([=] { onCheckUpdateStatus(); }, lifetime()); checker.start(); onCheckUpdateStatus(); } } void Widget::setupConnectingWidget() { _connecting = Window::ConnectingWidget::CreateDefaultWidget( this, rpl::single(true)); } void Widget::refreshLang() { _changeLanguage.destroy(); createLanguageLink(); InvokeQueued(this, [this] { updateControlsGeometry(); }); } void Widget::createLanguageLink() { if (_changeLanguage) return; auto createLink = [this](const QString &text, const QString &languageId) { _changeLanguage.create( this, object_ptr(this, text)); _changeLanguage->hide(anim::type::instant); _changeLanguage->entity()->setClickedCallback([=] { Lang::CurrentCloudManager().switchToLanguage(languageId); }); _changeLanguage->toggle( !_resetAccount && !_terms, anim::type::normal); updateControlsGeometry(); }; const auto currentId = Lang::LanguageIdOrDefault(Lang::Current().id()); const auto defaultId = Lang::DefaultLanguageId(); const auto suggested = Lang::CurrentCloudManager().suggestedLanguage(); if (currentId != defaultId) { createLink(Lang::GetOriginalValue(lng_switch_to_this), defaultId); } else if (!suggested.isEmpty() && suggested != currentId) { request(MTPlangpack_GetStrings( MTP_string(Lang::CloudLangPackName()), MTP_string(suggested), MTP_vector(1, MTP_string("lng_switch_to_this")) )).done([=](const MTPVector &result) { auto strings = Lang::Instance::ParseStrings(result); auto it = strings.find(lng_switch_to_this); if (it != strings.end()) { createLink(it->second, suggested); } }).send(); } } void Widget::onCheckUpdateStatus() { Expects(!Core::UpdaterDisabled()); if (Core::UpdateChecker().state() == Core::UpdateChecker::State::Ready) { if (_update) return; _update.create( this, object_ptr( this, langFactory(lng_menu_update), st::defaultBoxButton)); if (!_a_show.animating()) { _update->setVisible(true); } const auto stepHasCover = getStep()->hasCover(); _update->toggle(!stepHasCover, anim::type::instant); _update->entity()->setClickedCallback([] { Core::checkReadyUpdate(); App::restart(); }); } else { if (!_update) return; _update.destroy(); } updateControlsGeometry(); } void Widget::setInnerFocus() { if (getStep()->animating()) { setFocus(); } else { getStep()->setInnerFocus(); } } void Widget::historyMove(Direction direction) { if (getStep()->animating()) return; Assert(_stepHistory.size() > 1); auto wasStep = getStep((direction == Direction::Back) ? 0 : 1); if (direction == Direction::Back) { _stepHistory.pop_back(); wasStep->cancelled(); } else if (direction == Direction::Replace) { _stepHistory.removeAt(_stepHistory.size() - 2); } if (_resetAccount) { hideAndDestroy(std::exchange(_resetAccount, { nullptr })); } if (_terms) { hideAndDestroy(std::exchange(_terms, { nullptr })); } getStep()->finishInit(); getStep()->prepareShowAnimated(wasStep); if (wasStep->hasCover() != getStep()->hasCover()) { _nextTopFrom = wasStep->contentTop() + st::introStepHeight; _controlsTopFrom = wasStep->hasCover() ? st::introCoverHeight : 0; _coverShownAnimation.start([this] { updateControlsGeometry(); }, 0., 1., st::introCoverDuration, wasStep->hasCover() ? anim::linear : anim::easeOutCirc); } if (direction == Direction::Forward || direction == Direction::Replace) { wasStep->finished(); } if (direction == Direction::Back || direction == Direction::Replace) { delete base::take(wasStep); } _back->toggle(getStep()->hasBack(), anim::type::normal); auto stepHasCover = getStep()->hasCover(); _settings->toggle(!stepHasCover, anim::type::normal); if (_update) { _update->toggle(!stepHasCover, anim::type::normal); } _next->setText([this] { return getStep()->nextButtonText(); }); if (_resetAccount) _resetAccount->show(anim::type::normal); if (_terms) _terms->show(anim::type::normal); if (_changeLanguage) { _changeLanguage->toggle( !_resetAccount && !_terms, anim::type::normal); } getStep()->showAnimated(direction); fixOrder(); } void Widget::hideAndDestroy(object_ptr> widget) { const auto weak = make_weak(widget.data()); widget->hide(anim::type::normal); widget->shownValue( ) | rpl::start_with_next([=](bool shown) { if (!shown && weak) { weak->deleteLater(); } }, widget->lifetime()); } void Widget::fixOrder() { _next->raise(); if (_update) _update->raise(); _settings->raise(); _back->raise(); _connecting->raise(); } void Widget::moveToStep(Step *step, Direction direction) { appendStep(step); _back->raise(); _settings->raise(); if (_update) { _update->raise(); } _connecting->raise(); historyMove(direction); } void Widget::appendStep(Step *step) { _stepHistory.push_back(step); step->setGeometry(calculateStepRect()); step->setGoCallback([=](Step *step, Direction direction) { if (direction == Direction::Back) { historyMove(direction); } else { moveToStep(step, direction); } }); step->setShowResetCallback([=] { showResetButton(); }); step->setShowTermsCallback([=]() { showTerms(); }); step->setAcceptTermsCallback([=](Fn callback) { acceptTerms(callback); }); } void Widget::showResetButton() { if (!_resetAccount) { auto entity = object_ptr(this, langFactory(lng_signin_reset_account), st::introResetButton); _resetAccount.create(this, std::move(entity)); _resetAccount->hide(anim::type::instant); _resetAccount->entity()->setClickedCallback([this] { resetAccount(); }); updateControlsGeometry(); } _resetAccount->show(anim::type::normal); if (_changeLanguage) { _changeLanguage->hide(anim::type::normal); } } void Widget::showTerms() { if (getData()->termsLock.text.text.isEmpty()) { _terms.destroy(); } else if (!_terms) { auto entity = object_ptr( this, lng_terms_signup( lt_link, textcmdLink(1, lang(lng_terms_signup_link))), Ui::FlatLabel::InitType::Rich, st::introTermsLabel); _terms.create(this, std::move(entity)); _terms->entity()->setLink( 1, std::make_shared([=] { showTerms(nullptr); })); updateControlsGeometry(); _terms->hide(anim::type::instant); } if (_changeLanguage) { _changeLanguage->toggle( !_terms && !_resetAccount, anim::type::normal); } } void Widget::acceptTerms(Fn callback) { showTerms(callback); } void Widget::resetAccount() { if (_resetRequest) return; Ui::show(Box(lang(lng_signin_sure_reset), lang(lng_signin_reset), st::attentionBoxButton, crl::guard(this, [this] { if (_resetRequest) return; _resetRequest = request(MTPaccount_DeleteAccount(MTP_string("Forgot password"))).done([this](const MTPBool &result) { _resetRequest = 0; Ui::hideLayer(); moveToStep(new SignupWidget(this, getData()), Direction::Replace); }).fail([this](const RPCError &error) { _resetRequest = 0; auto type = error.type(); if (type.startsWith(qstr("2FA_CONFIRM_WAIT_"))) { auto seconds = type.mid(qstr("2FA_CONFIRM_WAIT_").size()).toInt(); auto days = (seconds + 59) / 86400; auto hours = ((seconds + 59) % 86400) / 3600; auto minutes = ((seconds + 59) % 3600) / 60; auto when = lng_signin_reset_minutes(lt_count, minutes); if (days > 0) { auto daysCount = lng_signin_reset_days(lt_count, days); auto hoursCount = lng_signin_reset_hours(lt_count, hours); when = lng_signin_reset_in_days(lt_days_count, daysCount, lt_hours_count, hoursCount, lt_minutes_count, when); } else if (hours > 0) { auto hoursCount = lng_signin_reset_hours(lt_count, hours); when = lng_signin_reset_in_hours(lt_hours_count, hoursCount, lt_minutes_count, when); } Ui::show(Box(lng_signin_reset_wait(lt_phone_number, App::formatPhone(getData()->phone), lt_when, when))); } else if (type == qstr("2FA_RECENT_CONFIRM")) { Ui::show(Box(lang(lng_signin_reset_cancelled))); } else { Ui::hideLayer(); getStep()->showError(&Lang::Hard::ServerError); } }).send(); }))); } void Widget::getNearestDC() { request(MTPhelp_GetNearestDc()).done([this](const MTPNearestDc &result) { auto &nearest = result.c_nearestDc(); DEBUG_LOG(("Got nearest dc, country: %1, nearest: %2, this: %3" ).arg(qs(nearest.vcountry) ).arg(nearest.vnearest_dc.v ).arg(nearest.vthis_dc.v)); Messenger::Instance().suggestMainDcId(nearest.vnearest_dc.v); auto nearestCountry = qs(nearest.vcountry); if (getData()->country != nearestCountry) { getData()->country = nearestCountry; getData()->updated.notify(); } }).send(); } void Widget::showTerms(Fn callback) { if (getData()->termsLock.text.text.isEmpty()) { return; } const auto weak = make_weak(this); const auto box = Ui::show(callback ? Box( getData()->termsLock, langFactory(lng_terms_agree), langFactory(lng_terms_decline)) : Box( getData()->termsLock.text, langFactory(lng_box_ok), nullptr)); box->setCloseByEscape(false); box->setCloseByOutsideClick(false); box->agreeClicks( ) | rpl::start_with_next([=] { if (callback) { callback(); } if (box) { box->closeBox(); } }, box->lifetime()); box->cancelClicks( ) | rpl::start_with_next([=] { const auto box = Ui::show(Box( TextWithEntities{ lang(lng_terms_signup_sorry) }, langFactory(lng_intro_finish), langFactory(lng_terms_decline))); box->agreeClicks( ) | rpl::start_with_next([=] { if (weak) { showTerms(callback); } }, box->lifetime()); box->cancelClicks( ) | rpl::start_with_next([=] { if (box) { box->closeBox(); } }, box->lifetime()); }, box->lifetime()); } void Widget::showControls() { getStep()->show(); _next->show(); _next->setText([this] { return getStep()->nextButtonText(); }); _connecting->setForceHidden(false); auto hasCover = getStep()->hasCover(); _settings->toggle(!hasCover, anim::type::instant); if (_update) { _update->toggle(!hasCover, anim::type::instant); } if (_changeLanguage) { _changeLanguage->toggle( !_resetAccount && !_terms, anim::type::instant); } if (_terms) { _terms->show(anim::type::instant); } _back->toggle(getStep()->hasBack(), anim::type::instant); } void Widget::hideControls() { getStep()->hide(); _next->hide(); _connecting->setForceHidden(true); _settings->hide(anim::type::instant); if (_update) _update->hide(anim::type::instant); if (_changeLanguage) _changeLanguage->hide(anim::type::instant); if (_terms) _terms->hide(anim::type::instant); _back->hide(anim::type::instant); } void Widget::showAnimated(const QPixmap &bgAnimCache, bool back) { _showBack = back; (_showBack ? _cacheOver : _cacheUnder) = bgAnimCache; _a_show.finish(); showControls(); (_showBack ? _cacheUnder : _cacheOver) = Ui::GrabWidget(this); hideControls(); _a_show.start([this] { animationCallback(); }, 0., 1., st::slideDuration, Window::SlideAnimation::transition()); show(); } void Widget::animationCallback() { update(); if (!_a_show.animating()) { _cacheUnder = _cacheOver = QPixmap(); showControls(); getStep()->activate(); } } void Widget::paintEvent(QPaintEvent *e) { bool trivial = (rect() == e->rect()); setMouseTracking(true); if (_coverShownAnimation.animating()) { _coverShownAnimation.step(getms()); } QPainter p(this); if (!trivial) { p.setClipRect(e->rect()); } p.fillRect(e->rect(), st::windowBg); auto progress = _a_show.current(getms(), 1.); if (_a_show.animating()) { auto coordUnder = _showBack ? anim::interpolate(-st::slideShift, 0, progress) : anim::interpolate(0, -st::slideShift, progress); auto coordOver = _showBack ? anim::interpolate(0, width(), progress) : anim::interpolate(width(), 0, progress); auto shadow = _showBack ? (1. - progress) : progress; if (coordOver > 0) { p.drawPixmap(QRect(0, 0, coordOver, height()), _cacheUnder, QRect(-coordUnder * cRetinaFactor(), 0, coordOver * cRetinaFactor(), height() * cRetinaFactor())); p.setOpacity(shadow); p.fillRect(0, 0, coordOver, height(), st::slideFadeOutBg); p.setOpacity(1); } p.drawPixmap(coordOver, 0, _cacheOver); p.setOpacity(shadow); st::slideShadow.fill(p, QRect(coordOver - st::slideShadow.width(), 0, st::slideShadow.width(), height())); } } QRect Widget::calculateStepRect() const { auto stepInnerTop = (height() - st::introHeight) / 2; accumulate_max(stepInnerTop, st::introStepTopMin); auto nextTop = stepInnerTop + st::introStepHeight; auto additionalHeight = st::introStepHeightAdd; auto stepWidth = width(); auto stepHeight = nextTop + additionalHeight; return QRect(0, 0, stepWidth, stepHeight); } void Widget::resizeEvent(QResizeEvent *e) { auto stepRect = calculateStepRect(); for_const (auto step, _stepHistory) { step->setGeometry(stepRect); } updateControlsGeometry(); } void Widget::updateControlsGeometry() { auto shown = _coverShownAnimation.current(1.); auto controlsTopTo = getStep()->hasCover() ? st::introCoverHeight : 0; auto controlsTop = anim::interpolate(_controlsTopFrom, controlsTopTo, shown); _settings->moveToRight(st::introSettingsSkip, controlsTop + st::introSettingsSkip); if (_update) { _update->moveToRight(st::introSettingsSkip + _settings->width() + st::introSettingsSkip, _settings->y()); } _back->moveToLeft(0, controlsTop); auto nextTopTo = getStep()->contentTop() + st::introStepHeight; auto nextTop = anim::interpolate(_nextTopFrom, nextTopTo, shown); _next->moveToLeft((width() - _next->width()) / 2, nextTop); if (_changeLanguage) { _changeLanguage->moveToLeft((width() - _changeLanguage->width()) / 2, _next->y() + _next->height() + _changeLanguage->height()); } if (_resetAccount) { _resetAccount->moveToLeft((width() - _resetAccount->width()) / 2, height() - st::introResetBottom - _resetAccount->height()); } if (_terms) { _terms->moveToLeft((width() - _terms->width()) / 2, height() - st::introTermsBottom - _terms->height()); } } void Widget::keyPressEvent(QKeyEvent *e) { if (_a_show.animating() || getStep()->animating()) return; if (e->key() == Qt::Key_Escape || e->key() == Qt::Key_Back) { if (getStep()->hasBack()) { historyMove(Direction::Back); } } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return || e->key() == Qt::Key_Space) { getStep()->submit(); } } Widget::~Widget() { for (auto step : base::take(_stepHistory)) { delete step; } if (App::wnd()) App::wnd()->noIntro(this); } QString Widget::Step::nextButtonText() const { return lang(lng_intro_next); } void Widget::Step::finish(const MTPUser &user, QImage &&photo) { if (user.type() != mtpc_user || !user.c_user().is_self() || !user.c_user().vid.v) { // No idea what to do here. // We could've reset intro and MTP, but this really should not happen. Ui::show(Box("Internal error: bad user.is_self() after sign in.")); return; } // Save the default language if we've suggested some other and user ignored it. const auto currentId = Lang::Current().id(); const auto defaultId = Lang::DefaultLanguageId(); const auto suggested = Lang::CurrentCloudManager().suggestedLanguage(); if (currentId.isEmpty() && !suggested.isEmpty() && suggested != defaultId) { Lang::Current().switchToId(Lang::DefaultLanguage()); Local::writeLangPack(); } Messenger::Instance().authSessionCreate(user); Local::writeMtpData(); App::wnd()->setupMain(); // "this" is already deleted here by creating the main widget. if (AuthSession::Exists()) { if (!photo.isNull()) { Auth().api().uploadPeerPhoto(Auth().user(), std::move(photo)); } if (Auth().supportMode()) { PrepareSupportMode(); } } } void Widget::Step::paintEvent(QPaintEvent *e) { Painter p(this); paintAnimated(p, e->rect()); } void Widget::Step::resizeEvent(QResizeEvent *e) { updateLabelsPosition(); } void Widget::Step::updateLabelsPosition() { Ui::SendPendingMoveResizeEvents(_description->entity()); if (hasCover()) { _title->moveToLeft((width() - _title->width()) / 2, contentTop() + st::introCoverTitleTop); _description->moveToLeft((width() - _description->width()) / 2, contentTop() + st::introCoverDescriptionTop); } else { _title->moveToLeft(contentLeft() + st::buttonRadius, contentTop() + st::introTitleTop); _description->moveToLeft(contentLeft() + st::buttonRadius, contentTop() + st::introDescriptionTop); } if (_error) { if (_errorCentered) { _error->entity()->resizeToWidth(width()); } Ui::SendPendingMoveResizeEvents(_error->entity()); auto errorLeft = _errorCentered ? 0 : (contentLeft() + st::buttonRadius); auto errorTop = contentTop() + (_errorBelowLink ? st::introErrorBelowLinkTop : st::introErrorTop); _error->moveToLeft(errorLeft, errorTop); } } void Widget::Step::setTitleText(Fn richTitleTextFactory) { _titleTextFactory = std::move(richTitleTextFactory); refreshTitle(); updateLabelsPosition(); } void Widget::Step::refreshTitle() { _title->setRichText(_titleTextFactory()); } void Widget::Step::setDescriptionText(Fn richDescriptionTextFactory) { _descriptionTextFactory = std::move(richDescriptionTextFactory); refreshDescription(); updateLabelsPosition(); } void Widget::Step::refreshDescription() { _description->entity()->setRichText(_descriptionTextFactory()); } void Widget::Step::refreshLang() { refreshTitle(); refreshDescription(); refreshError(); updateLabelsPosition(); } void Widget::Step::showFinished() { _a_show.finish(); _coverAnimation = CoverAnimation(); _slideAnimation.reset(); prepareCoverMask(); activate(); } bool Widget::Step::paintAnimated(Painter &p, QRect clip) { if (_slideAnimation) { _slideAnimation->paintFrame(p, (width() - st::introStepWidth) / 2, contentTop(), width(), getms()); if (!_slideAnimation->animating()) { showFinished(); return false; } return true; } auto dt = _a_show.current(getms(), 1.); if (!_a_show.animating()) { if (hasCover()) { paintCover(p, 0); } if (_coverAnimation.title) { showFinished(); } if (!QRect(0, contentTop(), width(), st::introStepHeight).intersects(clip)) { return true; } return false; } auto progress = (hasCover() ? anim::easeOutCirc(1., dt) : anim::linear(1., dt)); auto arrivingAlpha = progress; auto departingAlpha = 1. - progress; auto showCoverMethod = progress; auto hideCoverMethod = progress; auto coverTop = (hasCover() ? anim::interpolate(-st::introCoverHeight, 0, showCoverMethod) : anim::interpolate(0, -st::introCoverHeight, hideCoverMethod)); paintCover(p, coverTop); auto positionReady = hasCover() ? showCoverMethod : hideCoverMethod; _coverAnimation.title->paintFrame(p, positionReady, departingAlpha, arrivingAlpha); _coverAnimation.description->paintFrame(p, positionReady, departingAlpha, arrivingAlpha); paintContentSnapshot(p, _coverAnimation.contentSnapshotWas, departingAlpha, showCoverMethod); paintContentSnapshot(p, _coverAnimation.contentSnapshotNow, arrivingAlpha, 1. - hideCoverMethod); return true; } void Widget::Step::fillSentCodeData(const MTPDauth_sentCode &data) { if (data.has_terms_of_service()) { const auto &terms = data.vterms_of_service.c_help_termsOfService(); getData()->termsLock = Window::TermsLock::FromMTP(terms); } else { getData()->termsLock = Window::TermsLock(); } const auto &type = data.vtype; switch (type.type()) { case mtpc_auth_sentCodeTypeApp: { getData()->codeByTelegram = true; getData()->codeLength = type.c_auth_sentCodeTypeApp().vlength.v; } break; case mtpc_auth_sentCodeTypeSms: { getData()->codeByTelegram = false; getData()->codeLength = type.c_auth_sentCodeTypeSms().vlength.v; } break; case mtpc_auth_sentCodeTypeCall: { getData()->codeByTelegram = false; getData()->codeLength = type.c_auth_sentCodeTypeCall().vlength.v; } break; case mtpc_auth_sentCodeTypeFlashCall: LOG(("Error: should not be flashcall!")); break; } } void Widget::Step::showDescription() { _description->show(anim::type::normal); } void Widget::Step::hideDescription() { _description->hide(anim::type::normal); } void Widget::Step::paintContentSnapshot(Painter &p, const QPixmap &snapshot, float64 alpha, float64 howMuchHidden) { if (!snapshot.isNull()) { auto contentTop = anim::interpolate(height() - (snapshot.height() / cIntRetinaFactor()), height(), howMuchHidden); if (contentTop < height()) { p.setOpacity(alpha); p.drawPixmap(QPoint(contentLeft(), contentTop), snapshot, QRect(0, 0, snapshot.width(), (height() - contentTop) * cIntRetinaFactor())); } } } void Widget::Step::prepareCoverMask() { if (!_coverMask.isNull()) return; auto maskWidth = cIntRetinaFactor(); auto maskHeight = st::introCoverHeight * cIntRetinaFactor(); auto mask = QImage(maskWidth, maskHeight, QImage::Format_ARGB32_Premultiplied); auto maskInts = reinterpret_cast(mask.bits()); Assert(mask.depth() == (sizeof(uint32) << 3)); auto maskIntsPerLineAdded = (mask.bytesPerLine() >> 2) - maskWidth; Assert(maskIntsPerLineAdded >= 0); auto realHeight = static_cast(maskHeight - 1); for (auto y = 0; y != maskHeight; ++y) { auto color = anim::color(st::introCoverTopBg, st::introCoverBottomBg, y / realHeight); auto colorInt = anim::getPremultiplied(color); for (auto x = 0; x != maskWidth; ++x) { *maskInts++ = colorInt; } maskInts += maskIntsPerLineAdded; } _coverMask = App::pixmapFromImageInPlace(std::move(mask)); } void Widget::Step::paintCover(Painter &p, int top) { auto coverHeight = top + st::introCoverHeight; if (coverHeight > 0) { p.drawPixmap(QRect(0, 0, width(), coverHeight), _coverMask, QRect(0, -top * cIntRetinaFactor(), _coverMask.width(), coverHeight * cIntRetinaFactor())); } auto left = 0; auto right = 0; if (width() < st::introCoverMaxWidth) { auto iconsMaxSkip = st::introCoverMaxWidth - st::introCoverLeft.width() - st::introCoverRight.width(); auto iconsSkip = st::introCoverIconsMinSkip + (iconsMaxSkip - st::introCoverIconsMinSkip) * (width() - st::introStepWidth) / (st::introCoverMaxWidth - st::introStepWidth); auto outside = iconsSkip + st::introCoverLeft.width() + st::introCoverRight.width() - width(); left = -outside / 2; right = -outside - left; } if (top < 0) { auto shown = float64(coverHeight) / st::introCoverHeight; auto leftShown = qRound(shown * (left + st::introCoverLeft.width())); left = leftShown - st::introCoverLeft.width(); auto rightShown = qRound(shown * (right + st::introCoverRight.width())); right = rightShown - st::introCoverRight.width(); } st::introCoverLeft.paint(p, left, coverHeight - st::introCoverLeft.height(), width()); st::introCoverRight.paint(p, width() - right - st::introCoverRight.width(), coverHeight - st::introCoverRight.height(), width()); auto planeLeft = (width() - st::introCoverIcon.width()) / 2 - st::introCoverIconLeft; auto planeTop = top + st::introCoverIconTop; if (top < 0 && !_hasCover) { auto deltaLeft = -qRound(float64(st::introPlaneWidth / st::introPlaneHeight) * top); // auto deltaTop = top; planeLeft += deltaLeft; // planeTop += top; } st::introCoverIcon.paint(p, planeLeft, planeTop, width()); } int Widget::Step::contentLeft() const { return (width() - st::introNextButton.width) / 2; } int Widget::Step::contentTop() const { auto result = height() - st::introStepHeight - st::introStepHeightAdd; if (_hasCover) { auto added = 1. - snap(float64(height() - st::windowMinHeight) / (st::introStepHeightFull - st::windowMinHeight), 0., 1.); result += qRound(added * st::introStepHeightAdd); } return result; } void Widget::Step::setErrorCentered(bool centered) { _errorCentered = centered; _error.destroy(); } void Widget::Step::setErrorBelowLink(bool below) { _errorBelowLink = below; if (_error) { updateLabelsPosition(); } } void Widget::Step::showError(Fn textFactory) { _errorTextFactory = std::move(textFactory); refreshError(); updateLabelsPosition(); } void Widget::Step::refreshError() { if (!_errorTextFactory) { if (_error) _error->hide(anim::type::normal); } else { if (!_error) { _error.create( this, object_ptr( this, _errorCentered ? st::introErrorCentered : st::introError)); _error->hide(anim::type::instant); } _error->entity()->setText(_errorTextFactory()); updateLabelsPosition(); _error->show(anim::type::normal); } } Widget::Step::Step(QWidget *parent, Data *data, bool hasCover) : TWidget(parent) , _data(data) , _hasCover(hasCover) , _title(this, _hasCover ? st::introCoverTitle : st::introTitle) , _description( this, object_ptr( this, _hasCover ? st::introCoverDescription : st::introDescription)) { hide(); subscribe(Window::Theme::Background(), [this]( const Window::Theme::BackgroundUpdate &update) { if (update.paletteChanged()) { if (!_coverMask.isNull()) { _coverMask = QPixmap(); prepareCoverMask(); } } }); subscribe(Lang::Current().updated(), [this] { refreshLang(); }); } void Widget::Step::prepareShowAnimated(Step *after) { setInnerFocus(); if (hasCover() || after->hasCover()) { _coverAnimation = prepareCoverAnimation(after); prepareCoverMask(); } else { auto leftSnapshot = after->prepareSlideAnimation(); auto rightSnapshot = prepareSlideAnimation(); _slideAnimation = std::make_unique(); _slideAnimation->setSnapshots(std::move(leftSnapshot), std::move(rightSnapshot)); _slideAnimation->setOverflowHidden(false); } } Widget::Step::CoverAnimation Widget::Step::prepareCoverAnimation(Step *after) { auto result = CoverAnimation(); result.title = Ui::FlatLabel::CrossFade( after->_title, _title, st::introBg); result.description = Ui::FlatLabel::CrossFade( after->_description->entity(), _description->entity(), st::introBg, after->_description->pos(), _description->pos()); result.contentSnapshotWas = after->prepareContentSnapshot(); result.contentSnapshotNow = prepareContentSnapshot(); return result; } QPixmap Widget::Step::prepareContentSnapshot() { auto otherTop = _description->y() + _description->height(); auto otherRect = myrtlrect(contentLeft(), otherTop, st::introStepWidth, height() - otherTop); return Ui::GrabWidget(this, otherRect); } QPixmap Widget::Step::prepareSlideAnimation() { auto grabLeft = (width() - st::introStepWidth) / 2; auto grabTop = contentTop(); return Ui::GrabWidget( this, QRect(grabLeft, grabTop, st::introStepWidth, st::introStepHeight)); } void Widget::Step::showAnimated(Direction direction) { setFocus(); show(); hideChildren(); if (_slideAnimation) { auto slideLeft = (direction == Direction::Back); _slideAnimation->start(slideLeft, [this] { update(0, contentTop(), width(), st::introStepHeight); }, st::introSlideDuration); } else { _a_show.start([this] { update(); }, 0., 1., st::introCoverDuration); } } void Widget::Step::setGoCallback(Fn callback) { _goCallback = std::move(callback); } void Widget::Step::setShowResetCallback(Fn callback) { _showResetCallback = std::move(callback); } void Widget::Step::setShowTermsCallback(Fn callback) { _showTermsCallback = std::move(callback); } void Widget::Step::setAcceptTermsCallback( Fn callback)> callback) { _acceptTermsCallback = std::move(callback); } void Widget::Step::showFast() { show(); showFinished(); } bool Widget::Step::animating() const { return (_slideAnimation && _slideAnimation->animating()) || _a_show.animating(); } bool Widget::Step::hasCover() const { return _hasCover; } bool Widget::Step::hasBack() const { return false; } void Widget::Step::activate() { _title->show(); _description->show(anim::type::instant); if (_errorTextFactory) { _error->show(anim::type::instant); } } void Widget::Step::cancelled() { } void Widget::Step::finished() { hide(); } Widget::Step::CoverAnimation::~CoverAnimation() = default; Widget::Step::~Step() = default; } // namespace Intro