| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644 |
- /*
- 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 "window/window_connecting_widget.h"
- #include "ui/widgets/buttons.h"
- #include "ui/effects/radial_animation.h"
- #include "ui/painter.h"
- #include "ui/ui_utility.h"
- #include "mtproto/mtp_instance.h"
- #include "mtproto/facade.h"
- #include "main/main_account.h"
- #include "core/application.h"
- #include "core/core_settings.h"
- #include "core/update_checker.h"
- #include "boxes/connection_box.h"
- #include "boxes/abstract_box.h"
- #include "lang/lang_keys.h"
- #include "styles/style_window.h"
- #include <QtGui/QWindow>
- namespace Window {
- namespace {
- constexpr auto kIgnoreStartConnectingFor = crl::time(3000);
- constexpr auto kConnectingStateDelay = crl::time(1000);
- constexpr auto kRefreshTimeout = crl::time(200);
- constexpr auto kMinimalWaitingStateDuration = crl::time(4000);
- class Progress : public Ui::RpWidget {
- public:
- Progress(QWidget *parent);
- rpl::producer<> animationStepRequests() const;
- protected:
- void paintEvent(QPaintEvent *e) override;
- private:
- void animationStep();
- Ui::InfiniteRadialAnimation _animation;
- rpl::event_stream<> _animationStepRequests;
- };
- Progress::Progress(QWidget *parent)
- : RpWidget(parent)
- , _animation([=] { animationStep(); }, st::connectingRadial) {
- setAttribute(Qt::WA_OpaquePaintEvent);
- setAttribute(Qt::WA_TransparentForMouseEvents);
- resize(st::connectingRadial.size);
- _animation.start(st::connectingRadial.sineDuration);
- }
- void Progress::paintEvent(QPaintEvent *e) {
- auto p = QPainter(this);
- p.fillRect(e->rect(), st::windowBg);
- const auto &st = st::connectingRadial;
- const auto shift = st.thickness - (st.thickness / 2);
- _animation.draw(
- p,
- { shift, shift },
- QSize(st.size.width() - 2 * shift, st.size.height() - 2 * shift),
- width());
- }
- void Progress::animationStep() {
- if (!anim::Disabled()) {
- _animationStepRequests.fire({});
- update();
- }
- }
- rpl::producer<> Progress::animationStepRequests() const {
- return _animationStepRequests.events();
- }
- } // namespace
- class ConnectionState::Widget : public Ui::AbstractButton {
- public:
- Widget(
- QWidget *parent,
- not_null<Main::Account*> account,
- const Layout &layout);
- void refreshRetryLink(bool hasRetry);
- void setLayout(const Layout &layout);
- void setProgressVisibility(bool visible);
- rpl::producer<> refreshStateRequests() const;
- protected:
- void resizeEvent(QResizeEvent *e) override;
- void paintEvent(QPaintEvent *e) override;
- void onStateChanged(State was, StateChangeSource source) override;
- private:
- class ProxyIcon;
- using State = ConnectionState::State;
- using Layout = ConnectionState::Layout;
- void updateRetryGeometry();
- QRect innerRect() const;
- QRect contentRect() const;
- QRect textRect() const;
- const not_null<Main::Account*> _account;
- Layout _currentLayout;
- base::unique_qptr<Ui::LinkButton> _retry;
- QPointer<Progress> _progress;
- QPointer<ProxyIcon> _proxyIcon;
- rpl::event_stream<> _refreshStateRequests;
- };
- class ConnectionState::Widget::ProxyIcon final : public Ui::RpWidget {
- public:
- ProxyIcon(QWidget *parent);
- void setToggled(bool toggled);
- void setOpacity(float64 opacity);
- protected:
- void paintEvent(QPaintEvent *e) override;
- private:
- void refreshCacheImages();
- float64 _opacity = 1.;
- QPixmap _cacheOn;
- QPixmap _cacheOff;
- bool _toggled = true;
- };
- ConnectionState::Widget::ProxyIcon::ProxyIcon(QWidget *parent) : RpWidget(parent) {
- resize(
- std::max(
- st::connectingRadial.size.width(),
- st::connectingProxyOn.width()),
- std::max(
- st::connectingRadial.size.height(),
- st::connectingProxyOn.height()));
- style::PaletteChanged(
- ) | rpl::start_with_next([=] {
- refreshCacheImages();
- }, lifetime());
- refreshCacheImages();
- }
- void ConnectionState::Widget::ProxyIcon::refreshCacheImages() {
- const auto prepareCache = [&](const style::icon &icon) {
- auto image = QImage(
- size() * style::DevicePixelRatio(),
- QImage::Format_ARGB32_Premultiplied);
- image.setDevicePixelRatio(style::DevicePixelRatio());
- image.fill(st::windowBg->c);
- {
- auto p = QPainter(&image);
- icon.paint(
- p,
- (width() - icon.width()) / 2,
- (height() - icon.height()) / 2,
- width());
- }
- return Ui::PixmapFromImage(std::move(image));
- };
- _cacheOn = prepareCache(st::connectingProxyOn);
- _cacheOff = prepareCache(st::connectingProxyOff);
- }
- void ConnectionState::Widget::ProxyIcon::setToggled(bool toggled) {
- if (_toggled != toggled) {
- _toggled = toggled;
- update();
- }
- }
- void ConnectionState::Widget::ProxyIcon::setOpacity(float64 opacity) {
- _opacity = opacity;
- if (_opacity == 0.) {
- hide();
- } else if (isHidden()) {
- show();
- }
- update();
- }
- void ConnectionState::Widget::ProxyIcon::paintEvent(QPaintEvent *e) {
- auto p = QPainter(this);
- p.setOpacity(_opacity);
- p.drawPixmap(0, 0, _toggled ? _cacheOn : _cacheOff);
- }
- bool ConnectionState::State::operator==(const State &other) const {
- return (type == other.type)
- && (useProxy == other.useProxy)
- && (underCursor == other.underCursor)
- && (updateReady == other.updateReady)
- && (waitTillRetry == other.waitTillRetry);
- }
- ConnectionState::ConnectionState(
- not_null<Ui::RpWidget*> parent,
- not_null<Main::Account*> account,
- rpl::producer<bool> shown)
- : _account(account)
- , _parent(parent)
- , _refreshTimer([=] { refreshState(); })
- , _currentLayout(computeLayout(_state)) {
- rpl::combine(
- std::move(shown),
- visibility()
- ) | rpl::start_with_next([=](bool shown, float64 visible) {
- if (!shown || visible == 0.) {
- _widget = nullptr;
- } else if (!_widget) {
- createWidget();
- }
- }, _lifetime);
- if (!Core::UpdaterDisabled()) {
- Core::UpdateChecker checker;
- rpl::merge(
- rpl::single(rpl::empty),
- checker.ready()
- ) | rpl::start_with_next([=] {
- refreshState();
- }, _lifetime);
- }
- rpl::combine(
- Core::App().settings().proxy().connectionTypeValue(),
- rpl::single(QRect()) | rpl::then(_parent->paintRequest())
- ) | rpl::start_with_next([=] {
- refreshState();
- }, _lifetime);
- }
- void ConnectionState::createWidget() {
- _widget = base::make_unique_q<Widget>(_parent, _account, _currentLayout);
- _widget->setVisible(!_forceHidden);
- updateWidth();
- rpl::combine(
- visibility(),
- _parent->heightValue(),
- _bottomSkip.value()
- ) | rpl::start_with_next([=](float64 visible, int height, int skip) {
- _widget->moveToLeft(0, anim::interpolate(
- height - st::connectingMargin.top(),
- height - _widget->height() - skip,
- visible));
- }, _widget->lifetime());
- _widget->refreshStateRequests(
- ) | rpl::start_with_next([=] {
- refreshState();
- }, _widget->lifetime());
- }
- void ConnectionState::raise() {
- if (_widget) {
- _widget->raise();
- }
- }
- void ConnectionState::finishAnimating() {
- if (_contentWidth.animating()) {
- _contentWidth.stop();
- updateWidth();
- }
- if (_visibility.animating()) {
- _visibility.stop();
- updateVisibility();
- }
- }
- void ConnectionState::setForceHidden(bool hidden) {
- _forceHidden = hidden;
- if (_widget) {
- _widget->setVisible(!hidden);
- }
- }
- void ConnectionState::setBottomSkip(int skip) {
- _bottomSkip = skip;
- }
- void ConnectionState::refreshState() {
- using Checker = Core::UpdateChecker;
- const auto state = [&]() -> State {
- const auto exposed = _parent->window()->windowHandle()
- && _parent->window()->windowHandle()->isExposed();
- const auto under = _widget && _widget->isOver();
- const auto ready = (Checker().state() == Checker::State::Ready);
- const auto state = _account->mtp().dcstate();
- const auto proxy = Core::App().settings().proxy().isEnabled();
- if (state == MTP::ConnectingState
- || state == MTP::DisconnectedState
- || (state < 0 && state > -600)) {
- return { State::Type::Connecting, proxy, exposed, under, ready };
- } else if (state < 0
- && state >= -kMinimalWaitingStateDuration
- && _state.type != State::Type::Waiting) {
- return { State::Type::Connecting, proxy, exposed, under, ready };
- } else if (state < 0) {
- const auto wait = ((-state) / 1000) + 1;
- return { State::Type::Waiting, proxy, exposed, under, ready, wait };
- }
- return { State::Type::Connected, proxy, exposed, under, ready };
- }();
- if (state.exposed && state.waitTillRetry > 0) {
- _refreshTimer.callOnce(kRefreshTimeout);
- }
- if (state == _state) {
- return;
- } else if (state.type == State::Type::Connecting
- && _state.type == State::Type::Connected) {
- const auto now = crl::now();
- if (!_connectingStartedAt) {
- _connectingStartedAt = now;
- _refreshTimer.callOnce(kConnectingStateDelay);
- return;
- }
- const auto applyConnectingAt = std::max(
- _connectingStartedAt + kConnectingStateDelay,
- kIgnoreStartConnectingFor);
- if (now < applyConnectingAt) {
- _refreshTimer.callOnce(applyConnectingAt - now);
- return;
- }
- }
- applyState(state);
- }
- void ConnectionState::applyState(const State &state) {
- const auto newLayout = computeLayout(state);
- const auto guard = gsl::finally([&] { updateWidth(); });
- _state = state;
- if (_currentLayout.visible != newLayout.visible) {
- changeVisibilityWithLayout(newLayout);
- return;
- }
- if (_currentLayout.contentWidth != newLayout.contentWidth) {
- if (!_currentLayout.contentWidth
- || !newLayout.contentWidth
- || _contentWidth.animating()) {
- _contentWidth.start(
- [=] { updateWidth(); },
- _currentLayout.contentWidth,
- newLayout.contentWidth,
- st::connectingDuration);
- }
- }
- const auto saved = _currentLayout;
- setLayout(newLayout);
- if (_currentLayout.text.isEmpty()
- && !saved.text.isEmpty()
- && _contentWidth.animating()) {
- _currentLayout.text = saved.text;
- _currentLayout.textWidth = saved.textWidth;
- }
- }
- void ConnectionState::changeVisibilityWithLayout(const Layout &layout) {
- Expects(_currentLayout.visible != layout.visible);
- const auto changeLayout = !_currentLayout.visible;
- _visibility.start(
- [=] { updateVisibility(); },
- layout.visible ? 0. : 1.,
- layout.visible ? 1. : 0.,
- st::connectingDuration);
- if (_contentWidth.animating()) {
- _contentWidth.start(
- [=] { updateWidth(); },
- _currentLayout.contentWidth,
- (changeLayout ? layout : _currentLayout).contentWidth,
- st::connectingDuration);
- }
- if (changeLayout) {
- setLayout(layout);
- } else {
- _currentLayout.visible = layout.visible;
- }
- }
- void ConnectionState::setLayout(const Layout &layout) {
- _currentLayout = layout;
- if (_widget) {
- _widget->setLayout(layout);
- }
- refreshProgressVisibility();
- }
- void ConnectionState::refreshProgressVisibility() {
- if (_widget) {
- _widget->setProgressVisibility(_contentWidth.animating()
- || _currentLayout.progressShown);
- }
- }
- void ConnectionState::updateVisibility() {
- const auto value = currentVisibility();
- if (value == 0. && _contentWidth.animating()) {
- _contentWidth.stop();
- updateWidth();
- }
- _visibilityValues.fire_copy(value);
- }
- float64 ConnectionState::currentVisibility() const {
- return _visibility.value(_currentLayout.visible ? 1. : 0.);
- }
- rpl::producer<float64> ConnectionState::visibility() const {
- return _visibilityValues.events_starting_with(currentVisibility());
- }
- auto ConnectionState::computeLayout(const State &state) const -> Layout {
- auto result = Layout();
- result.proxyEnabled = state.useProxy;
- result.progressShown = (state.type != State::Type::Connected);
- result.visible = state.exposed
- && !state.updateReady
- && (state.useProxy
- || state.type == State::Type::Connecting
- || state.type == State::Type::Waiting);
- switch (state.type) {
- case State::Type::Connecting:
- result.text = state.underCursor
- ? tr::lng_connecting(tr::now)
- : QString();
- break;
- case State::Type::Waiting:
- Assert(state.waitTillRetry > 0);
- result.text = tr::lng_reconnecting(
- tr::now,
- lt_count,
- state.waitTillRetry);
- break;
- }
- result.textWidth = st::normalFont->width(result.text);
- result.contentWidth = (result.textWidth > 0)
- ? (st::connectingTextPadding.left()
- + result.textWidth
- + st::connectingTextPadding.right())
- : 0;
- if (state.type == State::Type::Waiting) {
- result.contentWidth += st::connectingRetryLink.padding.left()
- + st::connectingRetryLink.font->width(
- tr::lng_reconnecting_try_now(tr::now))
- + st::connectingRetryLink.padding.right();
- }
- result.hasRetry = (state.type == State::Type::Waiting);
- return result;
- }
- void ConnectionState::updateWidth() {
- const auto current = _contentWidth.value(_currentLayout.contentWidth);
- const auto height = st::connectingLeft.height();
- const auto desired = QRect(0, 0, current, height).marginsAdded(
- style::margins(
- st::connectingLeft.width(),
- 0,
- st::connectingRight.width(),
- 0)
- ).marginsAdded(
- st::connectingMargin
- );
- if (_widget) {
- _widget->resize(desired.size());
- _widget->update();
- }
- refreshProgressVisibility();
- }
- ConnectionState::Widget::Widget(
- QWidget *parent,
- not_null<Main::Account*> account,
- const Layout &layout)
- : AbstractButton(parent)
- , _account(account)
- , _currentLayout(layout) {
- _proxyIcon = Ui::CreateChild<ProxyIcon>(this);
- _progress = Ui::CreateChild<Progress>(this);
- addClickHandler([=] {
- Ui::show(ProxiesBoxController::CreateOwningBox(account));
- });
- _progress->animationStepRequests(
- ) | rpl::start_with_next([=] {
- _refreshStateRequests.fire({});
- }, _progress->lifetime());
- }
- void ConnectionState::Widget::onStateChanged(
- AbstractButton::State was,
- StateChangeSource source) {
- Ui::PostponeCall(crl::guard(this, [=] {
- _refreshStateRequests.fire({});
- }));
- }
- rpl::producer<> ConnectionState::Widget::refreshStateRequests() const {
- return _refreshStateRequests.events();
- }
- void ConnectionState::Widget::paintEvent(QPaintEvent *e) {
- Painter p(this);
- PainterHighQualityEnabler hq(p);
- p.setPen(Qt::NoPen);
- p.setBrush(st::windowBg);
- const auto inner = innerRect();
- const auto content = contentRect();
- const auto text = textRect();
- const auto left = inner.topLeft();
- const auto right = content.topLeft() + QPoint(content.width(), 0);
- st::connectingLeftShadow.paint(p, left, width());
- st::connectingLeft.paint(p, left, width());
- st::connectingRightShadow.paint(p, right, width());
- st::connectingRight.paint(p, right, width());
- st::connectingBodyShadow.fill(p, content);
- st::connectingBody.fill(p, content);
- const auto available = text.width();
- if (available > 0 && !_currentLayout.text.isEmpty()) {
- p.setFont(st::normalFont);
- p.setPen(st::windowSubTextFg);
- if (available >= _currentLayout.textWidth) {
- p.drawTextLeft(
- text.x(),
- text.y(),
- width(),
- _currentLayout.text,
- _currentLayout.textWidth);
- } else {
- p.drawTextLeft(
- text.x(),
- text.y(),
- width(),
- st::normalFont->elided(_currentLayout.text, available));
- }
- }
- }
- QRect ConnectionState::Widget::innerRect() const {
- return rect().marginsRemoved(
- st::connectingMargin
- );
- }
- QRect ConnectionState::Widget::contentRect() const {
- return innerRect().marginsRemoved(style::margins(
- st::connectingLeft.width(),
- 0,
- st::connectingRight.width(),
- 0));
- }
- QRect ConnectionState::Widget::textRect() const {
- return contentRect().marginsRemoved(
- st::connectingTextPadding
- );
- }
- void ConnectionState::Widget::resizeEvent(QResizeEvent *e) {
- {
- const auto xShift = (height() - _progress->width()) / 2;
- const auto yShift = (height() - _progress->height()) / 2;
- _progress->moveToLeft(xShift, yShift);
- }
- {
- const auto xShift = (height() - _proxyIcon->width()) / 2;
- const auto yShift = (height() - _proxyIcon->height()) / 2;
- _proxyIcon->moveToRight(xShift, yShift);
- }
- updateRetryGeometry();
- }
- void ConnectionState::Widget::updateRetryGeometry() {
- if (!_retry) {
- return;
- }
- const auto text = textRect();
- const auto available = text.width() - _currentLayout.textWidth;
- if (available <= 0) {
- _retry->hide();
- } else {
- _retry->show();
- _retry->resize(
- std::min(available, _retry->naturalWidth()),
- innerRect().height());
- _retry->moveToLeft(
- text.x() + text.width() - _retry->width(),
- st::connectingMargin.top());
- }
- }
- void ConnectionState::Widget::setLayout(const Layout &layout) {
- _currentLayout = layout;
- _proxyIcon->setToggled(_currentLayout.proxyEnabled);
- refreshRetryLink(_currentLayout.hasRetry);
- }
- void ConnectionState::Widget::setProgressVisibility(bool visible) {
- if (_progress->isHidden() == visible) {
- _progress->setVisible(visible);
- }
- }
- void ConnectionState::Widget::refreshRetryLink(bool hasRetry) {
- if (hasRetry && !_retry) {
- _retry = base::make_unique_q<Ui::LinkButton>(
- this,
- tr::lng_reconnecting_try_now(tr::now),
- st::connectingRetryLink);
- _retry->addClickHandler([=] {
- _account->mtp().restart();
- });
- updateRetryGeometry();
- } else if (!hasRetry) {
- _retry = nullptr;
- }
- }
- } // namespace Window
|