/* 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_main_menu.h" #include "window/themes/window_theme.h" #include "window/window_session_controller.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/widgets/menu.h" #include "ui/widgets/popup_menu.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/shadow.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" #include "ui/text/text_utilities.h" #include "ui/special_buttons.h" #include "ui/empty_userpic.h" #include "base/call_delayed.h" #include "mainwindow.h" #include "storage/localstorage.h" #include "storage/storage_account.h" #include "support/support_templates.h" #include "settings/settings_common.h" #include "base/qt_signal_producer.h" #include "boxes/about_box.h" #include "boxes/peer_list_controllers.h" #include "calls/calls_box_controller.h" #include "lang/lang_keys.h" #include "core/click_handler_types.h" #include "core/core_settings.h" #include "core/application.h" #include "main/main_session.h" #include "main/main_session_settings.h" #include "main/main_account.h" #include "main/main_domain.h" #include "mtproto/mtp_instance.h" #include "mtproto/mtproto_config.h" #include "data/data_folder.h" #include "data/data_session.h" #include "data/data_user.h" #include "data/data_changes.h" #include "mainwidget.h" #include "app.h" #include "styles/style_window.h" #include "styles/style_widgets.h" #include "styles/style_dialogs.h" #include "styles/style_settings.h" #include "styles/style_boxes.h" #include #include namespace { constexpr auto kMinDiffIntensity = 0.25; float64 IntensityOfColor(QColor color) { return (0.299 * color.red() + 0.587 * color.green() + 0.114 * color.blue()) / 255.0; } bool IsShadowShown(const QImage &img, const QRect r, float64 intensityText) { for (auto x = r.x(); x < r.x() + r.width(); x++) { for (auto y = r.y(); y < r.y() + r.height(); y++) { const auto intensity = IntensityOfColor(QColor(img.pixel(x, y))); if ((std::abs(intensity - intensityText)) < kMinDiffIntensity) { return true; } } } return false; } } // namespace namespace Window { class MainMenu::AccountButton final : public Ui::RippleButton { public: AccountButton(QWidget *parent, not_null account); private: void paintEvent(QPaintEvent *e) override; const not_null _account; const style::Menu &_st; std::shared_ptr _userpicView; int _unreadBadge = 0; bool _unreadBadgeMuted = true; }; class MainMenu::ToggleAccountsButton final : public Ui::AbstractButton { public: ToggleAccountsButton(QWidget *parent, rpl::producer toggled); [[nodiscard]] rpl::producer rightSkip() const { return _rightSkip.value(); } private: void paintEvent(QPaintEvent *e) override; rpl::variable _rightSkip; Ui::Animations::Simple _toggledAnimation; bool _toggled = false; }; class MainMenu::ResetScaleButton final : public Ui::AbstractButton { public: ResetScaleButton(QWidget *parent); protected: void paintEvent(QPaintEvent *e) override; static constexpr auto kText = "100%"; }; MainMenu::AccountButton::AccountButton( QWidget *parent, not_null account) : RippleButton(parent, st::defaultRippleAnimation) , _account(account) , _st(st::mainMenu){ const auto height = _st.itemPadding.top() + _st.itemStyle.font->height + _st.itemPadding.bottom(); resize(width(), height); _account->sessionValue( ) | rpl::filter([=](Main::Session *session) { return (session != nullptr); }) | rpl::start_with_next([=](not_null session) { rpl::single( rpl::empty_value() ) | rpl::then( session->data().unreadBadgeChanges() ) | rpl::start_with_next([=] { _unreadBadge = session->data().unreadBadge(); _unreadBadgeMuted = session->data().unreadBadgeMuted(); update(); }, lifetime()); }, lifetime()); } void MainMenu::AccountButton::paintEvent(QPaintEvent *e) { Expects(_account->sessionExists()); const auto &session = _account->session(); auto p = Painter(this); const auto over = isOver(); p.fillRect(rect(), over ? _st.itemBgOver : _st.itemBg); paintRipple(p, 0, 0); const auto available = width() - 2 * _st.itemPadding.left(); session.user()->paintUserpicLeft( p, _userpicView, _st.itemIconPosition.x(), _st.itemIconPosition.y(), width(), height() - 2 * _st.itemIconPosition.y()); p.setPen(over ? _st.itemFgOver : _st.itemFg); session.user()->nameText().drawElided( p, _st.itemPadding.left(), _st.itemPadding.top(), available); } MainMenu::ToggleAccountsButton::ToggleAccountsButton( QWidget *parent, rpl::producer toggled) : AbstractButton(parent) { std::move( toggled ) | rpl::filter([=](bool value) { return (_toggled != value); }) | rpl::start_with_next([=](bool value) { _toggled = value; _toggledAnimation.start( [=] { update(); }, _toggled ? 0. : 1., _toggled ? 1. : 0., st::slideWrapDuration); }, lifetime()); _toggledAnimation.stop(); } void MainMenu::ToggleAccountsButton::paintEvent(QPaintEvent *e) { auto p = QPainter(this); const auto toggled = _toggledAnimation.value(_toggled ? 1. : 0.); const auto x = 0. + width() - st::mainMenuTogglePosition.x(); const auto y = 0. + height() - st::mainMenuTogglePosition.y(); const auto size = st::mainMenuToggleSize; const auto size2 = size / 2.; const auto sqrt2 = sqrt(2.); const auto stroke = (st::mainMenuToggleFourStrokes / 4.) / sqrt2; const auto left = x - size; const auto right = x + size; const auto bottom = y + size2; const auto top = y - size2; constexpr auto kPointCount = 6; std::array points = { { { left - stroke, bottom - stroke }, { x, bottom - stroke - size - stroke }, { right + stroke, bottom - stroke }, { right - stroke, bottom + stroke }, { x, bottom + stroke - size + stroke }, { left + stroke, bottom + stroke } } }; const auto alpha = -toggled * M_PI; const auto cosalpha = cos(alpha); const auto sinalpha = sin(alpha); for (auto &point : points) { auto px = point.x() - x; auto py = point.y() - y; point.setX(x + px * cosalpha - py * sinalpha); point.setY(y + py * cosalpha + px * sinalpha); } QPainterPath path; path.moveTo(points[0]); for (int i = 1; i != kPointCount; ++i) { path.lineTo(points[i]); } path.lineTo(points[0]); auto hq = PainterHighQualityEnabler(p); p.fillPath(path, st::mainMenuCoverFg); } MainMenu::ResetScaleButton::ResetScaleButton(QWidget *parent) : AbstractButton(parent) { const auto margin = st::mainMenuCloudButton.height - st::mainMenuCloudSize; const auto textWidth = st::mainMenuResetScaleFont->width(kText); const auto innerWidth = st::mainMenuResetScaleLeft + textWidth + st::mainMenuResetScaleRight; const auto width = margin + innerWidth; resize(width, st::mainMenuCloudButton.height); } void MainMenu::ResetScaleButton::paintEvent(QPaintEvent *e) { Painter p(this); const auto innerHeight = st::mainMenuCloudSize; const auto radius = innerHeight / 2; const auto margin = st::mainMenuCloudButton.height - st::mainMenuCloudSize; const auto textWidth = st::mainMenuResetScaleFont->width(kText); const auto innerWidth = st::mainMenuResetScaleLeft + textWidth + st::mainMenuResetScaleRight; const auto left = margin / 2; const auto top = margin / 2; p.setPen(Qt::NoPen); p.setBrush(st::mainMenuCloudBg); p.drawRoundedRect(left, top, innerWidth, innerHeight, radius, radius); st::settingsIconInterfaceScale.paint( p, left + st::mainMenuResetScaleIconLeft, top + ((innerHeight - st::settingsIconInterfaceScale.height()) / 2), width(), st::mainMenuCloudFg->c); p.setFont(st::mainMenuResetScaleFont); p.setPen(st::mainMenuCloudFg); p.drawText( left + st::mainMenuResetScaleLeft, top + st::mainMenuResetScaleTop + st::mainMenuResetScaleFont->ascent, kText); } MainMenu::MainMenu( QWidget *parent, not_null controller) : LayerWidget(parent) , _controller(controller) , _userpicButton( this, _controller, _controller->session().user(), Ui::UserpicButton::Role::Custom, st::mainMenuUserpic) , _toggleAccounts(this, Core::App().settings().mainMenuAccountsShownValue()) , _archiveButton(this, st::mainMenuCloudButton) , _scroll(this, st::defaultSolidScroll) , _inner(_scroll->setOwnedWidget( object_ptr(_scroll.data()))) , _accounts(_inner->add(object_ptr>( _inner.get(), object_ptr(_inner.get())))) , _shadow(_inner->add(object_ptr>( _inner.get(), object_ptr(_inner.get())))) , _menu(_inner->add( object_ptr(_inner.get(), st::mainMenu), { 0, st::mainMenuSkip, 0, 0 })) , _footer(_inner->add(object_ptr(_inner.get()))) , _telegram( Ui::CreateChild(_footer.get(), st::mainMenuTelegramLabel)) , _version( Ui::CreateChild( _footer.get(), st::mainMenuVersionLabel)) { setAttribute(Qt::WA_OpaquePaintEvent); setupArchiveButton(); setupUserpicButton(); setupAccounts(); setupAccountsToggle(); _nightThemeSwitch.setCallback([this] { if (const auto action = *_nightThemeAction) { const auto nightMode = Window::Theme::IsNightMode(); if (action->isChecked() != nightMode) { Window::Theme::ToggleNightMode(); Window::Theme::KeepApplied(); } } }); _footer->heightValue( ) | rpl::start_with_next([=] { _telegram->moveToLeft(st::mainMenuFooterLeft, _footer->height() - st::mainMenuTelegramBottom - _telegram->height()); _version->moveToLeft(st::mainMenuFooterLeft, _footer->height() - st::mainMenuVersionBottom - _version->height()); }, _footer->lifetime()); rpl::combine( heightValue(), _inner->heightValue() ) | rpl::start_with_next([=] { updateInnerControlsGeometry(); }, _inner->lifetime()); parentResized(); _menu->setTriggeredCallback([](QAction *action, int actionTop, Ui::Menu::TriggeredSource source) { emit action->triggered(); }); refreshMenu(); refreshBackground(); _telegram->setMarkedText(Ui::Text::Link( qsl("Telegram Desktop"), qsl("https://desktop.telegram.org"))); _telegram->setLinksTrusted(); _version->setRichText(textcmdLink(1, tr::lng_settings_current_version(tr::now, lt_version, currentVersionText())) + QChar(' ') + QChar(8211) + QChar(' ') + textcmdLink(2, tr::lng_menu_about(tr::now))); _version->setLink(1, std::make_shared(qsl("https://desktop.telegram.org/changelog"))); _version->setLink(2, std::make_shared([] { Ui::show(Box()); })); subscribe(_controller->session().downloaderTaskFinished(), [=] { update(); }); _controller->session().changes().peerUpdates( _controller->session().user(), Data::PeerUpdate::Flag::PhoneNumber ) | rpl::start_with_next([=] { updatePhone(); }, lifetime()); _controller->session().serverConfig().phoneCallsEnabled.changes( ) | rpl::start_with_next([=] { refreshMenu(); }, lifetime()); subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &update) { if (update.type == Window::Theme::BackgroundUpdate::Type::ApplyingTheme) { refreshMenu(); } if (update.type == Window::Theme::BackgroundUpdate::Type::New) { refreshBackground(); } }); updatePhone(); initResetScaleButton(); } void MainMenu::setupArchiveButton() { const auto showArchive = [=] { const auto folder = _controller->session().data().folderLoaded( Data::Folder::kId); if (folder) { _controller->openFolder(folder); Ui::hideSettingsAndLayer(); } }; const auto checkArchive = [=] { const auto folder = _controller->session().data().folderLoaded( Data::Folder::kId); return folder && !folder->chatsList()->empty() && _controller->session().settings().archiveInMainMenu(); }; _archiveButton->setVisible(checkArchive()); _archiveButton->setAcceptBoth(true); _archiveButton->clicks( ) | rpl::start_with_next([=](Qt::MouseButton which) { if (which == Qt::LeftButton) { showArchive(); return; } else if (which != Qt::RightButton) { return; } _contextMenu = base::make_unique_q(this); _contextMenu->addAction( tr::lng_context_archive_to_list(tr::now), [=] { _controller->session().settings().setArchiveInMainMenu(false); _controller->session().saveSettingsDelayed(); Ui::hideSettingsAndLayer(); }); _contextMenu->popup(QCursor::pos()); }, _archiveButton->lifetime()); _controller->session().data().chatsListChanges( ) | rpl::filter([](Data::Folder *folder) { return folder && (folder->id() == Data::Folder::kId); }) | rpl::start_with_next([=](Data::Folder *folder) { _archiveButton->setVisible(checkArchive()); update(); }, lifetime()); } void MainMenu::setupUserpicButton() { _userpicButton->setClickedCallback([=] { _controller->content()->choosePeer( _controller->session().userPeerId(), ShowAtUnreadMsgId); }); _userpicButton->show(); } void MainMenu::setupAccounts() { const auto inner = _accounts->entity(); inner->add(object_ptr(inner, st::mainMenuSkip)); _addAccount = setupAddAccount(inner); inner->add(object_ptr(inner, st::mainMenuSkip)); rpl::single( rpl::empty_value() ) | rpl::then(Core::App().domain().accountsChanges( )) | rpl::start_with_next([=] { const auto &list = Core::App().domain().accounts(); const auto exists = [&](not_null account) { for (const auto &[index, existing] : list) { if (account == existing.get()) { return true; } } return false; }; for (auto i = _watched.begin(); i != _watched.end();) { if (!exists(i->first)) { i = _watched.erase(i); } else { ++i; } } for (const auto &[index, account] : list) { if (_watched.emplace(account.get()).second) { account->sessionChanges( ) | rpl::start_with_next([=](Main::Session *session) { rebuildAccounts(); }, lifetime()); } } rebuildAccounts(); }, lifetime()); _accounts->toggleOn(Core::App().settings().mainMenuAccountsShownValue()); _accounts->finishAnimating(); _shadow->setDuration(0)->toggleOn(_accounts->shownValue()); } void MainMenu::rebuildAccounts() { const auto inner = _accounts->entity(); auto count = 0; for (auto &[account, button] : _watched) { if (!account->sessionExists()) { button = nullptr; } else if (!button) { button.reset(inner->insert( ++count, object_ptr(inner, account))); button->setClickedCallback([=] { auto activate = [=, guard = _accountSwitchGuard.make_guard()]{ if (guard) { Core::App().domain().activate(account); } }; base::call_delayed( st::defaultRippleAnimation.hideDuration, account, std::move(activate)); }); } else { ++count; } } inner->resizeToWidth(_accounts->width()); _addAccount->toggle( (count < Main::Domain::kMaxAccounts), anim::type::instant); } not_null*> MainMenu::setupAddAccount( not_null container) { const auto result = container->add( object_ptr>( container.get(), object_ptr( container.get(), st::defaultRippleAnimation)))->setDuration(0); const auto st = &st::mainMenu; const auto height = st->itemPadding.top() + st->itemStyle.font->height + st->itemPadding.bottom(); const auto button = result->entity(); button->resize(button->width(), height); button->paintRequest( ) | rpl::start_with_next([=] { auto p = Painter(button); const auto over = button->isOver(); p.fillRect(button->rect(), over ? st->itemBgOver : st->itemBg); button->paintRipple(p, 0, 0); const auto &icon = over ? st::mainMenuAddAccountOver : st::mainMenuAddAccount; icon.paint(p, st->itemIconPosition, width()); p.setPen(over ? st->itemFgOver : st->itemFg); p.setFont(st->itemStyle.font); p.drawTextLeft( st->itemPadding.left(), st->itemPadding.top(), width(), tr::lng_menu_add_account(tr::now)); }, button->lifetime()); const auto add = [=](MTP::Environment environment) { auto &domain = Core::App().domain(); if (domain.accounts().size() < Main::Domain::kMaxAccounts) { domain.activate(domain.add(environment)); } else { for (auto &[index, account] : domain.accounts()) { if (!account->sessionExists() && account->mtp().environment() == environment) { domain.activate(account.get()); break; } } } }; button->setAcceptBoth(true); button->clicks( ) | rpl::start_with_next([=](Qt::MouseButton which) { if (which == Qt::LeftButton) { add(MTP::Environment::Production); return; } else if (which != Qt::RightButton || !(button->clickModifiers() & Qt::ShiftModifier) || !(button->clickModifiers() & Qt::AltModifier)) { return; } _contextMenu = base::make_unique_q(this); _contextMenu->addAction("Production Server", [=] { add(MTP::Environment::Production); }); _contextMenu->addAction("Test Server", [=] { add(MTP::Environment::Test); }); _contextMenu->popup(QCursor::pos()); }, button->lifetime()); return result; } void MainMenu::setupAccountsToggle() { _toggleAccounts->show(); _toggleAccounts->setClickedCallback([=] { auto &settings = Core::App().settings(); const auto shown = !settings.mainMenuAccountsShown(); settings.setMainMenuAccountsShown(shown); }); _toggleAccounts->paintRequest( ) | rpl::start_with_next([=] { auto p = Painter(_toggleAccounts.data()); }, _toggleAccounts->lifetime()); } void MainMenu::parentResized() { resize(st::mainMenuWidth, parentWidget()->height()); } void MainMenu::refreshMenu() { _menu->clearActions(); if (!_controller->session().supportMode()) { const auto controller = _controller; _menu->addAction(tr::lng_create_group_title(tr::now), [] { App::wnd()->onShowNewGroup(); }, &st::mainMenuNewGroup, &st::mainMenuNewGroupOver); _menu->addAction(tr::lng_create_channel_title(tr::now), [] { App::wnd()->onShowNewChannel(); }, &st::mainMenuNewChannel, &st::mainMenuNewChannelOver); _menu->addAction(tr::lng_menu_contacts(tr::now), [=] { Ui::show(Box(std::make_unique(controller), [](not_null box) { box->addButton(tr::lng_close(), [box] { box->closeBox(); }); box->addLeftButton(tr::lng_profile_add_contact(), [] { App::wnd()->onShowAddContact(); }); })); }, &st::mainMenuContacts, &st::mainMenuContactsOver); if (_controller->session().serverConfig().phoneCallsEnabled.current()) { _menu->addAction(tr::lng_menu_calls(tr::now), [=] { Ui::show(Box(std::make_unique(controller), [](not_null box) { box->addButton(tr::lng_close(), [=] { box->closeBox(); }); box->addTopButton(st::callSettingsButton, [=] { App::wnd()->sessionController()->showSettings( Settings::Type::Calls, Window::SectionShow(anim::type::instant)); }); })); }, &st::mainMenuCalls, &st::mainMenuCallsOver); } } else { _menu->addAction(tr::lng_profile_add_contact(tr::now), [] { App::wnd()->onShowAddContact(); }, &st::mainMenuContacts, &st::mainMenuContactsOver); const auto fix = std::make_shared>(); *fix = _menu->addAction(qsl("Fix chats order"), [=] { (*fix)->setChecked(!(*fix)->isChecked()); _controller->session().settings().setSupportFixChatsOrder( (*fix)->isChecked()); _controller->session().saveSettings(); }, &st::mainMenuFixOrder, &st::mainMenuFixOrderOver); (*fix)->setCheckable(true); (*fix)->setChecked( _controller->session().settings().supportFixChatsOrder()); _menu->addAction(qsl("Reload templates"), [=] { _controller->session().supportTemplates().reload(); }, &st::mainMenuReload, &st::mainMenuReloadOver); } _menu->addAction(tr::lng_menu_settings(tr::now), [] { App::wnd()->showSettings(); }, &st::mainMenuSettings, &st::mainMenuSettingsOver); _nightThemeAction = std::make_shared>(); auto action = _menu->addAction(tr::lng_menu_night_mode(tr::now), [=] { if (auto action = *_nightThemeAction) { action->setChecked(!action->isChecked()); _nightThemeSwitch.callOnce(st::mainMenu.itemToggle.duration); } }, &st::mainMenuNightMode, &st::mainMenuNightModeOver); *_nightThemeAction = action; action->setCheckable(true); action->setChecked(Window::Theme::IsNightMode()); _menu->finishAnimating(); updatePhone(); } void MainMenu::refreshBackground() { const auto fill = QRect(0, 0, width(), st::mainMenuCoverHeight); const auto intensityText = IntensityOfColor(st::mainMenuCoverFg->c); QImage backgroundImage( st::mainMenuWidth * cIntRetinaFactor(), st::mainMenuCoverHeight * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); QPainter p(&backgroundImage); const auto drawShadow = [](QPainter &p) { st::mainMenuShadow.paint( p, 0, st::mainMenuCoverHeight - st::mainMenuShadow.height(), st::mainMenuWidth, IntensityOfColor(st::mainMenuCoverFg->c) < 0.5 ? Qt::white : Qt::black); }; // Solid color. if (const auto color = Window::Theme::Background()->colorForFill()) { const auto intensity = IntensityOfColor(*color); p.fillRect(fill, *color); if (std::abs(intensity - intensityText) < kMinDiffIntensity) { drawShadow(p); } _background = backgroundImage; return; } // Background image. const auto &pixmap = Window::Theme::Background()->pixmap(); QRect to, from; Window::Theme::ComputeBackgroundRects(fill, pixmap.size(), to, from); // Cut off the part of the background that is under text. const QRect underText( st::mainMenuCoverTextLeft, st::mainMenuCoverNameTop, std::max( st::semiboldFont->width( _controller->session().user()->nameText().toString()), st::normalFont->width(_phoneText)), st::semiboldFont->height * 2); p.drawPixmap(to, pixmap, from); if (IsShadowShown(backgroundImage, underText, intensityText)) { drawShadow(p); } _background = backgroundImage; } void MainMenu::resizeEvent(QResizeEvent *e) { _menu->setForceWidth(width()); _inner->resizeToWidth(width()); updateControlsGeometry(); } void MainMenu::updateControlsGeometry() { _userpicButton->moveToLeft(st::mainMenuUserpicLeft, st::mainMenuUserpicTop); if (_resetScaleButton) { _resetScaleButton->moveToRight(0, 0); _archiveButton->moveToRight(_resetScaleButton->width(), 0); } else { const auto offset = st::mainMenuCloudSize / 4; _archiveButton->moveToRight(offset, offset); } _toggleAccounts->setGeometry( 0, st::mainMenuCoverNameTop, width(), st::mainMenuCoverHeight - st::mainMenuCoverNameTop); const auto top = st::mainMenuCoverHeight; _scroll->setGeometry(0, top, width(), height() - top); updateInnerControlsGeometry(); } void MainMenu::updateInnerControlsGeometry() { const auto contentHeight = _accounts->height() + _shadow->height() + st::mainMenuSkip + _menu->height(); const auto available = height() - st::mainMenuCoverHeight - contentHeight; const auto footerHeight = std::max( available, st::mainMenuTelegramBottom + _telegram->height() + st::mainMenuSkip); if (_footer->height() != footerHeight) { _footer->resize(_footer->width(), footerHeight); } } void MainMenu::updatePhone() { _phoneText = App::formatPhone(_controller->session().user()->phone()); update(); } void MainMenu::paintEvent(QPaintEvent *e) { Painter p(this); const auto clip = e->rect(); const auto cover = QRect(0, 0, width(), st::mainMenuCoverHeight) .intersected(e->rect()); const auto background = Window::Theme::Background(); const auto isFill = background->tile() || background->colorForFill().has_value() || background->isMonoColorImage() || background->paper().isPattern() || Data::IsLegacy1DefaultWallPaper(background->paper()); if (!isFill && !_background.isNull()) { PainterHighQualityEnabler hq(p); p.drawImage(0, 0, _background); } if (!cover.isEmpty()) { const auto widthText = width() - 2 * st::mainMenuCoverTextLeft; if (isFill) { p.fillRect(cover, st::mainMenuCoverBg); } p.setPen(st::mainMenuCoverFg); p.setFont(st::semiboldFont); _controller->session().user()->nameText().drawLeftElided( p, st::mainMenuCoverTextLeft, st::mainMenuCoverNameTop, widthText, width()); p.setFont(st::normalFont); p.drawTextLeft(st::mainMenuCoverTextLeft, st::mainMenuCoverStatusTop, width(), _phoneText); // Draw Archive button. if (!_archiveButton->isHidden()) { const auto folder = _controller->session().data().folderLoaded( Data::Folder::kId); if (folder) { folder->paintUserpic( p, _archiveButton->x() + (_archiveButton->width() - st::mainMenuCloudSize) / 2, _archiveButton->y() + (_archiveButton->height() - st::mainMenuCloudSize) / 2, st::mainMenuCloudSize, isFill ? st::mainMenuCloudBg : st::msgServiceBg, isFill ? st::mainMenuCloudFg : st::msgServiceFg); } } } auto other = QRect(0, st::mainMenuCoverHeight, width(), height() - st::mainMenuCoverHeight).intersected(clip); if (!other.isEmpty()) { p.fillRect(other, st::mainMenuBg); } } void MainMenu::initResetScaleButton() { if (!window() || !window()->windowHandle()) { return; } const auto handle = window()->windowHandle(); rpl::single( handle->screen() ) | rpl::then( base::qt_signal_producer(handle, &QWindow::screenChanged) ) | rpl::filter([](QScreen *screen) { return screen != nullptr; }) | rpl::map([](QScreen * screen) { return rpl::single( screen->availableGeometry() ) | rpl::then( #ifdef OS_MAC_OLD base::qt_signal_producer(screen, &QScreen::virtualGeometryChanged) #else // OS_MAC_OLD base::qt_signal_producer(screen, &QScreen::availableGeometryChanged) #endif // OS_MAC_OLD ); }) | rpl::flatten_latest( ) | rpl::map([](QRect available) { return (available.width() >= st::windowMinWidth) && (available.height() >= st::windowMinHeight); }) | rpl::distinct_until_changed( ) | rpl::start_with_next([=](bool good) { if (good) { _resetScaleButton.destroy(); } else { _resetScaleButton.create(this); _resetScaleButton->addClickHandler([] { cSetConfigScale(style::kScaleDefault); Local::writeSettings(); App::restart(); }); _resetScaleButton->show(); updateControlsGeometry(); } }, lifetime()); } } // namespace Window