diff --git a/Telegram/Resources/icons/menu_add_account.png b/Telegram/Resources/icons/menu_add_account.png deleted file mode 100644 index 25de7ef71..000000000 Binary files a/Telegram/Resources/icons/menu_add_account.png and /dev/null differ diff --git a/Telegram/Resources/icons/menu_add_account@2x.png b/Telegram/Resources/icons/menu_add_account@2x.png deleted file mode 100644 index 70265c3b3..000000000 Binary files a/Telegram/Resources/icons/menu_add_account@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/menu_add_account@3x.png b/Telegram/Resources/icons/menu_add_account@3x.png deleted file mode 100644 index 9a111d9bf..000000000 Binary files a/Telegram/Resources/icons/menu_add_account@3x.png and /dev/null differ diff --git a/Telegram/Resources/icons/settings/add.png b/Telegram/Resources/icons/settings/add.png new file mode 100644 index 000000000..b4f18d5de Binary files /dev/null and b/Telegram/Resources/icons/settings/add.png differ diff --git a/Telegram/Resources/icons/settings/add@2x.png b/Telegram/Resources/icons/settings/add@2x.png new file mode 100644 index 000000000..d40c899cc Binary files /dev/null and b/Telegram/Resources/icons/settings/add@2x.png differ diff --git a/Telegram/Resources/icons/settings/add@3x.png b/Telegram/Resources/icons/settings/add@3x.png new file mode 100644 index 000000000..5a8daff62 Binary files /dev/null and b/Telegram/Resources/icons/settings/add@3x.png differ diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index dd209de13..00129ce93 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -682,7 +682,15 @@ void PaintUnreadBadge(Painter &p, const QRect &rect, const UnreadBadgeStyle &st) p.drawPixmap(rect.x() + sizehalf + bar, rect.y(), badgeData->right[index]); } -} // namepsace +[[nodiscard]] QString ComputeUnreadBadgeText( + const QString &unreadCount, + int allowDigits) { + return (allowDigits > 0) && (unreadCount.size() > allowDigits + 1) + ? qsl("..") + unreadCount.mid(unreadCount.size() - allowDigits) + : unreadCount; +} + +} // namespace const style::icon *ChatTypeIcon( not_null peer, @@ -716,6 +724,19 @@ UnreadBadgeStyle::UnreadBadgeStyle() , font(st::dialogsUnreadFont) { } +QSize CountUnreadBadgeSize( + const QString &unreadCount, + const UnreadBadgeStyle &st, + int allowDigits) { + const auto text = ComputeUnreadBadgeText(unreadCount, allowDigits); + const auto unreadRectHeight = st.size; + const auto unreadWidth = st.font->width(text); + return { + std::max(unreadWidth + 2 * st.padding, unreadRectHeight), + unreadRectHeight, + }; +} + QRect PaintUnreadBadge( Painter &p, const QString &unreadCount, @@ -723,10 +744,7 @@ QRect PaintUnreadBadge( int y, const UnreadBadgeStyle &st, int allowDigits) { - const auto text = (allowDigits > 0) && (unreadCount.size() > allowDigits + 1) - ? qsl("..") + unreadCount.mid(unreadCount.size() - allowDigits) - : unreadCount; - + const auto text = ComputeUnreadBadgeText(unreadCount, allowDigits); const auto unreadRectHeight = st.size; const auto unreadWidth = st.font->width(text); const auto unreadRectWidth = std::max( diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.h b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.h index 8f4841e7e..82fc73b59 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.h @@ -83,6 +83,10 @@ struct UnreadBadgeStyle { UnreadBadgeSize sizeId = UnreadBadgeInDialogs; style::font font; }; +[[nodiscard]] QSize CountUnreadBadgeSize( + const QString &unreadCount, + const UnreadBadgeStyle &st, + int allowDigits = 0); QRect PaintUnreadBadge( Painter &p, const QString &t, diff --git a/Telegram/SourceFiles/settings/settings_common.cpp b/Telegram/SourceFiles/settings/settings_common.cpp index 29a371ebd..3691e713a 100644 --- a/Telegram/SourceFiles/settings/settings_common.cpp +++ b/Telegram/SourceFiles/settings/settings_common.cpp @@ -57,7 +57,10 @@ Icon::Icon(IconDescriptor descriptor) : _icon(descriptor.icon) { return descriptor.background; }(); if (background) { - _background.emplace(st::settingsIconRadius, *background); + const auto radius = (descriptor.type == IconType::Rounded) + ? st::settingsIconRadius + : (std::min(_icon->width(), _icon->height()) / 2); + _background.emplace(radius, *background); } } diff --git a/Telegram/SourceFiles/settings/settings_common.h b/Telegram/SourceFiles/settings/settings_common.h index f00220f91..33f951192 100644 --- a/Telegram/SourceFiles/settings/settings_common.h +++ b/Telegram/SourceFiles/settings/settings_common.h @@ -69,9 +69,15 @@ inline constexpr auto kIconPurple = 6; inline constexpr auto kIconDarkOrange = 8; inline constexpr auto kIconGray = 9; +enum class IconType { + Rounded, + Round, +}; + struct IconDescriptor { const style::icon *icon = nullptr; int color = 0; // settingsIconBg{color}, 9 for settingsIconBgArchive. + IconType type = IconType::Rounded; const style::color *background = nullptr; explicit operator bool() const { diff --git a/Telegram/SourceFiles/window/window.style b/Telegram/SourceFiles/window/window.style index 95001ae31..c3de773c2 100644 --- a/Telegram/SourceFiles/window/window.style +++ b/Telegram/SourceFiles/window/window.style @@ -132,23 +132,18 @@ mainMenu: Menu(defaultMenu) { } mainMenuButton: SettingsButton(defaultSettingsButton) { font: semiboldFont; - padding: margins(61px, 11px, 22px, 9px); + padding: margins(61px, 11px, 20px, 9px); toggleSkip: 19px; iconLeft: 21px; } +mainMenuAddAccountButton: SettingsButton(mainMenuButton) { + iconLeft: 23px; +} mainMenuShadow: icon {{ "menu_shadow", windowShadowFg }}; -mainMenuAddAccount: icon {{ "menu_add_account", menuIconFg }}; -mainMenuAddAccountOver: icon {{ "menu_add_account", menuIconFgOver }}; -mainMenuAccountSize: 32px; -mainMenuAccountCheckPosition: point(7px, 5px); -mainMenuAccountCheckLine: 2px; -mainMenuAccountCheck: RoundCheckbox(defaultRoundCheckbox) { - size: 18px; - bgInactive: overviewCheckBg; - bgActive: overviewCheckBgActive; - check: icon {{ "account_check", overviewCheckFgActive }}; -} +mainMenuAddAccount: icon {{ "settings/add", settingsIconFg }}; +mainMenuAccountSize: 26px; +mainMenuAccountLine: 2px; mainMenuFooterLeft: 25px; mainMenuTelegramLabel: FlatLabel(defaultFlatLabel) { diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index 27c108fcc..7382873e1 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -163,28 +163,204 @@ void ShowCallsBox(not_null window) { namespace Window { -class MainMenu::AccountButton final : public Ui::RippleButton { -public: - AccountButton(QWidget *parent, not_null account); - -private: - void paintEvent(QPaintEvent *e) override; - void contextMenuEvent(QContextMenuEvent *e) override; - void paintUserpic(Painter &p); - - const not_null _session; - const style::Menu &_st; - std::shared_ptr _userpicView; - InMemoryKey _userpicKey = {}; - QImage _userpicCache; - base::unique_qptr _menu; - - Dialogs::Ui::UnreadBadgeStyle _unreadSt; - int _unreadBadge = 0; - bool _unreadBadgeMuted = true; - +struct UnreadBadge { + int count = 0; + bool muted = false; }; +void AddUnreadBadge( + not_null button, + rpl::producer value) { + struct State { + State(QWidget *parent) : widget(parent) { + widget.setAttribute(Qt::WA_TransparentForMouseEvents); + } + + Ui::RpWidget widget; + Dialogs::Ui::UnreadBadgeStyle st; + int count = 0; + QString string; + }; + const auto state = button->lifetime().make_state(button); + + std::move( + value + ) | rpl::start_with_next([=](UnreadBadge badge) { + state->st.muted = badge.muted; + state->count = badge.count; + if (!badge.count) { + state->widget.hide(); + return; + } + state->string = (state->count > 99) + ? "99+" + : QString::number(state->count); + state->widget.resize(CountUnreadBadgeSize(state->string, state->st)); + if (state->widget.isHidden()) { + state->widget.show(); + } + }, state->widget.lifetime()); + + state->widget.paintRequest( + ) | rpl::start_with_next([=] { + auto p = Painter(&state->widget); + Dialogs::Ui::PaintUnreadBadge( + p, + state->string, + state->widget.width(), + 0, + state->st); + }, state->widget.lifetime()); + + rpl::combine( + button->sizeValue(), + state->widget.sizeValue(), + state->widget.shownValue() + ) | rpl::start_with_next([=](QSize outer, QSize inner, bool shown) { + auto padding = button->st().padding; + if (shown) { + state->widget.moveToRight( + padding.right(), + (outer.height() - inner.height()) / 2, + outer.width()); + padding.setRight( + padding.right() + inner.width() + button->st().font->spacew); + } + button->setPaddingOverride(padding); + }, state->widget.lifetime()); +} + +[[nodiscard]] object_ptr MakeAccountButton( + QWidget *parent, + not_null account, + Fn callback) { + const auto active = (account == &Core::App().activeAccount()); + const auto session = &account->session(); + const auto user = session->user(); + + auto text = rpl::single( + user->name + ) | rpl::then(session->changes().realtimeNameUpdates( + user + ) | rpl::map([=] { + return user->name; + })); + auto result = object_ptr( + parent, + std::move(text), + st::mainMenuAddAccountButton); + const auto raw = result.data(); + + struct State { + State(QWidget *parent) : userpic(parent) { + userpic.setAttribute(Qt::WA_TransparentForMouseEvents); + } + + Ui::RpWidget userpic; + std::shared_ptr view; + base::unique_qptr menu; + }; + const auto state = raw->lifetime().make_state(raw); + + if (!active) { + AddUnreadBadge(raw, rpl::single( + rpl::empty_value() + ) | rpl::then( + session->data().unreadBadgeChanges() + ) | rpl::map([=] { + auto &owner = session->data(); + return UnreadBadge{ + owner.unreadBadge(), + owner.unreadBadgeMuted(), + }; + })); + } + + const auto userpicSkip = 2 * st::mainMenuAccountLine + st::lineWidth; + const auto userpicSize = st::mainMenuAccountSize + + userpicSkip * 2; + raw->heightValue( + ) | rpl::start_with_next([=](int height) { + const auto left = st::mainMenuAddAccountButton.iconLeft + + (st::mainMenuAddAccount.width() - userpicSize) / 2; + const auto top = (height - userpicSize) / 2; + state->userpic.setGeometry(left, top, userpicSize, userpicSize); + }, state->userpic.lifetime()); + + state->userpic.paintRequest( + ) | rpl::start_with_next([=] { + auto p = Painter(&state->userpic); + const auto size = st::mainMenuAccountSize; + const auto line = st::mainMenuAccountLine; + const auto skip = 2 * line + st::lineWidth; + const auto full = size + skip * 2; + user->paintUserpicLeft(p, state->view, skip, skip, full, size); + if (active) { + const auto shift = st::lineWidth + (line * 0.5); + const auto diameter = full - 2 * shift; + const auto rect = QRectF(shift, shift, diameter, diameter); + auto hq = PainterHighQualityEnabler(p); + auto pen = st::settingsIconBg4->p; // The same as '+' in add. + pen.setWidthF(line); + p.setPen(pen); + p.setBrush(Qt::NoBrush); + p.drawEllipse(rect); + } + }, state->userpic.lifetime()); + + raw->setAcceptBoth(true); + raw->clicks( + ) | rpl::start_with_next([=](Qt::MouseButton which) { + if (which == Qt::LeftButton) { + callback(); + return; + } else if (which != Qt::RightButton) { + return; + } + const auto addAction = [&]( + const QString &text, + Fn callback, + const style::icon *icon) { + return state->menu->addAction( + text, + crl::guard(raw, std::move(callback)), + icon); + }; + if (!state->menu && IsAltShift(raw->clickModifiers())) { + state->menu = base::make_unique_q( + raw, + st::popupMenuWithIcons); + MenuAddMarkAsReadAllChatsAction(&session->data(), addAction); + state->menu->popup(QCursor::pos()); + return; + } + if (&session->account() == &Core::App().activeAccount() + || state->menu) { + return; + } + state->menu = base::make_unique_q( + raw, + st::popupMenuWithIcons); + addAction(tr::lng_menu_activate(tr::now), [=] { + Core::App().domain().activate(&session->account()); + }, &st::menuIconProfile); + addAction(tr::lng_settings_logout(tr::now), [=] { + const auto callback = [=](Fn &&close) { + close(); + Core::App().logoutWithChecks(&session->account()); + }; + Ui::show(Box( + tr::lng_sure_logout(tr::now), + tr::lng_settings_logout(tr::now), + st::attentionBoxButton, + crl::guard(session, callback))); + }, &st::menuIconLeave); + state->menu->popup(QCursor::pos()); + }, raw->lifetime()); + + return result; +} + class MainMenu::ToggleAccountsButton final : public Ui::AbstractButton { public: explicit ToggleAccountsButton(QWidget *parent); @@ -221,164 +397,6 @@ protected: }; -MainMenu::AccountButton::AccountButton( - QWidget *parent, - not_null account) -: RippleButton(parent, st::defaultRippleAnimation) -, _session(&account->session()) -, _st(st::mainMenu){ - const auto height = _st.itemPadding.top() - + _st.itemStyle.font->height - + _st.itemPadding.bottom(); - resize(width(), height); - - style::PaletteChanged( - ) | rpl::start_with_next([=] { - _userpicKey = {}; - }, lifetime()); - - rpl::single( - rpl::empty_value() - ) | rpl::then( - _session->data().unreadBadgeChanges() - ) | rpl::start_with_next([=] { - _unreadBadge = _session->data().unreadBadge(); - _unreadBadgeMuted = _session->data().unreadBadgeMuted(); - update(); - }, lifetime()); -} - -void MainMenu::AccountButton::paintUserpic(Painter &p) { - const auto size = st::mainMenuAccountSize; - const auto iconSize = height() - 2 * _st.itemIconPosition.y(); - const auto shift = (size - iconSize) / 2; - const auto x = _st.itemIconPosition.x() - shift; - const auto y = (height() - size) / 2; - - const auto check = (&_session->account() - == &Core::App().domain().active()); - const auto user = _session->user(); - if (!check) { - user->paintUserpicLeft(p, _userpicView, x, y, width(), size); - return; - } - const auto added = st::mainMenuAccountCheck.size; - const auto cacheSize = QSize(size + added, size + added) - * cIntRetinaFactor(); - const auto key = user->userpicUniqueKey(_userpicView); - if (_userpicKey != key) { - _userpicKey = key; - if (_userpicCache.size() != cacheSize) { - _userpicCache = QImage(cacheSize, QImage::Format_ARGB32_Premultiplied); - _userpicCache.setDevicePixelRatio(cRetinaFactor()); - } - _userpicCache.fill(Qt::transparent); - - auto q = Painter(&_userpicCache); - user->paintUserpicLeft(q, _userpicView, 0, 0, width(), size); - - const auto iconDiameter = st::mainMenuAccountCheck.size; - const auto iconLeft = size + st::mainMenuAccountCheckPosition.x() - iconDiameter; - const auto iconTop = size + st::mainMenuAccountCheckPosition.y() - iconDiameter; - const auto iconEllipse = QRect(iconLeft, iconTop, iconDiameter, iconDiameter); - auto iconBorderPen = QPen(Qt::transparent); - const auto line = st::mainMenuAccountCheckLine; - iconBorderPen.setWidth(line); - - PainterHighQualityEnabler hq(q); - q.setCompositionMode(QPainter::CompositionMode_Source); - q.setPen(iconBorderPen); - q.setBrush(st::dialogsUnreadBg); - q.drawEllipse(iconEllipse); - - q.setCompositionMode(QPainter::CompositionMode_SourceOver); - st::mainMenuAccountCheck.check.paintInCenter(q, iconEllipse); - } - p.drawImage(x, y, _userpicCache); -} - -void MainMenu::AccountButton::paintEvent(QPaintEvent *e) { - auto p = Painter(this); - const auto over = isOver(); - p.fillRect(rect(), over ? _st.itemBgOver : _st.itemBg); - paintRipple(p, 0, 0); - - paintUserpic(p); - - auto available = width() - _st.itemPadding.left(); - if (_unreadBadge - && (&_session->account() != &Core::App().activeAccount())) { - _unreadSt.muted = _unreadBadgeMuted; - const auto string = (_unreadBadge > 99) - ? "99+" - : QString::number(_unreadBadge); - const auto skip = _st.itemPadding.right() - - st::mainMenu.itemToggleShift; - const auto unreadRight = width() - skip; - const auto unreadTop = (height() - _unreadSt.size) / 2; - const auto badge = Dialogs::Ui::PaintUnreadBadge( - p, - string, - unreadRight, - unreadTop, - _unreadSt); - available -= badge.width() - + skip - + st::mainMenu.itemStyle.font->spacew; - } else { - available -= _st.itemPadding.right(); - } - - p.setPen(over ? _st.itemFgOver : _st.itemFg); - _session->user()->nameText().drawElided( - p, - _st.itemPadding.left(), - _st.itemPadding.top(), - available); -} - -void MainMenu::AccountButton::contextMenuEvent(QContextMenuEvent *e) { - if (!_menu && IsAltShift(e->modifiers())) { - _menu = base::make_unique_q( - this, - st::popupMenuWithIcons); - const auto addAction = [&]( - const QString &text, - Fn callback, - const style::icon *icon) { - return _menu->addAction( - text, - crl::guard(this, std::move(callback)), - icon); - }; - MenuAddMarkAsReadAllChatsAction(&_session->data(), addAction); - _menu->popup(QCursor::pos()); - return; - } - if (&_session->account() == &Core::App().activeAccount() || _menu) { - return; - } - _menu = base::make_unique_q( - this, - st::popupMenuWithIcons); - _menu->addAction(tr::lng_menu_activate(tr::now), crl::guard(this, [=] { - Core::App().domain().activate(&_session->account()); - }), &st::menuIconProfile); - _menu->addAction(tr::lng_settings_logout(tr::now), crl::guard(this, [=] { - const auto session = _session; - const auto callback = [=](Fn &&close) { - close(); - Core::App().logoutWithChecks(&session->account()); - }; - Ui::show(Box( - tr::lng_sure_logout(tr::now), - tr::lng_settings_logout(tr::now), - st::attentionBoxButton, - crl::guard(session, callback))); - }), &st::menuIconLeave); - _menu->popup(QCursor::pos()); -} - MainMenu::ToggleAccountsButton::ToggleAccountsButton(QWidget *parent) : AbstractButton(parent) { rpl::single( @@ -865,9 +883,7 @@ void MainMenu::rebuildAccounts() { if (!account->sessionExists()) { button = nullptr; } else if (!button) { - button.reset(inner->add( - object_ptr(inner, account))); - button->setClickedCallback([=] { + button.reset(inner->add(MakeAccountButton(inner, account, [=] { if (_reordering) { return; } @@ -885,7 +901,7 @@ void MainMenu::rebuildAccounts() { st::defaultRippleAnimation.hideDuration, account, std::move(activate)); - }); + }))); } } inner->resizeToWidth(_accounts->width()); @@ -897,39 +913,23 @@ void MainMenu::rebuildAccounts() { _reorder->start(); } -not_null*> MainMenu::setupAddAccount( +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); + using namespace Settings; - 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 result = container->add( + object_ptr>( + container.get(), + CreateButton( + container.get(), + tr::lng_menu_add_account(), + st::mainMenuAddAccountButton, + { + &st::mainMenuAddAccount, + kIconLightBlue, + IconType::Round, + })))->setDuration(0); + const auto button = result->entity(); const auto add = [=](MTP::Environment environment) { Core::App().preventOrInvoke([=] { diff --git a/Telegram/SourceFiles/window/window_main_menu.h b/Telegram/SourceFiles/window/window_main_menu.h index f861613d2..2ab1244cb 100644 --- a/Telegram/SourceFiles/window/window_main_menu.h +++ b/Telegram/SourceFiles/window/window_main_menu.h @@ -51,15 +51,15 @@ protected: } private: - class AccountButton; class ToggleAccountsButton; class ResetScaleButton; void setupUserpicButton(); void setupAccounts(); void setupAccountsToggle(); - [[nodiscard]] not_null*> setupAddAccount( - not_null container); + [[nodiscard]] auto setupAddAccount( + not_null container) + -> not_null*>; void setupArchive(); void setupMenu(); void rebuildAccounts(); @@ -78,9 +78,9 @@ private: not_null _inner; base::flat_map< not_null, - base::unique_qptr> _watched; + base::unique_qptr> _watched; not_null*> _accounts; - Ui::SlideWrap *_addAccount = nullptr; + Ui::SlideWrap *_addAccount = nullptr; not_null*> _shadow; not_null _menu; not_null _footer; diff --git a/Telegram/lib_ui b/Telegram/lib_ui index c1f44ca8c..43c61172d 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit c1f44ca8c739cd7183c1e85eb1b7a62f1ada12d6 +Subproject commit 43c61172d8d2ef08c2c190d6b3425823727ea9c9