From f2a58627146c315316993a3188d4054a32ce03ed Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 25 Sep 2017 19:06:53 +0300 Subject: [PATCH] Add members list to info profile. --- Telegram/SourceFiles/app.cpp | 25 +- Telegram/SourceFiles/history/history.style | 26 +- Telegram/SourceFiles/info/info.style | 108 +++++- Telegram/SourceFiles/info/info_layer_wrap.cpp | 3 + Telegram/SourceFiles/info/info_memento.cpp | 18 +- Telegram/SourceFiles/info/info_memento.h | 8 +- .../info/profile/info_profile_button.cpp | 6 +- .../info/profile/info_profile_button.h | 1 - .../info/profile/info_profile_cover.cpp | 327 +++++++++--------- .../info/profile/info_profile_cover.h | 61 ++-- .../profile/info_profile_inner_widget.cpp | 16 +- .../info/profile/info_profile_inner_widget.h | 10 +- .../info/profile/info_profile_members.cpp | 281 +++++++++++++++ .../info/profile/info_profile_members.h | 89 +++++ .../info/profile/info_profile_values.cpp | 20 ++ .../info/profile/info_profile_values.h | 2 + .../info/profile/info_profile_widget.cpp | 1 + Telegram/SourceFiles/mainwidget.cpp | 2 + Telegram/SourceFiles/profile/profile.style | 10 +- .../profile/profile_block_group_members.cpp | 6 +- .../profile/profile_block_peer_list.cpp | 46 +-- .../profile/profile_block_widget.cpp | 5 +- .../profile/profile_block_widget.h | 3 +- Telegram/SourceFiles/rpl/mappers.h | 39 +++ Telegram/SourceFiles/ui/abstract_button.cpp | 5 +- Telegram/SourceFiles/ui/focus_persister.h | 55 +++ Telegram/SourceFiles/ui/rp_widget.h | 25 +- .../SourceFiles/ui/widgets/scroll_area.cpp | 3 +- Telegram/SourceFiles/ui/widgets/scroll_area.h | 3 +- Telegram/SourceFiles/ui/widgets/widgets.style | 31 ++ Telegram/SourceFiles/ui/wrap/slide_wrap.cpp | 3 +- Telegram/SourceFiles/ui/wrap/slide_wrap.h | 1 - .../SourceFiles/window/top_bar_widget.cpp | 3 + Telegram/gyp/telegram_sources.txt | 1 + 34 files changed, 943 insertions(+), 300 deletions(-) create mode 100644 Telegram/SourceFiles/info/profile/info_profile_members.cpp create mode 100644 Telegram/SourceFiles/info/profile/info_profile_members.h create mode 100644 Telegram/SourceFiles/ui/focus_persister.h diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 4eb3493f112f53..0be3c24292753b 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -961,23 +961,28 @@ namespace { } void feedChatAdmins(const MTPDupdateChatAdmins &d) { - ChatData *chat = App::chat(d.vchat_id.v); + auto chat = App::chat(d.vchat_id.v); if (chat->version <= d.vversion.v) { - bool badVersion = (chat->version + 1 < d.vversion.v); - if (badVersion) { - chat->invalidateParticipants(); - Auth().api().requestPeer(chat); - } + auto wasCanEdit = chat->canEdit(); + auto badVersion = (chat->version + 1 < d.vversion.v); chat->version = d.vversion.v; if (mtpIsTrue(d.venabled)) { - if (!badVersion) { - chat->invalidateParticipants(); - } chat->flags |= MTPDchat::Flag::f_admins_enabled; } else { chat->flags &= ~MTPDchat::Flag::f_admins_enabled; } - Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::AdminsChanged); + if (badVersion || mtpIsTrue(d.venabled)) { + chat->invalidateParticipants(); + Auth().api().requestPeer(chat); + } + if (wasCanEdit != chat->canEdit()) { + Notify::peerUpdatedDelayed( + chat, + Notify::PeerUpdate::Flag::ChatCanEdit); + } + Notify::peerUpdatedDelayed( + chat, + Notify::PeerUpdate::Flag::AdminsChanged); } } diff --git a/Telegram/SourceFiles/history/history.style b/Telegram/SourceFiles/history/history.style index 433f0294229a29..9dc6b09db7e2cb 100644 --- a/Telegram/SourceFiles/history/history.style +++ b/Telegram/SourceFiles/history/history.style @@ -90,27 +90,7 @@ membersInnerDropdown: InnerDropdown(defaultInnerDropdown) { scrollMargin: margins(0px, 5px, 0px, 5px); scrollPadding: margins(0px, 3px, 0px, 3px); } -membersInnerItem: ProfilePeerListItem { - left: 0px; - bottom: 0px; - button: OutlineButton { - outlineWidth: 0px; - - textBg: windowBg; - textBgOver: windowBgOver; - - textFg: windowSubTextFg; - textFgOver: windowSubTextFgOver; - - font: normalFont; - padding: margins(11px, 5px, 11px, 5px); - - ripple: defaultRippleAnimation; - } - statusFg: windowSubTextFg; - statusFgOver: windowSubTextFgOver; - statusFgActive: windowActiveTextFg; -} +membersInnerItem: defaultProfileMemberItem; historyFileOutImage: icon {{ "history_file_image", historyFileOutIconFg }}; historyFileOutImageSelected: icon {{ "history_file_image", historyFileOutIconFgSelected }}; @@ -456,14 +436,14 @@ historyAdminLogCancelSearch: CrossButton { height: 54px; cross: CrossAnimation { - size: 36px; + size: 32px; skip: 10px; stroke: 2px; minScale: 0.3; } crossFg: menuIconFg; crossFgOver: menuIconFgOver; - crossPosition: point(4px, 9px); + crossPosition: point(6px, 11px); duration: 150; loadingPeriod: 1000; diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index cfa4400d71130b..c4ef2e5a5bb24c 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -158,6 +158,7 @@ infoIconMediaPhoto: icon {{ "info_media_photo", infoIconFg }}; infoInformationIconPosition: point(25px, 12px); infoNotificationsIconPosition: point(20px, 5px); infoSharedMediaIconPosition: point(20px, 24px); +infoMembersIconPosition: point(20px, 15px); infoLabeledOneLine: FlatLabel(defaultFlatLabel) { width: 0px; // No need to set minWidth in one-line text. @@ -176,6 +177,16 @@ infoLabeled: FlatLabel(infoLabeledOneLine) { margin: margins(5px, 5px, 5px, 5px); } +infoBlockHeaderLabel: FlatLabel(infoProfileStatusLabel) { + textFg: windowBoldFg; + style: TextStyle(defaultTextStyle) { + font: semiboldFont; + linkFont: semiboldFont; + linkFontOver: semiboldFont; + } +} +infoBlockHeaderPosition: point(79px, 22px); + infoProfileToggle: Toggle(defaultToggle) { diameter: 16px; width: 14px; @@ -209,14 +220,93 @@ infoMainButton: InfoProfileButton(infoProfileButton) { textFgOver: lightButtonFgOver; } infoSharedMediaCoverHeight: 62px; -infoSharedMediaLabelPosition: point(79px, 22px); -infoSharedMediaLabel: FlatLabel(infoProfileStatusLabel) { - textFg: windowBoldFg; - style: TextStyle(defaultTextStyle) { - font: semiboldFont; - linkFont: semiboldFont; - linkFontOver: semiboldFont; - } -} infoSharedMediaButton: infoProfileButton; infoSharedMediaBottomSkip: 12px; + +infoMembersHeader: 56px; +infoMembersItem: ProfilePeerListItem(defaultProfileMemberItem) { + photoPosition: point(18px, 6px); + namePosition: point(79px, 11px); + statusPosition: point(79px, 31px); +} +infoMembersButtonPosition: point(12px, 9px); +infoMembersButtonIconPosition: point(6px, 6px); +infoMembersButton: IconButton(defaultIconButton) { + width: 44px; + height: 44px; + iconPosition: infoMembersButtonIconPosition; + rippleAreaPosition: point(0px, 0px); + rippleAreaSize: 44px; + ripple: RippleAnimation(defaultRippleAnimation) { + color: windowBgOver; + } +} +infoMembersAddMember: IconButton(infoMembersButton) { + icon: icon {{ "info_add_member", menuIconFg }}; + iconOver: icon {{ "info_add_member", menuIconFgOver }}; +} +infoMembersSearch: IconButton(infoMembersButton) { + icon: icon {{ + "top_bar_search", + menuIconFg, + infoMembersButtonIconPosition + }}; + iconOver: icon {{ + "top_bar_search", + menuIconFgOver, + infoMembersButtonIconPosition + }}; + iconPosition: point(0px, 0px); +} +infoMembersSearchActive: icon { + { size(44px, 44px), windowBg }, + { + "top_bar_search", + menuIconFgOver, + infoMembersButtonIconPosition + } +}; +infoMembersSearchActiveLayer: icon { + { size(44px, 44px), boxBg }, + { + "top_bar_search", + menuIconFgOver, + infoMembersButtonIconPosition + } +}; +infoMembersSearchField: FlatInput(defaultFlatInput) { + textColor: windowFg; + bgColor: topBarBg; + bgActive: topBarBg; + + font: font(fsize); + + borderWidth: 0px; + borderColor: topBarBg; + borderActive: topBarBg; + + width: 100px; + height: 32px; + textMrg: margins(0px, 0px, 0px, 0px); +} +infoMembersCancelSearch: CrossButton { + width: 44px; + height: 44px; + + cross: CrossAnimation { + size: 44px; + skip: 16px; + stroke: 2px; + minScale: 0.3; + } + crossFg: menuIconFg; + crossFgOver: menuIconFgOver; + crossPosition: point(0px, 0px); + + duration: 150; + loadingPeriod: 1000; + ripple: RippleAnimation(defaultRippleAnimation) { + color: windowBgOver; + } +} +infoMembersSearchTop: 15px; diff --git a/Telegram/SourceFiles/info/info_layer_wrap.cpp b/Telegram/SourceFiles/info/info_layer_wrap.cpp index 29ee97d2bb6d76..1be85188de9c57 100644 --- a/Telegram/SourceFiles/info/info_layer_wrap.cpp +++ b/Telegram/SourceFiles/info/info_layer_wrap.cpp @@ -23,6 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "info/info_memento.h" #include "info/info_top_bar.h" #include "ui/rp_widget.h" +#include "ui/focus_persister.h" #include "ui/widgets/buttons.h" #include "window/section_widget.h" #include "window/window_controller.h" @@ -106,6 +107,7 @@ void LayerWrap::parentResized() { auto parentSize = parentWidget()->size(); auto parentWidth = parentSize.width(); if (parentWidth < MinimalSupportedWidth()) { + Ui::FocusPersister persister(this); auto localCopy = _controller; auto memento = MoveMemento(std::move(_content), Wrap::Narrow); localCopy->hideSpecialLayer(anim::type::instant); @@ -124,6 +126,7 @@ void LayerWrap::parentResized() { } bool LayerWrap::takeToThirdSection() { + Ui::FocusPersister persister(this); auto localCopy = _controller; auto memento = MoveMemento(std::move(_content), Wrap::Side); localCopy->hideSpecialLayer(anim::type::instant); diff --git a/Telegram/SourceFiles/info/info_memento.cpp b/Telegram/SourceFiles/info/info_memento.cpp index 3516e3cff2a2a4..7c16a41c653821 100644 --- a/Telegram/SourceFiles/info/info_memento.cpp +++ b/Telegram/SourceFiles/info/info_memento.cpp @@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "info/info_memento.h" #include +#include #include "window/window_controller.h" #include "ui/widgets/scroll_area.h" #include "lang/lang_keys.h" @@ -51,7 +52,7 @@ ContentWidget::ContentWidget( void ContentWidget::setWrap(Wrap wrap) { if (_wrap != wrap) { _wrap = wrap; - wrapUpdatedHook(); + _wrapChanges.fire_copy(_wrap); update(); } } @@ -62,7 +63,6 @@ void ContentWidget::resizeEvent(QResizeEvent *e) { QMargins(0, _scrollTopSkip, 0, 0)); if (_scroll->geometry() != scrollGeometry) { _scroll->setGeometry(scrollGeometry); - _inner->setMinimumHeight(_scroll->height()); _inner->resizeToWidth(_scroll->width()); } @@ -100,14 +100,18 @@ void ContentWidget::setGeometryWithTopMoved( Ui::RpWidget *ContentWidget::doSetInnerWidget( object_ptr inner, int scrollTopSkip) { + using namespace rpl::mappers; + _inner = _scroll->setOwnedWidget(std::move(inner)); _inner->move(0, 0); - scrollTopValue() - | rpl::start([this, inner = _inner](int value) { - inner->setVisibleTopBottom( - value, - value + _scroll->height()); // TODO rpl::combine + rpl::combine( + _scroll->scrollTopValue(), + _scroll->heightValue(), + _inner->desiredHeightValue(), + tuple($1, $1 + $2, $3)) + | rpl::start([inner = _inner](int top, int bottom, int desired) { + inner->setVisibleTopBottom(top, bottom); }, _inner->lifetime()); return _inner; } diff --git a/Telegram/SourceFiles/info/info_memento.h b/Telegram/SourceFiles/info/info_memento.h index 2c0959b0a58854..70d692dc0b694e 100644 --- a/Telegram/SourceFiles/info/info_memento.h +++ b/Telegram/SourceFiles/info/info_memento.h @@ -121,8 +121,8 @@ class ContentWidget : public Ui::RpWidget { not_null controller() const { return _controller; } - Wrap wrap() const { - return _wrap; + rpl::producer wrapValue() const { + return _wrapChanges.events_starting_with_copy(_wrap); } void resizeEvent(QResizeEvent *e) override; @@ -132,9 +132,6 @@ class ContentWidget : public Ui::RpWidget { int scrollTopSave() const; void scrollTopRestore(int scrollTop); - virtual void wrapUpdatedHook() { - } - private: RpWidget *doSetInnerWidget( object_ptr inner, @@ -143,6 +140,7 @@ class ContentWidget : public Ui::RpWidget { const not_null _controller; const not_null _peer; Wrap _wrap = Wrap::Layer; + rpl::event_stream _wrapChanges; int _scrollTopSkip = 0; object_ptr _scroll; diff --git a/Telegram/SourceFiles/info/profile/info_profile_button.cpp b/Telegram/SourceFiles/info/profile/info_profile_button.cpp index 4bebe050acbcd5..767159aa2721c4 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_button.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_button.cpp @@ -46,7 +46,7 @@ Button::Button( } Button *Button::toggleOn(rpl::producer &&toggled) { - _toggleOnLifetime.destroy(); + Expects(_toggle == nullptr); _toggle = std::make_unique( isOver() ? _st.toggleOver : _st.toggle, false, @@ -54,11 +54,11 @@ Button *Button::toggleOn(rpl::producer &&toggled) { clicks() | rpl::start([this](auto) { _toggle->setCheckedAnimated(!_toggle->checked()); - }, _toggleOnLifetime); + }, lifetime()); std::move(toggled) | rpl::start([this](bool toggled) { _toggle->setCheckedAnimated(toggled); - }, _toggleOnLifetime); + }, lifetime()); _toggle->finishAnimation(); return this; } diff --git a/Telegram/SourceFiles/info/profile/info_profile_button.h b/Telegram/SourceFiles/info/profile/info_profile_button.h index f146252391bdfe..bb039683a0576b 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_button.h +++ b/Telegram/SourceFiles/info/profile/info_profile_button.h @@ -61,7 +61,6 @@ class Button : public Ui::RippleButton { int _originalWidth = 0; int _textWidth = 0; std::unique_ptr _toggle; - rpl::lifetime _toggleOnLifetime; }; diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp index b8e6ed5521d975..e0494c34f5e990 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp @@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "info/profile/info_profile_cover.h" #include +#include #include "info/profile/info_profile_values.h" #include "lang/lang_keys.h" #include "styles/style_info.h" @@ -36,6 +37,102 @@ namespace Info { namespace Profile { namespace { +class SectionToggle : public Ui::AbstractCheckView { +public: + SectionToggle( + const style::InfoToggle &st, + bool checked, + base::lambda updateCallback); + + QSize getSize() const override; + void paint( + Painter &p, + int left, + int top, + int outerWidth, + TimeMs ms) override; + QImage prepareRippleMask() const override; + bool checkRippleStartPosition(QPoint position) const override; + +private: + QSize rippleSize() const; + + const style::InfoToggle &_st; + +}; + +SectionToggle::SectionToggle( + const style::InfoToggle &st, + bool checked, + base::lambda updateCallback) +: AbstractCheckView(st.duration, checked, std::move(updateCallback)) +, _st(st) { +} + +QSize SectionToggle::getSize() const { + return QSize(_st.size, _st.size); +} + +void SectionToggle::paint( + Painter &p, + int left, + int top, + int outerWidth, + TimeMs ms) { + auto sqrt2 = sqrt(2.); + auto vLeft = rtlpoint(left + _st.skip, 0, outerWidth).x() + 0.; + auto vTop = top + _st.skip + 0.; + auto vWidth = _st.size - 2 * _st.skip; + auto vHeight = _st.size - 2 * _st.skip; + auto vStroke = _st.stroke / sqrt2; + constexpr auto kPointCount = 6; + std::array pathV = { { + { vLeft, vTop + (vHeight / 4.) + vStroke }, + { vLeft + vStroke, vTop + (vHeight / 4.) }, + { vLeft + (vWidth / 2.), vTop + (vHeight * 3. / 4.) - vStroke }, + { vLeft + vWidth - vStroke, vTop + (vHeight / 4.) }, + { vLeft + vWidth, vTop + (vHeight / 4.) + vStroke }, + { vLeft + (vWidth / 2.), vTop + (vHeight * 3. / 4.) + vStroke }, + } }; + + auto toggled = currentAnimationValue(ms); + auto alpha = (toggled - 1.) * M_PI_2; + auto cosalpha = cos(alpha); + auto sinalpha = sin(alpha); + auto shiftx = vLeft + (vWidth / 2.); + auto shifty = vTop + (vHeight / 2.); + for (auto &point : pathV) { + auto x = point.x() - shiftx; + auto y = point.y() - shifty; + point.setX(shiftx + x * cosalpha - y * sinalpha); + point.setY(shifty + y * cosalpha + x * sinalpha); + } + QPainterPath path; + path.moveTo(pathV[0]); + for (int i = 1; i != kPointCount; ++i) { + path.lineTo(pathV[i]); + } + path.lineTo(pathV[0]); + + PainterHighQualityEnabler hq(p); + p.fillPath(path, _st.color); +} + +QImage SectionToggle::prepareRippleMask() const { + return Ui::RippleAnimation::ellipseMask(rippleSize()); +} + +QSize SectionToggle::rippleSize() const { + return getSize() + 2 * QSize( + _st.rippleAreaPadding, + _st.rippleAreaPadding); +} + +bool SectionToggle::checkRippleStartPosition(QPoint position) const { + return QRect(QPoint(0, 0), rippleSize()).contains(position); + +} + auto MembersStatusText(int count) { return lng_chat_status_members(lt_count, count); }; @@ -59,8 +156,55 @@ auto ChatStatusText(int fullCount, int onlineCount, bool isGroup) { } // namespace +SectionWithToggle *SectionWithToggle::setToggleShown( + rpl::producer &&shown) { + _toggle.create( + this, + QString(), + st::infoToggleCheckbox, + std::make_unique( + st::infoToggle, + false, + [this] { _toggle->updateCheck(); })); + _toggle->hide(); + _toggle->lower(); + _toggle->setCheckAlignment(style::al_right); + widthValue() + | rpl::start([this](int newValue) { + _toggle->setGeometry(0, 0, newValue, height()); + }, _toggle->lifetime()); + std::move(shown) + | rpl::start([this](bool shown) { + if (_toggle->isHidden() == shown) { + _toggle->setVisible(shown); + _toggleShown.fire_copy(shown); + } + }, lifetime()); + return this; +} + +rpl::producer SectionWithToggle::toggledValue() const { + return _toggle + ? (rpl::single(_toggle->checked()) + | rpl::then( + base::ObservableViewer(_toggle->checkedChanged))) + : rpl::never(); +} + +rpl::producer SectionWithToggle::toggleShownValue() const { + return _toggleShown.events_starting_with( + _toggle && !_toggle->isHidden()); +} + +int SectionWithToggle::toggleSkip() const { + return (!_toggle || _toggle->isHidden()) + ? 0 + : st::infoToggleCheckbox.checkPosition.x() + + _toggle->checkRect().width(); +} + Cover::Cover(QWidget *parent, not_null peer) -: FixedHeightWidget( +: SectionWithToggle( parent, st::infoProfilePhotoTop + st::infoProfilePhotoSize @@ -80,7 +224,11 @@ Cover::Cover(QWidget *parent, not_null peer) } void Cover::setupChildGeometry() { - widthValue() + using namespace rpl::mappers; + rpl::combine( + toggleShownValue(), + widthValue(), + $2) | rpl::start([this](int newWidth) { _userpic->moveToLeft( st::infoProfilePhotoLeft, @@ -100,30 +248,6 @@ Cover *Cover::setOnlineCount(rpl::producer &&count) { return this; } -Cover *Cover::setToggleShown(rpl::producer &&shown) { - _toggle.create( - this, - QString(), - st::infoToggleCheckbox, - std::make_unique( - st::infoToggle, - false, - [this] { _toggle->updateCheck(); })); - _toggle->lower(); - _toggle->setCheckAlignment(style::al_right); - widthValue() - | rpl::start([this](int newValue) { - _toggle->setGeometry(0, 0, newValue, height()); - }, _toggle->lifetime()); - std::move(shown) - | rpl::start([this](bool shown) { - if (_toggle->isHidden() == shown) { - _toggle->setVisible(shown); - } - }, lifetime()); - return this; -} - void Cover::initViewers() { using Flag = Notify::PeerUpdate::Flag; PeerUpdateValue(_peer, Flag::PhotoChanged) @@ -201,11 +325,8 @@ void Cover::refreshStatusText() { void Cover::refreshNameGeometry(int newWidth) { auto nameWidth = newWidth - st::infoProfileNameLeft - - st::infoProfileNameRight; - if (_toggle) { - nameWidth -= st::infoToggleCheckbox.checkPosition.x() - + _toggle->checkRect().width(); - } + - st::infoProfileNameRight + - toggleSkip(); _name->resizeToWidth(nameWidth); _name->moveToLeft( st::infoProfileNameLeft, @@ -216,11 +337,8 @@ void Cover::refreshNameGeometry(int newWidth) { void Cover::refreshStatusGeometry(int newWidth) { auto statusWidth = newWidth - st::infoProfileStatusLeft - - st::infoProfileStatusRight; - if (_toggle) { - statusWidth -= st::infoToggleCheckbox.checkPosition.x() - + _toggle->checkRect().width(); - } + - st::infoProfileStatusRight + - toggleSkip(); _status->resizeToWidth(statusWidth); _status->moveToLeft( st::infoProfileStatusLeft, @@ -228,145 +346,38 @@ void Cover::refreshStatusGeometry(int newWidth) { newWidth); } -rpl::producer Cover::toggledValue() const { - return _toggle - ? (rpl::single(_toggle->checked()) - | rpl::then( - base::ObservableViewer(_toggle->checkedChanged))) - : rpl::never(); -} - QMargins SharedMediaCover::getMargins() const { return QMargins(0, 0, 0, st::infoSharedMediaBottomSkip); } SharedMediaCover::SharedMediaCover(QWidget *parent) -: FixedHeightWidget(parent, st::infoSharedMediaCoverHeight) { +: SectionWithToggle(parent, st::infoSharedMediaCoverHeight) { createLabel(); } void SharedMediaCover::createLabel() { + using namespace rpl::mappers; auto label = object_ptr( this, Lang::Viewer(lng_profile_shared_media) | ToUpperValue(), - st::infoSharedMediaLabel); + st::infoBlockHeaderLabel); label->setAttribute(Qt::WA_TransparentForMouseEvents); - widthValue() - | rpl::start([weak = label.data()](int newWidth) { - weak->resizeToNaturalWidth(newWidth - - st::infoSharedMediaLabelPosition.x() - - st::infoSharedMediaButton.padding.right()); + rpl::combine( + toggleShownValue(), + widthValue(), + $2) + | rpl::start([this, weak = label.data()](int newWidth) { + auto availableWidth = newWidth + - st::infoBlockHeaderPosition.x() + - st::infoSharedMediaButton.padding.right() + - toggleSkip(); + weak->resizeToWidth(availableWidth); weak->moveToLeft( - st::infoSharedMediaLabelPosition.x(), - st::infoSharedMediaLabelPosition.y(), + st::infoBlockHeaderPosition.x(), + st::infoBlockHeaderPosition.y(), newWidth); }, label->lifetime()); } -SharedMediaCover *SharedMediaCover::setToggleShown( - rpl::producer &&shown) { - _toggle.create( - this, - QString(), - st::infoToggleCheckbox, - std::make_unique( - st::infoToggle, - false, - [this] { _toggle->updateCheck(); })); - _toggle->lower(); - _toggle->setCheckAlignment(style::al_right); - widthValue() - | rpl::start([this](int newValue) { - _toggle->setGeometry(0, 0, newValue, height()); - }, _toggle->lifetime()); - std::move(shown) - | rpl::start([this](bool shown) { - if (_toggle->isHidden() == shown) { - _toggle->setVisible(shown); - } - }, lifetime()); - return this; -} - -rpl::producer SharedMediaCover::toggledValue() const { - return _toggle - ? (rpl::single(_toggle->checked()) - | rpl::then( - base::ObservableViewer(_toggle->checkedChanged))) - : rpl::never(); -} - -SectionToggle::SectionToggle( - const style::InfoToggle &st, - bool checked, - base::lambda updateCallback) -: AbstractCheckView(st.duration, checked, std::move(updateCallback)) -, _st(st) { -} - -QSize SectionToggle::getSize() const { - return QSize(_st.size, _st.size); -} - -void SectionToggle::paint( - Painter &p, - int left, - int top, - int outerWidth, - TimeMs ms) { - auto sqrt2 = sqrt(2.); - auto vLeft = rtlpoint(left + _st.skip, 0, outerWidth).x() + 0.; - auto vTop = top + _st.skip + 0.; - auto vWidth = _st.size - 2 * _st.skip; - auto vHeight = _st.size - 2 * _st.skip; - auto vStroke = _st.stroke / sqrt2; - constexpr auto kPointCount = 6; - std::array pathV = { { - { vLeft, vTop + (vHeight / 4.) + vStroke }, - { vLeft + vStroke, vTop + (vHeight / 4.) }, - { vLeft + (vWidth / 2.), vTop + (vHeight * 3. / 4.) - vStroke }, - { vLeft + vWidth - vStroke, vTop + (vHeight / 4.) }, - { vLeft + vWidth, vTop + (vHeight / 4.) + vStroke }, - { vLeft + (vWidth / 2.), vTop + (vHeight * 3. / 4.) + vStroke }, - } }; - - auto toggled = currentAnimationValue(ms); - auto alpha = (toggled - 1.) * M_PI_2; - auto cosalpha = cos(alpha); - auto sinalpha = sin(alpha); - auto shiftx = vLeft + (vWidth / 2.); - auto shifty = vTop + (vHeight / 2.); - for (auto &point : pathV) { - auto x = point.x() - shiftx; - auto y = point.y() - shifty; - point.setX(shiftx + x * cosalpha - y * sinalpha); - point.setY(shifty + y * cosalpha + x * sinalpha); - } - QPainterPath path; - path.moveTo(pathV[0]); - for (int i = 1; i != kPointCount; ++i) { - path.lineTo(pathV[i]); - } - path.lineTo(pathV[0]); - - PainterHighQualityEnabler hq(p); - p.fillPath(path, _st.color); -} - -QImage SectionToggle::prepareRippleMask() const { - return Ui::RippleAnimation::ellipseMask(rippleSize()); -} - -QSize SectionToggle::rippleSize() const { - return getSize() + 2 * QSize( - _st.rippleAreaPadding, - _st.rippleAreaPadding); -} - -bool SectionToggle::checkRippleStartPosition(QPoint position) const { - return QRect(QPoint(0, 0), rippleSize()).contains(position); - -} - } // namespace Profile } // namespace Info diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.h b/Telegram/SourceFiles/info/profile/info_profile_cover.h index ff58e66ddda9d9..9d5bad30c93286 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.h +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.h @@ -40,13 +40,33 @@ class SlideWrap; namespace Info { namespace Profile { -class Cover : public Ui::FixedHeightWidget { +class SectionWithToggle : public Ui::FixedHeightWidget { +public: + using FixedHeightWidget::FixedHeightWidget; + + SectionWithToggle *setToggleShown(rpl::producer &&shown); + rpl::producer toggledValue() const; + +protected: + rpl::producer toggleShownValue() const; + int toggleSkip() const; + +private: + object_ptr _toggle = { nullptr }; + rpl::event_stream _toggleShown; + +}; + +class Cover : public SectionWithToggle { public: Cover(QWidget *parent, not_null peer); Cover *setOnlineCount(rpl::producer &&count); - Cover *setToggleShown(rpl::producer &&shown); - rpl::producer toggledValue() const; + + Cover *setToggleShown(rpl::producer &&shown) { + return static_cast( + SectionWithToggle::setToggleShown(std::move(shown))); + } private: void setupChildGeometry(); @@ -64,49 +84,24 @@ class Cover : public Ui::FixedHeightWidget { object_ptr<::Profile::UserpicButton> _userpic; object_ptr _name = { nullptr }; object_ptr _status = { nullptr }; - object_ptr _toggle = { nullptr }; //object_ptr _dropArea = { nullptr }; }; -class SharedMediaCover : public Ui::FixedHeightWidget { +class SharedMediaCover : public SectionWithToggle { public: SharedMediaCover(QWidget *parent); - SharedMediaCover *setToggleShown(rpl::producer &&shown); - rpl::producer toggledValue() const; + SharedMediaCover *setToggleShown(rpl::producer &&shown) { + return static_cast( + SectionWithToggle::setToggleShown(std::move(shown))); + } QMargins getMargins() const override; private: void createLabel(); - object_ptr _toggle = { nullptr }; - -}; - -class SectionToggle : public Ui::AbstractCheckView { -public: - SectionToggle( - const style::InfoToggle &st, - bool checked, - base::lambda updateCallback); - - QSize getSize() const override; - void paint( - Painter &p, - int left, - int top, - int outerWidth, - TimeMs ms) override; - QImage prepareRippleMask() const override; - bool checkRippleStartPosition(QPoint position) const override; - -private: - QSize rippleSize() const; - - const style::InfoToggle &_st; - }; } // namespace Profile diff --git a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp index 674661f4bb7e0f..56ffb844805249 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp @@ -27,6 +27,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "info/profile/info_profile_values.h" #include "info/profile/info_profile_cover.h" #include "info/profile/info_profile_icon.h" +#include "info/profile/info_profile_members.h" #include "boxes/abstract_box.h" #include "boxes/add_contact_box.h" #include "mainwidget.h" @@ -44,12 +45,13 @@ namespace Profile { InnerWidget::InnerWidget( QWidget *parent, + rpl::producer &&wrapValue, not_null controller, not_null peer) : RpWidget(parent) , _controller(controller) , _peer(peer) -, _content(setupContent(this)) { +, _content(setupContent(this, std::move(wrapValue))) { _content->heightValue() | rpl::start([this](int height) { TWidget::resizeToWidth(width()); @@ -67,7 +69,8 @@ rpl::producer InnerWidget::canHideDetails() const { } object_ptr InnerWidget::setupContent( - RpWidget *parent) const { + RpWidget *parent, + rpl::producer &&wrapValue) const { auto result = object_ptr(parent); auto cover = result->add(object_ptr( result, @@ -93,6 +96,12 @@ object_ptr InnerWidget::setupContent( // setupChannelActions(result, channel); // } } + if (_peer->isChat() || _peer->isMegagroup()) { + result->add(object_ptr( + result, + std::move(wrapValue), + _peer)); + } return std::move(result); } @@ -361,8 +370,7 @@ object_ptr> InnerWidget::createSlideSkipWidget( void InnerWidget::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { - _visibleTop = visibleTop; - _visibleBottom = visibleBottom; + setChildVisibleTopBottom(_content, visibleTop, visibleBottom); } void InnerWidget::saveState(not_null memento) { diff --git a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.h b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.h index 4edf08b83c358d..8cd87d1f2df4d9 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.h +++ b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.h @@ -34,6 +34,9 @@ class SlideWrap; } // namespace Ui namespace Info { + +enum class Wrap; + namespace Profile { class Memento; @@ -42,6 +45,7 @@ class InnerWidget final : public Ui::RpWidget { public: InnerWidget( QWidget *parent, + rpl::producer &&wrapValue, not_null controller, not_null peer); @@ -64,7 +68,9 @@ class InnerWidget final : public Ui::RpWidget { int visibleBottom) override; private: - object_ptr setupContent(RpWidget *parent) const; + object_ptr setupContent( + RpWidget *parent, + rpl::producer &&wrapValue) const; object_ptr setupDetails(RpWidget *parent) const; object_ptr setupSharedMedia(RpWidget *parent) const; object_ptr setupMuteToggle(RpWidget *parent) const; @@ -86,8 +92,6 @@ class InnerWidget final : public Ui::RpWidget { not_null _controller; not_null _peer; - int _visibleTop = 0; - int _visibleBottom = 0; int _minHeight = 0; object_ptr _content; diff --git a/Telegram/SourceFiles/info/profile/info_profile_members.cpp b/Telegram/SourceFiles/info/profile/info_profile_members.cpp new file mode 100644 index 00000000000000..92d0f719c180e5 --- /dev/null +++ b/Telegram/SourceFiles/info/profile/info_profile_members.cpp @@ -0,0 +1,281 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org +*/ +#include "info/profile/info_profile_members.h" + +#include +#include "info/profile/info_profile_values.h" +#include "info/profile/info_profile_icon.h" +#include "info/profile/info_profile_values.h" +#include "info/info_memento.h" +#include "profile/profile_block_group_members.h" +#include "ui/widgets/labels.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" +#include "styles/style_info.h" +#include "lang/lang_keys.h" +#include "boxes/confirm_box.h" +#include "boxes/peer_list_controllers.h" + +namespace Info { +namespace Profile { +namespace { + +constexpr auto kEnableSearchMembersAfterCount = 50; + +} // namespace + +Members::Members( + QWidget *parent, + rpl::producer &&wrapValue, + not_null peer) +: RpWidget(parent) +, _peer(peer) +, _labelWrap(this) +, _label(setupHeader()) +, _addMember(this, st::infoMembersAddMember) +, _searchField( + this, + st::infoMembersSearchField, + langFactory(lng_participant_filter)) +, _search(this, st::infoMembersSearch) +, _cancelSearch(this, st::infoMembersCancelSearch) +, _list(setupList(this)) { + setupButtons(); + std::move(wrapValue) + | rpl::start([this](Wrap wrap) { + _wrap = wrap; + updateSearchOverrides(); + }, lifetime()); +} + +object_ptr Members::setupHeader() { + auto result = object_ptr( + _labelWrap, + MembersCountValue(_peer) + | rpl::map([](int count) { + return lng_chat_status_members(lt_count, count); + }) + | ToUpperValue(), + st::infoBlockHeaderLabel); + result->setAttribute(Qt::WA_TransparentForMouseEvents); + return result; +} + +void Members::setupButtons() { + using namespace rpl::mappers; + + _searchField->hide(); + _cancelSearch->hideFast(); + + auto addMemberShown = CanAddMemberValue(_peer) + | rpl::start_spawning(lifetime()); + widthValue() + | rpl::start([button = _addMember.data()](int newWidth) { + button->moveToRight( + st::infoMembersButtonPosition.x(), + st::infoMembersButtonPosition.y(), + newWidth); + }, _addMember->lifetime()); + _addMember->showOn(rpl::duplicate(addMemberShown)); + _addMember->clicks() // TODO throttle(ripple duration) + | rpl::start([this](auto&&) { + this->addMember(); + }, _addMember->lifetime()); + + auto searchShown = MembersCountValue(_peer) + | rpl::map($1 >= kEnableSearchMembersAfterCount) + | rpl::distinct_until_changed() + | rpl::start_spawning(lifetime()); + _search->showOn(rpl::duplicate(searchShown)); + _search->clicks() + | rpl::start([this](auto&&) { + this->showSearch(); + }, _search->lifetime()); + _cancelSearch->clicks() + | rpl::start([this](auto&&) { + this->cancelSearch(); + }, _cancelSearch->lifetime()); + + rpl::combine( + std::move(addMemberShown), + std::move(searchShown)) + | rpl::start([this](auto&&) { + this->resizeToWidth(width()); + }, lifetime()); + + object_ptr( + this, + st::infoIconMembers, + st::infoMembersIconPosition)->lower(); +} + +object_ptr Members::setupList( + RpWidget *parent) const { + auto result = object_ptr( + parent, + _peer, + ::Profile::GroupMembersWidget::TitleVisibility::Hidden, + st::infoMembersItem); + result->moveToLeft(0, st::infoMembersHeader); + parent->widthValue() + | rpl::start([list = result.data()](int newWidth) { + list->resizeToWidth(newWidth); + }, result->lifetime()); + result->heightValue() + | rpl::start([parent](int listHeight) { + auto newHeight = (listHeight > 0) + ? (st::infoMembersHeader + listHeight) + : 0; + parent->resize(parent->width(), newHeight); + }, result->lifetime()); + return result; +} + +int Members::resizeGetHeight(int newWidth) { + auto availableWidth = newWidth + - st::infoMembersButtonPosition.x(); + if (!_addMember->isHidden()) { + availableWidth -= st::infoMembersAddMember.width; + } + + auto cancelLeft = availableWidth - _cancelSearch->width(); + _cancelSearch->moveToLeft( + cancelLeft, + st::infoMembersButtonPosition.y()); + + auto searchShownLeft = st::infoMembersIconPosition.x() + - st::infoMembersSearch.iconPosition.x(); + auto searchHiddenLeft = availableWidth - _search->width(); + auto searchShown = _searchShownAnimation.current(_searchShown ? 1. : 0.); + auto searchCurrentLeft = anim::interpolate( + searchHiddenLeft, + searchShownLeft, + searchShown); + _search->moveToLeft( + searchCurrentLeft, + st::infoMembersButtonPosition.y()); + + auto fieldLeft = anim::interpolate( + cancelLeft, + st::infoBlockHeaderPosition.x(), + searchShown); + _searchField->setGeometryToLeft( + fieldLeft, + st::infoMembersSearchTop, + cancelLeft - fieldLeft, + _searchField->height()); + + _labelWrap->resize( + searchCurrentLeft - st::infoBlockHeaderPosition.x(), + _label->height()); + _labelWrap->moveToLeft( + st::infoBlockHeaderPosition.x(), + st::infoBlockHeaderPosition.y(), + newWidth); + + _label->resizeToWidth(searchHiddenLeft); + _label->moveToLeft(0, 0); + + return heightNoMargins(); +} + +void Members::addMember() { + if (auto chat = _peer->asChat()) { + if (chat->count >= Global::ChatSizeMax() && chat->amCreator()) { + Ui::show(Box(chat)); + } else { + AddParticipantsBoxController::Start(chat); + } + } else if (auto channel = _peer->asChannel()) { + if (channel->mgInfo) { + auto &participants = channel->mgInfo->lastParticipants; + AddParticipantsBoxController::Start(channel, { participants.cbegin(), participants.cend() }); + } + } +} + +void Members::showSearch() { + if (!_searchShown) { + toggleSearch(); + } +} + +void Members::toggleSearch() { + _searchShown = !_searchShown; + _cancelSearch->toggleAnimated(_searchShown); + _searchShownAnimation.start( + [this] { searchAnimationCallback(); }, + _searchShown ? 0. : 1., + _searchShown ? 1. : 0., + st::slideWrapDuration); + _search->setDisabled(_searchShown); + if (_searchShown) { + _searchField->show(); + _searchField->setFocus(); + } else { + setFocus(); + } +} + +void Members::searchAnimationCallback() { + if (!_searchShownAnimation.animating()) { + _searchField->setVisible(_searchShown); + updateSearchOverrides(); + _search->setPointerCursor(!_searchShown); + } + resizeToWidth(width()); +} + +void Members::updateSearchOverrides() { + auto iconOverride = !_searchShown + ? nullptr + : (_wrap == Wrap::Layer) + ? &st::infoMembersSearchActiveLayer + : &st::infoMembersSearchActive; + _search->setIconOverride(iconOverride, iconOverride); +} + +void Members::cancelSearch() { + if (_searchShown) { + if (!_searchField->getLastText().isEmpty()) { + _searchField->setText(QString()); + _searchField->updatePlaceholder(); + _searchField->setFocus(); + applySearch(); + } else { + toggleSearch(); + } + } +} + +void Members::applySearch() { + +} + +void Members::visibleTopBottomUpdated( + int visibleTop, + int visibleBottom) { + setChildVisibleTopBottom(_list, visibleTop, visibleBottom); +} + +} // namespace Profile +} // namespace Info + diff --git a/Telegram/SourceFiles/info/profile/info_profile_members.h b/Telegram/SourceFiles/info/profile/info_profile_members.h new file mode 100644 index 00000000000000..cf06515f462410 --- /dev/null +++ b/Telegram/SourceFiles/info/profile/info_profile_members.h @@ -0,0 +1,89 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "ui/rp_widget.h" + +namespace Ui { +class FlatInput; +class CrossButton; +class IconButton; +class FlatLabel; +} // namespace Ui + +namespace Profile { +class GroupMembersWidget; +} // namespace Profile + +namespace Info { + +enum class Wrap; + +namespace Profile { + +class Members : public Ui::RpWidget { +public: + Members( + QWidget *parent, + rpl::producer &&wrapValue, + not_null peer); + +protected: + void visibleTopBottomUpdated( + int visibleTop, + int visibleBottom) override; + int resizeGetHeight(int newWidth) override; + +private: + using ListWidget = ::Profile::GroupMembersWidget; + + object_ptr setupHeader(); + object_ptr setupList( + RpWidget *parent) const; + + void setupButtons(); + void updateSearchOverrides(); + + void addMember(); + void showSearch(); + void toggleSearch(); + void cancelSearch(); + void applySearch(); + void searchAnimationCallback(); + + Wrap _wrap; + not_null _peer; + object_ptr _labelWrap; + object_ptr _label; + object_ptr _addMember; + object_ptr _searchField; + object_ptr _search; + object_ptr _cancelSearch; + object_ptr _list; + + Animation _searchShownAnimation; + bool _searchShown = false; + base::Timer _searchTimer; + +}; + +} // namespace Profile +} // namespace Info diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.cpp b/Telegram/SourceFiles/info/profile/info_profile_values.cpp index f57c76e7aad12f..a6d7d0d9f107d3 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_values.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_values.cpp @@ -220,6 +220,26 @@ rpl::producer CommonGroupsCountValue( }); } +rpl::producer CanAddMemberValue( + not_null peer) { + if (auto chat = peer->asChat()) { + return PeerUpdateValue( + chat, + Notify::PeerUpdate::Flag::ChatCanEdit) + | rpl::map([chat](auto&&) { + return chat->canEdit(); + }); + } else if (auto channel = peer->asChannel()) { + return PeerUpdateValue( + channel, + Notify::PeerUpdate::Flag::ChannelRightsChanged) + | rpl::map([channel](auto&&) { + return channel->canAddMembers(); + }); + } + return rpl::single(false); +} + rpl::producer MultiLineTracker::atLeastOneShownValue() const { auto shown = std::vector>(); shown.reserve(_widgets.size()); diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.h b/Telegram/SourceFiles/info/profile/info_profile_values.h index 0440e3e4120f5e..cc0c9ba63947a4 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_values.h +++ b/Telegram/SourceFiles/info/profile/info_profile_values.h @@ -74,6 +74,8 @@ rpl::producer SharedMediaCountValue( Storage::SharedMediaType type); rpl::producer CommonGroupsCountValue( not_null user); +rpl::producer CanAddMemberValue( + not_null peer); class MultiLineTracker { public: diff --git a/Telegram/SourceFiles/info/profile/info_profile_widget.cpp b/Telegram/SourceFiles/info/profile/info_profile_widget.cpp index cfacaa18d02381..2158d172741a12 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_widget.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_widget.cpp @@ -48,6 +48,7 @@ Widget::Widget( : ContentWidget(parent, wrap, controller, peer) { _inner = setInnerWidget(object_ptr( this, + wrapValue(), controller, peer)); _inner->move(0, 0); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 2dc3ee1dfd8b55..51ab4dfadf4f46 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -31,6 +31,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "window/top_bar_widget.h" #include "data/data_drafts.h" #include "ui/widgets/dropdown_menu.h" +#include "ui/focus_persister.h" #include "chat_helpers/message_field.h" #include "chat_helpers/stickers.h" #include "observer_peer.h" @@ -3028,6 +3029,7 @@ void MainWidget::checkMainSectionToLayer() { if (!_mainSection) { return; } + Ui::FocusPersister persister(this); if (auto layer = _mainSection->moveContentToLayer(width())) { dropMainSection(_mainSection); _controller->showSpecialLayer( diff --git a/Telegram/SourceFiles/profile/profile.style b/Telegram/SourceFiles/profile/profile.style index 47a13559ab8a96..df2662dacaf56d 100644 --- a/Telegram/SourceFiles/profile/profile.style +++ b/Telegram/SourceFiles/profile/profile.style @@ -121,21 +121,15 @@ profileInviteLinkText: FlatLabel(profileBlockTextPart) { profileLimitReachedSkip: 6px; -profileMemberItem: ProfilePeerListItem { +profileMemberItem: ProfilePeerListItem(defaultProfileMemberItem) { left: 8px; bottom: profileBlockMarginBottom; button: defaultLeftOutlineButton; - statusFg: windowSubTextFg; + maximalWidth: profileBlockWideWidthMax; statusFgOver: profileStatusFgOver; - statusFgActive: windowActiveTextFg; } -profileMemberHeight: 58px; profileMemberPaddingLeft: 16px; -profileMemberPhotoSize: 46px; -profileMemberPhotoPosition: point(12px, 6px); -profileMemberNamePosition: point(68px, 11px); profileMemberNameFg: windowBoldFg; -profileMemberStatusPosition: point(68px, 31px); profileMemberCreatorIcon: icon {{ "profile_admin_star", profileAdminStartFg, point(4px, 3px) }}; profileMemberCreatorIconOver: icon {{ "profile_admin_star", profileAdminStarFgOver, point(4px, 3px) }}; profileMemberAdminIcon: icon {{ "profile_admin_star", profileOtherAdminStarFg, point(4px, 3px) }}; diff --git a/Telegram/SourceFiles/profile/profile_block_group_members.cpp b/Telegram/SourceFiles/profile/profile_block_group_members.cpp index 84c10305b69ad1..af374f5f97cb90 100644 --- a/Telegram/SourceFiles/profile/profile_block_group_members.cpp +++ b/Telegram/SourceFiles/profile/profile_block_group_members.cpp @@ -35,7 +35,11 @@ namespace Profile { using UpdateFlag = Notify::PeerUpdate::Flag; -GroupMembersWidget::GroupMembersWidget(QWidget *parent, PeerData *peer, TitleVisibility titleVisibility, const style::ProfilePeerListItem &st) +GroupMembersWidget::GroupMembersWidget( + QWidget *parent, + PeerData *peer, + TitleVisibility titleVisibility, + const style::ProfilePeerListItem &st) : PeerListWidget(parent , peer , (titleVisibility == TitleVisibility::Visible) ? lang(lng_profile_participants_section) : QString() diff --git a/Telegram/SourceFiles/profile/profile_block_peer_list.cpp b/Telegram/SourceFiles/profile/profile_block_peer_list.cpp index c5642e82e1dc70..ee197245360b86 100644 --- a/Telegram/SourceFiles/profile/profile_block_peer_list.cpp +++ b/Telegram/SourceFiles/profile/profile_block_peer_list.cpp @@ -45,7 +45,7 @@ PeerListWidget::PeerListWidget(QWidget *parent, PeerData *peer, const QString &t int PeerListWidget::resizeGetHeight(int newWidth) { auto newHeight = getListTop(); - newHeight += _items.size() * st::profileMemberHeight; + newHeight += _items.size() * _st.height; return newHeight + _st.bottom; } @@ -67,10 +67,10 @@ void PeerListWidget::paintContents(Painter &p) { auto top = getListTop(); auto memberRowWidth = rowWidth(); - auto from = floorclamp(_visibleTop - top, st::profileMemberHeight, 0, _items.size()); - auto to = ceilclamp(_visibleBottom - top, st::profileMemberHeight, 0, _items.size()); + auto from = floorclamp(_visibleTop - top, _st.height, 0, _items.size()); + auto to = ceilclamp(_visibleBottom - top, _st.height, 0, _items.size()); for (auto i = from; i < to; ++i) { - auto y = top + i * st::profileMemberHeight; + auto y = top + i * _st.height; auto selected = (_menuRowIndex >= 0) ? (i == _menuRowIndex) : (_pressed >= 0) ? (i == _pressed) : (i == _selected); auto selectedRemove = selected && _selectedRemove; if (_pressed >= 0 && !_pressedRemove) { @@ -87,7 +87,7 @@ void PeerListWidget::paintItem(Painter &p, int x, int y, Item *item, bool select auto memberRowWidth = rowWidth(); if (selected) { - paintOutlinedRect(p, x, y, memberRowWidth, st::profileMemberHeight); + paintOutlinedRect(p, x, y, memberRowWidth, _st.height); } if (auto &ripple = item->ripple) { ripple->paint(p, x + _st.button.outlineWidth, y, width(), ms); @@ -95,16 +95,16 @@ void PeerListWidget::paintItem(Painter &p, int x, int y, Item *item, bool select ripple.reset(); } } - int skip = st::profileMemberPhotoPosition.x(); + int skip = _st.photoPosition.x(); - item->peer->paintUserpicLeft(p, x + st::profileMemberPhotoPosition.x(), y + st::profileMemberPhotoPosition.y(), width(), st::profileMemberPhotoSize); + item->peer->paintUserpicLeft(p, x + _st.photoPosition.x(), y + _st.photoPosition.y(), width(), _st.photoSize); if (item->name.isEmpty()) { item->name.setText(st::msgNameStyle, App::peerName(item->peer), _textNameOptions); } - int nameLeft = x + st::profileMemberNamePosition.x(); - int nameTop = y + st::profileMemberNamePosition.y(); - int nameWidth = memberRowWidth - st::profileMemberNamePosition.x() - skip; + int nameLeft = x + _st.namePosition.x(); + int nameTop = y + _st.namePosition.y(); + int nameWidth = memberRowWidth - _st.namePosition.x() - skip; if (item->hasRemoveLink && selected) { p.setFont(selectedKick ? st::normalFont->underline() : st::normalFont); p.setPen(st::windowActiveTextFg); @@ -128,7 +128,7 @@ void PeerListWidget::paintItem(Painter &p, int x, int y, Item *item, bool select p.setPen(selected ? _st.statusFgOver : _st.statusFg); } p.setFont(st::normalFont); - p.drawTextLeft(x + st::profileMemberStatusPosition.x(), y + st::profileMemberStatusPosition.y(), width(), item->statusText); + p.drawTextLeft(x + _st.statusPosition.x(), y + _st.statusPosition.y(), width(), item->statusText); } void PeerListWidget::paintOutlinedRect(Painter &p, int x, int y, int w, int h) const { @@ -155,13 +155,13 @@ void PeerListWidget::mousePressEvent(QMouseEvent *e) { auto item = _items[_pressed]; if (!item->ripple) { auto memberRowWidth = rowWidth(); - auto mask = Ui::RippleAnimation::rectMask(QSize(memberRowWidth - _st.button.outlineWidth, st::profileMemberHeight)); + auto mask = Ui::RippleAnimation::rectMask(QSize(memberRowWidth - _st.button.outlineWidth, _st.height)); item->ripple = std::make_unique(_st.button.ripple, std::move(mask), [this, index = _pressed] { repaintRow(index); }); } auto left = getListLeft() + _st.button.outlineWidth; - auto top = getListTop() + st::profileMemberHeight * _pressed; + auto top = getListTop() + _st.height * _pressed; item->ripple->add(e->pos() - QPoint(left, top)); } } @@ -250,14 +250,14 @@ void PeerListWidget::updateSelection() { auto top = getListTop(); auto memberRowWidth = rowWidth(); if (mouse.x() >= left && mouse.x() < left + memberRowWidth && mouse.y() >= top) { - selected = (mouse.y() - top) / st::profileMemberHeight; + selected = (mouse.y() - top) / _st.height; if (selected >= _items.size()) { selected = -1; } else if (_items[selected]->hasRemoveLink) { - int skip = st::profileMemberPhotoPosition.x(); - int nameLeft = left + st::profileMemberNamePosition.x(); - int nameTop = top + _selected * st::profileMemberHeight + st::profileMemberNamePosition.y(); - int nameWidth = memberRowWidth - st::profileMemberNamePosition.x() - skip; + int skip = _st.photoPosition.x(); + int nameLeft = left + _st.namePosition.x(); + int nameTop = top + _selected * _st.height + _st.namePosition.y(); + int nameWidth = memberRowWidth - _st.namePosition.x() - skip; if (mouse.x() >= nameLeft + nameWidth - _removeWidth && mouse.x() < nameLeft + nameWidth) { if (mouse.y() >= nameTop && mouse.y() < nameTop + st::normalFont->height) { selectedKick = true; @@ -294,7 +294,7 @@ void PeerListWidget::repaintSelectedRow() { void PeerListWidget::repaintRow(int index) { if (index >= 0) { auto left = getListLeft(); - rtlupdate(left, getListTop() + index * st::profileMemberHeight, width() - left, st::profileMemberHeight); + rtlupdate(left, getListTop() + index * _st.height, width() - left, _st.height); } } @@ -303,14 +303,16 @@ int PeerListWidget::getListLeft() const { } int PeerListWidget::rowWidth() const { - return qMin(width() - getListLeft(), st::profileBlockWideWidthMax); + return _st.maximalWidth + ? qMin(width() - getListLeft(), _st.maximalWidth) + : width() - getListLeft(); } void PeerListWidget::preloadPhotos() { int top = getListTop(); int preloadFor = (_visibleBottom - _visibleTop) * PreloadHeightsCount; - int from = floorclamp(_visibleTop - top, st::profileMemberHeight, 0, _items.size()); - int to = ceilclamp(_visibleBottom + preloadFor - top, st::profileMemberHeight, 0, _items.size()); + int from = floorclamp(_visibleTop - top, _st.height, 0, _items.size()); + int to = ceilclamp(_visibleBottom + preloadFor - top, _st.height, 0, _items.size()); for (int i = from; i < to; ++i) { _items[i]->peer->loadUserpic(); } diff --git a/Telegram/SourceFiles/profile/profile_block_widget.cpp b/Telegram/SourceFiles/profile/profile_block_widget.cpp index 0eb34df77707db..2fe1ba1067c13d 100644 --- a/Telegram/SourceFiles/profile/profile_block_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_block_widget.cpp @@ -25,7 +25,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org namespace Profile { -BlockWidget::BlockWidget(QWidget *parent, PeerData *peer, const QString &title) : TWidget(parent) +BlockWidget::BlockWidget( + QWidget *parent, + PeerData *peer, + const QString &title) : RpWidget(parent) , _peer(peer) , _title(title) { } diff --git a/Telegram/SourceFiles/profile/profile_block_widget.h b/Telegram/SourceFiles/profile/profile_block_widget.h index 2ed5a74085b43d..940ea394e5b979 100644 --- a/Telegram/SourceFiles/profile/profile_block_widget.h +++ b/Telegram/SourceFiles/profile/profile_block_widget.h @@ -21,12 +21,13 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #pragma once #include "base/observer.h" +#include "ui/rp_widget.h" namespace Profile { class SectionMemento; -class BlockWidget : public TWidget, protected base::Subscriber { +class BlockWidget : public Ui::RpWidget, protected base::Subscriber { Q_OBJECT public: diff --git a/Telegram/SourceFiles/rpl/mappers.h b/Telegram/SourceFiles/rpl/mappers.h index c39d6ddada37a6..340f439a27499a 100644 --- a/Telegram/SourceFiles/rpl/mappers.h +++ b/Telegram/SourceFiles/rpl/mappers.h @@ -432,6 +432,45 @@ inline auto operator~(Type &&value) { std::forward(value)); } +template +class tuple_mapper { + template + using tuple_result = std::tuple>>()( + std::declval()...))...>; +public: + template + tuple_mapper(OtherMappers &&...mappers) : _mappers( + std::forward(mappers)...) { + } + + template + constexpr tuple_result operator()( + Args &&...args) const { + constexpr auto kArity = sizeof...(Mappers); + return call_helper( + std::make_index_sequence(), + std::forward(args)...); + } + +private: + template + inline tuple_result call_helper( + std::index_sequence, + Args &&...args) const { + return std::make_tuple( + std::get(_mappers)(std::forward(args)...)...); + } + + std::tuple>...> _mappers; + +}; + +template +tuple_mapper tuple(Args &&...args) { + return tuple_mapper(std::forward(args)...); +} + } // namespace details namespace mappers { diff --git a/Telegram/SourceFiles/ui/abstract_button.cpp b/Telegram/SourceFiles/ui/abstract_button.cpp index 092918fa6ad83d..9379687d3f5081 100644 --- a/Telegram/SourceFiles/ui/abstract_button.cpp +++ b/Telegram/SourceFiles/ui/abstract_button.cpp @@ -71,12 +71,15 @@ void AbstractButton::mouseReleaseEvent(QMouseEvent *e) { onStateChanged(was, StateChangeSource::ByPress); if (was & StateFlag::Over) { _modifiers = e->modifiers(); + auto test = weak(this); if (_clickedCallback) { _clickedCallback(); } else { emit clicked(); } - _clicks.fire({}); + if (test) { + _clicks.fire({}); + } } else { setOver(false, StateChangeSource::ByHover); } diff --git a/Telegram/SourceFiles/ui/focus_persister.h b/Telegram/SourceFiles/ui/focus_persister.h new file mode 100644 index 00000000000000..84ec3de4b2d4f8 --- /dev/null +++ b/Telegram/SourceFiles/ui/focus_persister.h @@ -0,0 +1,55 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org +*/ +#pragma once + +namespace Ui { + +class FocusPersister { +public: + FocusPersister(QWidget *parent, QWidget *steal = nullptr) + : _weak(GrabFocused(parent)) { + if (steal) { + steal->setFocus(); + } + } + + ~FocusPersister() { + if (auto strong = _weak.data()) { + if (auto window = strong->window()) { + if (window->focusWidget() != strong) { + strong->setFocus(); + } + } + } + } + +private: + static QWidget *GrabFocused(QWidget *parent) { + if (auto window = parent ? parent->window() : nullptr) { + return window->focusWidget(); + } + return nullptr; + } + QPointer _weak; + +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/rp_widget.h b/Telegram/SourceFiles/ui/rp_widget.h index f21d65c9fb6458..fce9f5f38cd5d5 100644 --- a/Telegram/SourceFiles/ui/rp_widget.h +++ b/Telegram/SourceFiles/ui/rp_widget.h @@ -26,8 +26,16 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org namespace Ui { -template -class RpWidgetWrap : public Parent { +template +using RpWidgetParent = std::conditional_t< + std::is_same_v, + TWidget, + TWidgetHelper>; + +template +class RpWidgetWrap : public RpWidgetParent { + using Parent = RpWidgetParent; + public: using Parent::Parent; @@ -77,6 +85,13 @@ class RpWidgetWrap : public Parent { return eventStreams().alive.events(); } + void showOn(rpl::producer &&shown) { + std::move(shown) + | rpl::start([this](bool visible) { + setVisible(visible); + }, lifetime()); + } + rpl::lifetime &lifetime() { return _lifetime.data; } @@ -110,7 +125,7 @@ class RpWidgetWrap : public Parent { return eventHook(event); } virtual bool eventHook(QEvent *event) { - return TWidget::event(event); + return Parent::event(event); } private: @@ -139,9 +154,9 @@ class RpWidgetWrap : public Parent { }; -class RpWidget : public RpWidgetWrap { +class RpWidget : public RpWidgetWrap { public: - using RpWidgetWrap::RpWidgetWrap; + using RpWidgetWrap::RpWidgetWrap; }; diff --git a/Telegram/SourceFiles/ui/widgets/scroll_area.cpp b/Telegram/SourceFiles/ui/widgets/scroll_area.cpp index 945166bb5e83b0..4d184a0dc847ff 100644 --- a/Telegram/SourceFiles/ui/widgets/scroll_area.cpp +++ b/Telegram/SourceFiles/ui/widgets/scroll_area.cpp @@ -319,7 +319,8 @@ void SplittedWidgetOther::paintEvent(QPaintEvent *e) { } } -ScrollArea::ScrollArea(QWidget *parent, const style::ScrollArea &st, bool handleTouch) : TWidgetHelper(parent) +ScrollArea::ScrollArea(QWidget *parent, const style::ScrollArea &st, bool handleTouch) +: RpWidgetWrap(parent) , _st(st) , _horizontalBar(this, false, &_st) , _verticalBar(this, true, &_st) diff --git a/Telegram/SourceFiles/ui/widgets/scroll_area.h b/Telegram/SourceFiles/ui/widgets/scroll_area.h index dc70a283bdc8ef..f508ccb32305b4 100644 --- a/Telegram/SourceFiles/ui/widgets/scroll_area.h +++ b/Telegram/SourceFiles/ui/widgets/scroll_area.h @@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #pragma once #include +#include "ui/rp_widget.h" #include "styles/style_widgets.h" namespace Ui { @@ -171,7 +172,7 @@ public slots: }; class SplittedWidgetOther; -class ScrollArea : public TWidgetHelper { +class ScrollArea : public Ui::RpWidgetWrap { Q_OBJECT public: diff --git a/Telegram/SourceFiles/ui/widgets/widgets.style b/Telegram/SourceFiles/ui/widgets/widgets.style index 485a8d32f8361f..5fc0cf3e71f8d5 100644 --- a/Telegram/SourceFiles/ui/widgets/widgets.style +++ b/Telegram/SourceFiles/ui/widgets/widgets.style @@ -1055,6 +1055,12 @@ MediaPlayerButton { ProfilePeerListItem { left: pixels; bottom: pixels; + height: pixels; + photoPosition: point; + namePosition: point; + statusPosition: point; + photoSize: pixels; + maximalWidth: pixels; button: OutlineButton; statusFg: color; @@ -1062,6 +1068,31 @@ ProfilePeerListItem { statusFgActive: color; } +defaultProfileMemberItem: ProfilePeerListItem { + height: 58px; + photoPosition: point(12px, 6px); + namePosition: point(68px, 11px); + statusPosition: point(68px, 31px); + photoSize: 46px; + button: OutlineButton { + outlineWidth: 0px; + + textBg: windowBg; + textBgOver: windowBgOver; + + textFg: windowSubTextFg; + textFgOver: windowSubTextFgOver; + + font: normalFont; + padding: margins(11px, 5px, 11px, 5px); + + ripple: defaultRippleAnimation; + } + statusFg: windowSubTextFg; + statusFgOver: windowSubTextFgOver; + statusFgActive: windowActiveTextFg; +} + InfoTopBar { height: pixels; back: IconButton; diff --git a/Telegram/SourceFiles/ui/wrap/slide_wrap.cpp b/Telegram/SourceFiles/ui/wrap/slide_wrap.cpp index 18d2193c41843b..1d34057c2e3447 100644 --- a/Telegram/SourceFiles/ui/wrap/slide_wrap.cpp +++ b/Telegram/SourceFiles/ui/wrap/slide_wrap.cpp @@ -84,11 +84,10 @@ SlideWrap *SlideWrap::finishAnimations() { SlideWrap *SlideWrap::toggleOn( rpl::producer &&shown) { - _toggleOnLifetime.destroy(); std::move(shown) | rpl::start([this](bool shown) { toggleAnimated(shown); - }, _toggleOnLifetime); + }, lifetime()); finishAnimations(); return this; } diff --git a/Telegram/SourceFiles/ui/wrap/slide_wrap.h b/Telegram/SourceFiles/ui/wrap/slide_wrap.h index a8468207d2b8d5..d34bc2b5bf9053 100644 --- a/Telegram/SourceFiles/ui/wrap/slide_wrap.h +++ b/Telegram/SourceFiles/ui/wrap/slide_wrap.h @@ -77,7 +77,6 @@ class SlideWrap : public Wrap> { bool _shown = true; rpl::event_stream _shownUpdated; - rpl::lifetime _toggleOnLifetime; Animation _slideAnimation; int _duration = 0; diff --git a/Telegram/SourceFiles/window/top_bar_widget.cpp b/Telegram/SourceFiles/window/top_bar_widget.cpp index 2fc5200802ac63..52d243e83c9a18 100644 --- a/Telegram/SourceFiles/window/top_bar_widget.cpp +++ b/Telegram/SourceFiles/window/top_bar_widget.cpp @@ -263,6 +263,9 @@ void TopBarWidget::paintEvent(QPaintEvent *e) { if (!_menuToggle->isHidden()) { decreaseWidth += _menuToggle->width(); } + if (!_infoToggle->isHidden()) { + decreaseWidth += _infoToggle->width() + st::topBarSkip; + } if (!_search->isHidden()) { decreaseWidth += _search->width(); } diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index 888b3d28b48dda..8ab0dea8af8cde 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -564,6 +564,7 @@ <(src_loc)/ui/countryinput.h <(src_loc)/ui/emoji_config.cpp <(src_loc)/ui/emoji_config.h +<(src_loc)/ui/focus_persister.h <(src_loc)/ui/images.cpp <(src_loc)/ui/images.h <(src_loc)/ui/rp_widget.h