diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 47d4a5737..2ce8107c1 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -68,37 +68,6 @@ aboutRevokePublicLabel: flatLabel(labelDefFlat) { textFg: windowTextFg; } -boxSearchField: InputField(defaultInputField) { - textBg: transparent; - textMargins: margins(2px, 7px, 2px, 0px); - - placeholderFg: #999; - placeholderFgActive: #aaa; - placeholderMargins: margins(2px, 0px, 2px, 0px); - duration: 150; - - border: 0px; - borderActive: 0px; - borderError: 0px; - - height: 32px; - - font: normalFont; -} -boxSearchCancel: IconButton { - width: 41px; - height: 48px; - - opacity: 0.3; - overOpacity: 0.4; - - icon: icon {{ "box_search_cancel", #000000 }}; - iconPosition: point(8px, 18px); - downIconPosition: point(8px, 19px); - - duration: 150; -} - contactsMultiSelect: MultiSelect { padding: margins(8px, 8px, 8px, 8px); maxHeight: 104px; @@ -128,12 +97,40 @@ contactsMultiSelect: MultiSelect { } itemSkip: 8px; - field: boxSearchField; + field: InputField(defaultInputField) { + textBg: transparent; + textMargins: margins(2px, 7px, 2px, 0px); + + placeholderFg: #999; + placeholderFgActive: #aaa; + placeholderMargins: margins(2px, 0px, 2px, 0px); + + border: 0px; + borderActive: 0px; + borderError: 0px; + + height: 32px; + + font: normalFont; + } + fieldMinWidth: 42px; fieldIcon: icon {{ "box_search_icon", #aaaaaa, point(11px, 9px) }}; fieldIconSkip: 36px; - fieldCancel: boxSearchCancel; + + fieldCancel: IconButton { + width: 41px; + height: 48px; + + opacity: 0.3; + overOpacity: 0.4; + + icon: icon {{ "box_search_cancel", #000000 }}; + iconPosition: point(8px, 18px); + downIconPosition: point(8px, 19px); + + duration: 150; + } fieldCancelSkip: 34px; - fieldMinWidth: 42px; } contactsPhotoCheckbox: RoundImageCheckbox { imageRadius: 21px; diff --git a/Telegram/SourceFiles/boxes/contactsbox.cpp b/Telegram/SourceFiles/boxes/contactsbox.cpp index cbf186b8f..090bbf80c 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.cpp +++ b/Telegram/SourceFiles/boxes/contactsbox.cpp @@ -32,6 +32,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "application.h" #include "ui/filedialog.h" #include "ui/widgets/multi_select.h" +#include "ui/widgets/widget_slide_wrap.h" #include "boxes/photocropbox.h" #include "boxes/confirmbox.h" #include "observer_peer.h" @@ -43,7 +44,7 @@ QString cantInviteError() { ContactsBox::ContactsBox() : ItemListBox(st::contactsScroll) , _inner(this, CreatingGroupNone) -, _select(this, st::contactsMultiSelect, lang(lng_participant_filter)) +, _select(this, new Ui::MultiSelect(this, st::contactsMultiSelect, lang(lng_participant_filter)), QMargins(0, 0, 0, 0), [this] { updateScrollSkips(); }) , _next(this, lang(lng_create_group_next), st::defaultBoxButton) , _cancel(this, lang(lng_cancel), st::cancelBoxButton) , _topShadow(this) { @@ -52,7 +53,7 @@ ContactsBox::ContactsBox() : ItemListBox(st::contactsScroll) ContactsBox::ContactsBox(const QString &name, const QImage &photo) : ItemListBox(st::boxScroll) , _inner(this, CreatingGroupGroup) -, _select(this, st::contactsMultiSelect, lang(lng_participant_filter)) +, _select(this, new Ui::MultiSelect(this, st::contactsMultiSelect, lang(lng_participant_filter)), QMargins(0, 0, 0, 0), [this] { updateScrollSkips(); }) , _next(this, lang(lng_create_group_create), st::defaultBoxButton) , _cancel(this, lang(lng_create_group_back), st::cancelBoxButton) , _topShadow(this) @@ -63,7 +64,7 @@ ContactsBox::ContactsBox(const QString &name, const QImage &photo) : ItemListBox ContactsBox::ContactsBox(ChannelData *channel) : ItemListBox(st::boxScroll) , _inner(this, channel, MembersFilter::Recent, MembersAlreadyIn()) -, _select(this, st::contactsMultiSelect, lang(lng_participant_filter)) +, _select(this, new Ui::MultiSelect(this, st::contactsMultiSelect, lang(lng_participant_filter)), QMargins(0, 0, 0, 0), [this] { updateScrollSkips(); }) , _next(this, lang(lng_participant_invite), st::defaultBoxButton) , _cancel(this, lang(lng_create_group_skip), st::cancelBoxButton) , _topShadow(this) { @@ -72,7 +73,7 @@ ContactsBox::ContactsBox(ChannelData *channel) : ItemListBox(st::boxScroll) ContactsBox::ContactsBox(ChannelData *channel, MembersFilter filter, const MembersAlreadyIn &already) : ItemListBox((filter == MembersFilter::Admins) ? st::contactsScroll : st::boxScroll) , _inner(this, channel, filter, already) -, _select(this, st::contactsMultiSelect, lang(lng_participant_filter)) +, _select(this, new Ui::MultiSelect(this, st::contactsMultiSelect, lang(lng_participant_filter)), QMargins(0, 0, 0, 0), [this] { updateScrollSkips(); }) , _next(this, lang(lng_participant_invite), st::defaultBoxButton) , _cancel(this, lang(lng_cancel), st::cancelBoxButton) , _topShadow(this) { @@ -81,7 +82,7 @@ ContactsBox::ContactsBox(ChannelData *channel, MembersFilter filter, const Membe ContactsBox::ContactsBox(ChatData *chat, MembersFilter filter) : ItemListBox(st::boxScroll) , _inner(this, chat, filter) -, _select(this, st::contactsMultiSelect, lang(lng_participant_filter)) +, _select(this, new Ui::MultiSelect(this, st::contactsMultiSelect, lang(lng_participant_filter)), QMargins(0, 0, 0, 0), [this] { updateScrollSkips(); }) , _next(this, lang((filter == MembersFilter::Admins) ? lng_settings_save : lng_participant_invite), st::defaultBoxButton) , _cancel(this, lang(lng_cancel), st::cancelBoxButton) , _topShadow(this) { @@ -90,7 +91,7 @@ ContactsBox::ContactsBox(ChatData *chat, MembersFilter filter) : ItemListBox(st: ContactsBox::ContactsBox(UserData *bot) : ItemListBox(st::contactsScroll) , _inner(this, bot) -, _select(this, st::contactsMultiSelect, lang(lng_participant_filter)) +, _select(this, new Ui::MultiSelect(this, st::contactsMultiSelect, lang(lng_participant_filter)), QMargins(0, 0, 0, 0), [this] { updateScrollSkips(); }) , _next(this, lang(lng_create_group_next), st::defaultBoxButton) , _cancel(this, lang(lng_cancel), st::cancelBoxButton) , _topShadow(this) { @@ -101,7 +102,7 @@ void ContactsBox::init() { _select->resizeToWidth(st::boxWideWidth); auto inviting = (_inner->creating() == CreatingGroupGroup) || (_inner->channel() && _inner->membersFilter() == MembersFilter::Recent) || _inner->chat(); - auto topSkip = st::boxTitleHeight + _select->height(); + auto topSkip = getTopScrollSkip(); auto bottomSkip = inviting ? (st::boxButtonPadding.top() + _next.height() + st::boxButtonPadding.bottom()) : st::boxScrollSkip; ItemListBox::init(_inner, bottomSkip, topSkip); @@ -109,6 +110,20 @@ void ContactsBox::init() { _inner->setPeerSelectedChangedCallback([this](PeerData *peer, bool checked) { onPeerSelectedChanged(peer, checked); }); + for (auto i : _inner->selected()) { + addPeerToMultiSelect(i, true); + } + _inner->setAllAdminsChangedCallback([this] { + if (_inner->allAdmins()) { + _select->entity()->clearQuery(); + _select->slideUp(); + _inner->setFocus(); + } else { + _select->slideDown(); + _select->entity()->setInnerFocus(); + } + updateScrollSkips(); + }); if (_inner->channel() && _inner->membersFilter() == MembersFilter::Admins) { _next.hide(); @@ -128,15 +143,14 @@ void ContactsBox::init() { } connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll())); - _select->setQueryChangedCallback([this](const QString &query) { onFilterUpdate(query); }); - _select->setItemRemovedCallback([this](uint64 itemId) { + _select->entity()->setQueryChangedCallback([this](const QString &query) { onFilterUpdate(query); }); + _select->entity()->setItemRemovedCallback([this](uint64 itemId) { if (auto peer = App::peerLoaded(itemId)) { _inner->peerUnselected(peer); update(); } }); - _select->setSubmittedCallback([this](bool) { onSubmit(); }); - _select->setResizedCallback([this] { updateScrollSkips(); }); + _select->entity()->setSubmittedCallback([this](bool) { onSubmit(); }); connect(_inner, SIGNAL(mustScrollTo(int, int)), scrollArea(), SLOT(scrollToY(int, int))); connect(_inner, SIGNAL(searchByUsername()), this, SLOT(onNeedSearchByUsername())); connect(_inner, SIGNAL(adminAdded()), this, SIGNAL(adminAdded())); @@ -148,7 +162,7 @@ void ContactsBox::init() { } bool ContactsBox::onSearchByUsername(bool searchCache) { - auto q = _select->getQuery(); + auto q = _select->entity()->getQuery(); if (q.isEmpty()) { if (_peopleRequest) { _peopleRequest = 0; @@ -216,7 +230,11 @@ bool ContactsBox::peopleFailed(const RPCError &error, mtpRequestId req) { } void ContactsBox::showAll() { - _select->show(); + if (_inner->chat() && _inner->membersFilter() == MembersFilter::Admins && _inner->allAdmins()) { + _select->hideFast(); + } else { + _select->showFast(); + } if (_inner->channel() && _inner->membersFilter() == MembersFilter::Admins) { _next.hide(); _cancel.hide(); @@ -236,7 +254,11 @@ void ContactsBox::showAll() { } void ContactsBox::doSetInnerFocus() { - _select->setInnerFocus(); + if (_select->isHidden()) { + _inner->setFocus(); + } else { + _select->entity()->setInnerFocus(); + } } void ContactsBox::onSubmit() { @@ -282,10 +304,18 @@ void ContactsBox::paintEvent(QPaintEvent *e) { } } +int ContactsBox::getTopScrollSkip() const { + auto result = st::boxTitleHeight; + if (!_select->isHidden()) { + result += _select->height(); + } + return result; +} + void ContactsBox::updateScrollSkips() { auto oldScrollHeight = scrollArea()->height(); auto inviting = (_inner->creating() == CreatingGroupGroup) || (_inner->channel() && _inner->membersFilter() == MembersFilter::Recent) || _inner->chat(); - auto topSkip = st::boxTitleHeight + _select->height(); + auto topSkip = getTopScrollSkip(); auto bottomSkip = inviting ? (st::boxButtonPadding.top() + _next.height() + st::boxButtonPadding.bottom()) : st::boxScrollSkip; setScrollSkips(bottomSkip, topSkip); auto scrollHeightDelta = scrollArea()->height() - oldScrollHeight; @@ -293,7 +323,7 @@ void ContactsBox::updateScrollSkips() { scrollArea()->scrollToY(scrollArea()->scrollTop() - scrollHeightDelta); } - _topShadow.setGeometry(0, st::boxTitleHeight + _select->height(), width(), st::lineWidth); + _topShadow.setGeometry(0, topSkip, width(), st::lineWidth); } void ContactsBox::resizeEvent(QResizeEvent *e) { @@ -321,24 +351,30 @@ void ContactsBox::onFilterUpdate(const QString &filter) { _inner->updateFilter(filter); } +void ContactsBox::addPeerToMultiSelect(PeerData *peer, bool skipAnimation) { + auto getColor = [peer]() -> const style::color & { + switch (peer->colorIndex) { + case 1: return st::historyPeer2UserpicFg; + case 2: return st::historyPeer3UserpicFg; + case 3: return st::historyPeer4UserpicFg; + case 4: return st::historyPeer5UserpicFg; + case 5: return st::historyPeer6UserpicFg; + case 6: return st::historyPeer7UserpicFg; + case 7: return st::historyPeer8UserpicFg; + default: return st::historyPeer1UserpicFg; + } + }; + using AddItemWay = Ui::MultiSelect::AddItemWay; + auto addItemWay = skipAnimation ? AddItemWay::SkipAnimation : AddItemWay::Default; + _select->entity()->addItem(peer->id, peer->shortName(), getColor(), PaintUserpicCallback(peer), addItemWay); +} + void ContactsBox::onPeerSelectedChanged(PeerData *peer, bool checked) { if (checked) { - auto getColor = [peer]() -> const style::color &{ - switch (peer->colorIndex) { - case 1: return st::historyPeer2UserpicFg; - case 2: return st::historyPeer3UserpicFg; - case 3: return st::historyPeer4UserpicFg; - case 4: return st::historyPeer5UserpicFg; - case 5: return st::historyPeer6UserpicFg; - case 6: return st::historyPeer7UserpicFg; - case 7: return st::historyPeer8UserpicFg; - default: return st::historyPeer1UserpicFg; - } - }; - _select->addItem(peer->id, peer->shortName(), getColor(), PaintUserpicCallback(peer)); - _select->clearQuery(); + addPeerToMultiSelect(peer); + _select->entity()->clearQuery(); } else { - _select->removeItem(peer->id); + _select->entity()->removeItem(peer->id); } update(); } @@ -346,7 +382,7 @@ void ContactsBox::onPeerSelectedChanged(PeerData *peer, bool checked) { void ContactsBox::onInvite() { QVector users(_inner->selected()); if (users.isEmpty()) { - _select->setInnerFocus(); + _select->entity()->setInnerFocus(); return; } @@ -364,7 +400,7 @@ void ContactsBox::onCreate() { auto users = _inner->selectedInputs(); if (users.isEmpty() || (users.size() == 1 && users.at(0).type() == mtpc_inputUserSelf)) { - _select->setInnerFocus(); + _select->entity()->setInnerFocus(); return; } _saveRequestId = MTP::send(MTPmessages_CreateChat(MTP_vector(users), MTP_string(_creationName)), rpcDone(&ContactsBox::creationDone), rpcFail(&ContactsBox::creationFail)); @@ -514,7 +550,7 @@ bool ContactsBox::creationFail(const RPCError &error) { onClose(); return true; } else if (error.type() == "USERS_TOO_FEW") { - _select->setInnerFocus(); + _select->entity()->setInnerFocus(); return true; } else if (error.type() == "PEER_FLOOD") { Ui::showLayer(new InformBox(cantInviteError()), KeepOtherLayers); @@ -641,7 +677,7 @@ void ContactsBox::Inner::init() { } void ContactsBox::Inner::initList() { - if (!usingMultiSelect()) return; + if (!_chat || _membersFilter != MembersFilter::Admins) return; QList admins, others; admins.reserve(_chat->admins.size() + 1); @@ -653,7 +689,9 @@ void ContactsBox::Inner::initList() { if (i.key()->id == peerFromUser(_chat->creator)) continue; if (!_allAdmins.checked() && _chat->admins.contains(i.key())) { admins.push_back(i.key()); - _checkedContacts.insert(i.key()); + if (!_checkedContacts.contains(i.key())) { + _checkedContacts.insert(i.key()); + } } else { others.push_back(i.key()); } @@ -716,10 +754,10 @@ void ContactsBox::Inner::onNoAddAdminBox(QObject *obj) { } void ContactsBox::Inner::onAllAdminsChanged() { - if (_saving) { - if (_allAdmins.checked() != _allAdminsChecked) { - _allAdmins.setChecked(_allAdminsChecked); - } + if (_saving && _allAdmins.checked() != _allAdminsChecked) { + _allAdmins.setChecked(_allAdminsChecked); + } else if (_allAdminsChangedCallback) { + _allAdminsChangedCallback(); } update(); } @@ -1355,7 +1393,8 @@ void ContactsBox::Inner::changeCheckState(Dialogs::Row *row) { void ContactsBox::Inner::changeCheckState(ContactData *data, PeerData *peer) { t_assert(usingMultiSelect()); - if (data->checkbox->checked()) { + if (_chat && _membersFilter == MembersFilter::Admins && _allAdmins.checked()) { + } else if (data->checkbox->checked()) { changePeerCheckState(data, peer, false); } else if (selectedCount() < ((_channel && _channel->isMegagroup()) ? Global::MegagroupSizeMax() : Global::ChatSizeMax())) { changePeerCheckState(data, peer, true); @@ -1385,7 +1424,7 @@ void ContactsBox::Inner::changePeerCheckState(ContactData *data, PeerData *peer, } else { _checkedContacts.remove(peer); } - if (useCallback != ChangeStateWay::SkipCallback) { + if (useCallback != ChangeStateWay::SkipCallback && _peerSelectedChangedCallback) { _peerSelectedChangedCallback(peer, checked); } } @@ -1874,12 +1913,12 @@ QVector ContactsBox::Inner::selected() { } } result.reserve(_contactsData.size()); - for (ContactsData::const_iterator i = _contactsData.cbegin(), e = _contactsData.cend(); i != e; ++i) { + for (auto i = _contactsData.cbegin(), e = _contactsData.cend(); i != e; ++i) { if (i.value()->checkbox->checked() && i.key()->isUser()) { result.push_back(i.key()->asUser()); } } - for (int32 i = 0, l = _byUsername.size(); i < l; ++i) { + for (int i = 0, l = _byUsername.size(); i < l; ++i) { if (d_byUsername[i]->checkbox->checked() && _byUsername[i]->isUser()) { result.push_back(_byUsername[i]->asUser()); } @@ -1899,12 +1938,12 @@ QVector ContactsBox::Inner::selectedInputs() { } } result.reserve(_contactsData.size()); - for (ContactsData::const_iterator i = _contactsData.cbegin(), e = _contactsData.cend(); i != e; ++i) { + for (auto i = _contactsData.cbegin(), e = _contactsData.cend(); i != e; ++i) { if (i.value()->checkbox->checked() && i.key()->isUser()) { result.push_back(i.key()->asUser()->inputUser); } } - for (int32 i = 0, l = _byUsername.size(); i < l; ++i) { + for (int i = 0, l = _byUsername.size(); i < l; ++i) { if (d_byUsername[i]->checkbox->checked() && _byUsername[i]->isUser()) { result.push_back(_byUsername[i]->asUser()->inputUser); } diff --git a/Telegram/SourceFiles/boxes/contactsbox.h b/Telegram/SourceFiles/boxes/contactsbox.h index 9685bdd20..b5dbdee5c 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.h +++ b/Telegram/SourceFiles/boxes/contactsbox.h @@ -32,6 +32,8 @@ class IndexedList; namespace Ui { class MultiSelect; +template +class WidgetSlideWrap; } // namespace Ui QString cantInviteError(); @@ -79,13 +81,15 @@ protected: private: void init(); + int getTopScrollSkip() const; void updateScrollSkips(); void onFilterUpdate(const QString &filter); void onPeerSelectedChanged(PeerData *peer, bool checked); + void addPeerToMultiSelect(PeerData *peer, bool skipAnimation = false); class Inner; ChildWidget _inner; - ChildWidget _select; + ChildWidget> _select; BoxButton _next, _cancel; MembersFilter _membersFilter; @@ -151,6 +155,9 @@ public: bool allAdmins() const { return _allAdmins.checked(); } + void setAllAdminsChangedCallback(base::lambda_unique allAdminsChangedCallback) { + _allAdminsChangedCallback = std_::move(allAdminsChangedCallback); + } void loadProfilePhotos(int32 yFrom); void chooseParticipant(); @@ -259,6 +266,7 @@ private: Checkbox _allAdmins; int32 _aboutWidth; Text _aboutAllAdmins, _aboutAdmins; + base::lambda_unique _allAdminsChangedCallback; PeerData *_addToPeer = nullptr; UserData *_addAdmin = nullptr; diff --git a/Telegram/SourceFiles/boxes/sharebox.cpp b/Telegram/SourceFiles/boxes/sharebox.cpp index ca2323138..76c99e7a4 100644 --- a/Telegram/SourceFiles/boxes/sharebox.cpp +++ b/Telegram/SourceFiles/boxes/sharebox.cpp @@ -23,6 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "dialogs/dialogs_indexed_list.h" #include "styles/style_boxes.h" +#include "styles/style_history.h" #include "observer_peer.h" #include "lang.h" #include "mainwindow.h" @@ -32,7 +33,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "boxes/confirmbox.h" #include "apiwrap.h" #include "ui/toast/toast.h" -#include "ui/buttons/icon_button.h" +#include "ui/widgets/multi_select.h" #include "history/history_media_types.h" #include "boxes/contactsbox.h" @@ -40,30 +41,37 @@ ShareBox::ShareBox(CopyCallback &©Callback, SubmitCallback &&submitCallback, , _copyCallback(std_::move(copyCallback)) , _submitCallback(std_::move(submitCallback)) , _inner(this, std_::move(filterCallback)) -, _filter(this, st::boxSearchField, lang(lng_participant_filter)) -, _filterCancel(this, st::boxSearchCancel) +, _select(this, st::contactsMultiSelect, lang(lng_participant_filter)) , _copy(this, lang(lng_share_copy_link), st::defaultBoxButton) , _share(this, lang(lng_share_confirm), st::defaultBoxButton) , _cancel(this, lang(lng_cancel), st::cancelBoxButton) , _topShadow(this) , _bottomShadow(this) { - int topSkip = st::boxTitleHeight + _filter->height(); - int bottomSkip = st::boxButtonPadding.top() + _share->height() + st::boxButtonPadding.bottom(); + _select->resizeToWidth(st::boxWideWidth); + + auto topSkip = getTopScrollSkip(); + auto bottomSkip = st::boxButtonPadding.top() + _share->height() + st::boxButtonPadding.bottom(); init(_inner, bottomSkip, topSkip); - connect(_inner, SIGNAL(selectedChanged()), this, SLOT(onSelectedChanged())); connect(_inner, SIGNAL(mustScrollTo(int,int)), this, SLOT(onMustScrollTo(int,int))); connect(_copy, SIGNAL(clicked()), this, SLOT(onCopyLink())); connect(_share, SIGNAL(clicked()), this, SLOT(onSubmit())); connect(_cancel, SIGNAL(clicked()), this, SLOT(onClose())); connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll())); - connect(_filter, SIGNAL(changed()), this, SLOT(onFilterUpdate())); - connect(_filter, SIGNAL(submitted(bool)), _inner, SLOT(onSelectActive())); - connect(_filterCancel, SIGNAL(clicked()), this, SLOT(onFilterCancel())); - connect(_inner, SIGNAL(filterCancel()), this, SLOT(onFilterCancel())); + _select->setQueryChangedCallback([this](const QString &query) { onFilterUpdate(query); }); + _select->setItemRemovedCallback([this](uint64 itemId) { + if (auto peer = App::peerLoaded(itemId)) { + _inner->peerUnselected(peer); + onSelectedChanged(); + update(); + } + }); + _select->setResizedCallback([this] { updateScrollSkips(); }); + _select->setSubmittedCallback([this](bool) { _inner->onSelectActive(); }); connect(_inner, SIGNAL(searchByUsername()), this, SLOT(onNeedSearchByUsername())); - - _filterCancel->setAttribute(Qt::WA_OpaquePaintEvent); + _inner->setPeerSelectedChangedCallback([this](PeerData *peer, bool checked) { + onPeerSelectedChanged(peer, checked); + }); _searchTimer.setSingleShot(true); connect(&_searchTimer, SIGNAL(timeout()), this, SLOT(onSearchByUsername())); @@ -73,8 +81,29 @@ ShareBox::ShareBox(CopyCallback &©Callback, SubmitCallback &&submitCallback, prepare(); } +int ShareBox::getTopScrollSkip() const { + auto result = st::boxTitleHeight; + if (!_select->isHidden()) { + result += _select->height(); + } + return result; +} + +void ShareBox::updateScrollSkips() { + auto oldScrollHeight = scrollArea()->height(); + auto topSkip = getTopScrollSkip(); + auto bottomSkip = st::boxButtonPadding.top() + _share->height() + st::boxButtonPadding.bottom(); + setScrollSkips(bottomSkip, topSkip); + auto scrollHeightDelta = scrollArea()->height() - oldScrollHeight; + if (scrollHeightDelta) { + scrollArea()->scrollToY(scrollArea()->scrollTop() - scrollHeightDelta); + } + + _topShadow->setGeometry(0, topSkip, width(), st::lineWidth); +} + bool ShareBox::onSearchByUsername(bool searchCache) { - auto query = _filter->getLastText().trimmed(); + auto query = _select->getQuery(); if (query.isEmpty()) { if (_peopleRequest) { _peopleRequest = 0; @@ -142,7 +171,7 @@ bool ShareBox::peopleFailed(const RPCError &error, mtpRequestId requestId) { } void ShareBox::doSetInnerFocus() { - _filter->setFocus(); + _select->setInnerFocus(); } void ShareBox::paintEvent(QPaintEvent *e) { @@ -154,17 +183,21 @@ void ShareBox::paintEvent(QPaintEvent *e) { void ShareBox::resizeEvent(QResizeEvent *e) { ItemListBox::resizeEvent(e); - _filter->resize(width(), _filter->height()); - _filter->moveToLeft(0, st::boxTitleHeight); - _filterCancel->moveToRight(0, st::boxTitleHeight); + + _select->resizeToWidth(width()); + _select->moveToLeft(0, st::boxTitleHeight); + + updateScrollSkips(); + _inner->resizeToWidth(width()); moveButtons(); - _topShadow->setGeometry(0, st::boxTitleHeight + _filter->height(), width(), st::lineWidth); + _topShadow->setGeometry(0, getTopScrollSkip(), width(), st::lineWidth); _bottomShadow->setGeometry(0, height() - st::boxButtonPadding.bottom() - _share->height() - st::boxButtonPadding.top() - st::lineWidth, width(), st::lineWidth); } void ShareBox::keyPressEvent(QKeyEvent *e) { - if (_filter->hasFocus()) { + auto focused = focusWidget(); + if (_select == focused || _select->isAncestorOf(focusWidget())) { if (e->key() == Qt::Key_Up) { _inner->activateSkipColumn(-1); } else if (e->key() == Qt::Key_Down) { @@ -194,13 +227,38 @@ void ShareBox::updateButtonsVisibility() { _cancel->setVisible(hasSelected); } -void ShareBox::onFilterCancel() { - _filter->setText(QString()); +void ShareBox::onFilterUpdate(const QString &query) { + scrollArea()->scrollToY(0); + _inner->updateFilter(query); } -void ShareBox::onFilterUpdate() { - _filterCancel->setVisible(!_filter->getLastText().isEmpty()); - _inner->updateFilter(_filter->getLastText()); +void ShareBox::addPeerToMultiSelect(PeerData *peer, bool skipAnimation) { + auto getColor = [peer]() -> const style::color & { + switch (peer->colorIndex) { + case 1: return st::historyPeer2UserpicFg; + case 2: return st::historyPeer3UserpicFg; + case 3: return st::historyPeer4UserpicFg; + case 4: return st::historyPeer5UserpicFg; + case 5: return st::historyPeer6UserpicFg; + case 6: return st::historyPeer7UserpicFg; + case 7: return st::historyPeer8UserpicFg; + default: return st::historyPeer1UserpicFg; + } + }; + using AddItemWay = Ui::MultiSelect::AddItemWay; + auto addItemWay = skipAnimation ? AddItemWay::SkipAnimation : AddItemWay::Default; + _select->addItem(peer->id, peer->shortName(), getColor(), PaintUserpicCallback(peer), addItemWay); +} + +void ShareBox::onPeerSelectedChanged(PeerData *peer, bool checked) { + if (checked) { + addPeerToMultiSelect(peer); + _select->clearQuery(); + } else { + _select->removeItem(peer->id); + } + onSelectedChanged(); + update(); } void ShareBox::onSubmit() { @@ -597,17 +655,34 @@ void ShareBox::Inner::changeCheckState(Chat *chat) { if (!chat->checkbox.checked()) { _chatsIndexed->moveToTop(chat->peer); } - emit filterCancel(); } - chat->checkbox.setChecked(!chat->checkbox.checked()); - if (chat->checkbox.checked()) { + changePeerCheckState(chat, !chat->checkbox.checked()); +} + +void ShareBox::Inner::peerUnselected(PeerData *peer) { + // If data is nullptr we simply won't do anything. + auto chat = _dataMap.value(peer, nullptr); + changePeerCheckState(chat, false, ChangeStateWay::SkipCallback); +} + +void ShareBox::Inner::setPeerSelectedChangedCallback(base::lambda_unique callback) { + _peerSelectedChangedCallback = std_::move(callback); +} + +void ShareBox::Inner::changePeerCheckState(Chat *chat, bool checked, ChangeStateWay useCallback) { + if (chat) { + chat->checkbox.setChecked(checked); + } + if (checked) { _selected.insert(chat->peer); setActive(chatIndex(chat->peer)); } else { _selected.remove(chat->peer); } - emit selectedChanged(); + if (useCallback != ChangeStateWay::SkipCallback && _peerSelectedChangedCallback) { + _peerSelectedChangedCallback(chat->peer, checked); + } } bool ShareBox::Inner::hasSelected() const { diff --git a/Telegram/SourceFiles/boxes/sharebox.h b/Telegram/SourceFiles/boxes/sharebox.h index 96da0fb1c..7629d608e 100644 --- a/Telegram/SourceFiles/boxes/sharebox.h +++ b/Telegram/SourceFiles/boxes/sharebox.h @@ -36,7 +36,7 @@ struct PeerUpdate; } // namespace Notify namespace Ui { -class IconButton; +class MultiSelect; } // namespace Ui QString appendShareGameScoreUrl(const QString &url, const FullMsgId &fullId); @@ -52,8 +52,6 @@ public: ShareBox(CopyCallback &©Callback, SubmitCallback &&submitCallback, FilterCallback &&filterCallback); private slots: - void onFilterUpdate(); - void onFilterCancel(); void onScroll(); bool onSearchByUsername(bool searchCache = false); @@ -61,7 +59,6 @@ private slots: void onSubmit(); void onCopyLink(); - void onSelectedChanged(); void onMustScrollTo(int top, int bottom); @@ -73,8 +70,15 @@ protected: void doSetInnerFocus() override; private: + void onFilterUpdate(const QString &query); + void onSelectedChanged(); void moveButtons(); void updateButtonsVisibility(); + int getTopScrollSkip() const; + void updateScrollSkips(); + + void addPeerToMultiSelect(PeerData *peer, bool skipAnimation = false); + void onPeerSelectedChanged(PeerData *peer, bool checked); void peopleReceived(const MTPcontacts_Found &result, mtpRequestId requestId); bool peopleFailed(const RPCError &error, mtpRequestId requestId); @@ -84,8 +88,7 @@ private: class Inner; ChildWidget _inner; - ChildWidget _filter; - ChildWidget _filterCancel; + ChildWidget _select; ChildWidget _copy; ChildWidget _share; @@ -116,6 +119,9 @@ class ShareBox::Inner : public ScrolledWidget, public RPCSender, private base::S public: Inner(QWidget *parent, ShareBox::FilterCallback &&filterCallback); + void setPeerSelectedChangedCallback(base::lambda_unique callback); + void peerUnselected(PeerData *peer); + QVector selected() const; bool hasSelected() const; @@ -134,9 +140,7 @@ public slots: signals: void mustScrollTo(int ymin, int ymax); - void filterCancel(); void searchByUsername(); - void selectedChanged(); protected: void paintEvent(QPaintEvent *e) override; @@ -170,6 +174,11 @@ private: void loadProfilePhotos(int yFrom); void changeCheckState(Chat *chat); + enum class ChangeStateWay { + Default, + SkipCallback, + }; + void changePeerCheckState(Chat *chat, bool checked, ChangeStateWay useCallback = ChangeStateWay::Default); Chat *getChat(Dialogs::Row *row); void setActive(int active); @@ -198,6 +207,8 @@ private: using SelectedChats = OrderedSet; SelectedChats _selected; + base::lambda_unique _peerSelectedChangedCallback; + ChatData *data(Dialogs::Row *row); bool _searching = false; diff --git a/Telegram/SourceFiles/core/lambda_wrap.h b/Telegram/SourceFiles/core/lambda_wrap.h index f75ceb5ef..3e211781d 100644 --- a/Telegram/SourceFiles/core/lambda_wrap.h +++ b/Telegram/SourceFiles/core/lambda_wrap.h @@ -60,7 +60,7 @@ struct lambda_wrap_helper_base { protected: static void bad_construct_copy(void *lambda, const void *source) { - throw std::exception(); + t_assert(!"base::lambda bad_construct_copy() called!"); } }; @@ -72,7 +72,8 @@ struct lambda_wrap_empty : public lambda_wrap_helper_base { static void construct_move_other_method(void *lambda, void *source) { } static Return call_method(const void *lambda, Args... args) { - throw std::exception(); + t_assert(!"base::lambda empty call_method() called!"); + return Return(); } static void destruct_method(const void *lambda) { } diff --git a/Telegram/SourceFiles/ui/countryinput.cpp b/Telegram/SourceFiles/ui/countryinput.cpp index a13c1712b..c4a390e9c 100644 --- a/Telegram/SourceFiles/ui/countryinput.cpp +++ b/Telegram/SourceFiles/ui/countryinput.cpp @@ -24,7 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "lang.h" #include "application.h" #include "ui/scrollarea.h" -#include "ui/buttons/icon_button.h" +#include "ui/widgets/multi_select.h" #include "boxes/contactsbox.h" #include "countries.h" #include "styles/style_boxes.h" @@ -65,7 +65,8 @@ namespace { countriesFiltered.reserve(countriesCount); countriesNames.resize(countriesCount); } -} + +} // namespace const CountriesByCode &countriesByCode() { initCountries(); @@ -196,19 +197,17 @@ void CountryInput::setText(const QString &newText) { CountrySelectBox::CountrySelectBox() : ItemListBox(st::countriesScroll, st::boxWidth) , _inner(this) -, _filter(this, st::boxSearchField, lang(lng_country_ph)) -, _filterCancel(this, st::boxSearchCancel) +, _select(this, st::contactsMultiSelect, lang(lng_country_ph)) , _topShadow(this) { - ItemListBox::init(_inner, st::boxScrollSkip, st::boxTitleHeight + _filter->height()); + _select->resizeToWidth(st::boxWidth); - connect(_filter, SIGNAL(changed()), this, SLOT(onFilterUpdate())); - connect(_filter, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); - connect(_filterCancel, SIGNAL(clicked()), this, SLOT(onFilterCancel())); + ItemListBox::init(_inner, st::boxScrollSkip, st::boxTitleHeight + _select->height()); + + _select->setQueryChangedCallback([this](const QString &query) { onFilterUpdate(query); }); + _select->setSubmittedCallback([this](bool) { onSubmit(); }); connect(_inner, SIGNAL(mustScrollTo(int, int)), scrollArea(), SLOT(scrollToY(int, int))); connect(_inner, SIGNAL(countryChosen(const QString&)), this, SIGNAL(countryChosen(const QString&))); - _filterCancel->setAttribute(Qt::WA_OpaquePaintEvent); - prepare(); } @@ -239,40 +238,27 @@ void CountrySelectBox::paintEvent(QPaintEvent *e) { void CountrySelectBox::resizeEvent(QResizeEvent *e) { ItemListBox::resizeEvent(e); - _filter->resize(width(), _filter->height()); - _filter->moveToLeft(0, st::boxTitleHeight); - _filterCancel->moveToRight(0, st::boxTitleHeight); - _inner->resize(width(), _inner->height()); - _topShadow.setGeometry(0, st::boxTitleHeight + _filter->height(), width(), st::lineWidth); + + _select->resizeToWidth(width()); + _select->moveToLeft(0, st::boxTitleHeight); + + _inner->resizeToWidth(width()); + _topShadow.setGeometry(0, st::boxTitleHeight + _select->height(), width(), st::lineWidth); } void CountrySelectBox::showAll() { - _filter->show(); - if (_filter->getLastText().isEmpty()) { - _filterCancel->hide(); - } else { - _filterCancel->show(); - } + _select->show(); _topShadow.show(); ItemListBox::showAll(); } -void CountrySelectBox::onFilterCancel() { - _filter->setText(QString()); -} - -void CountrySelectBox::onFilterUpdate() { +void CountrySelectBox::onFilterUpdate(const QString &query) { scrollArea()->scrollToY(0); - if (_filter->getLastText().isEmpty()) { - _filterCancel->hide(); - } else { - _filterCancel->show(); - } - _inner->updateFilter(_filter->getLastText()); + _inner->updateFilter(query); } void CountrySelectBox::doSetInnerFocus() { - _filter->setFocus(); + _select->setInnerFocus(); } CountrySelectBox::Inner::Inner(QWidget *parent) : ScrolledWidget(parent) diff --git a/Telegram/SourceFiles/ui/countryinput.h b/Telegram/SourceFiles/ui/countryinput.h index 971b192cb..fe3b217a9 100644 --- a/Telegram/SourceFiles/ui/countryinput.h +++ b/Telegram/SourceFiles/ui/countryinput.h @@ -29,10 +29,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org QString findValidCode(QString fullCode); class CountrySelect; -class InputField; namespace Ui { -class IconButton; +class MultiSelect; } // namespace Ui class CountryInput : public QWidget { @@ -82,8 +81,6 @@ signals: void countryChosen(const QString &iso); public slots: - void onFilterUpdate(); - void onFilterCancel(); void onSubmit(); protected: @@ -95,10 +92,11 @@ protected: void showAll() override; private: + void onFilterUpdate(const QString &query); + class Inner; ChildWidget _inner; - ChildWidget _filter; - ChildWidget _filterCancel; + ChildWidget _select; ScrollableBoxShadow _topShadow; diff --git a/Telegram/SourceFiles/ui/flatinput.cpp b/Telegram/SourceFiles/ui/flatinput.cpp index 9cb1f3aa2..7b5966b4d 100644 --- a/Telegram/SourceFiles/ui/flatinput.cpp +++ b/Telegram/SourceFiles/ui/flatinput.cpp @@ -1890,9 +1890,7 @@ void InputField::step_placeholderFg(float64 ms, bool timer) { void InputField::step_placeholderShift(float64 ms, bool timer) { float64 dt = ms / _st.duration; if (dt >= 1) { - _a_placeholderShift.stop(); - a_placeholderLeft.finish(); - a_placeholderOpacity.finish(); + finishPlaceholderAnimation(); } else { a_placeholderLeft.update(dt, anim::linear); a_placeholderOpacity.update(dt, anim::linear); @@ -1900,6 +1898,13 @@ void InputField::step_placeholderShift(float64 ms, bool timer) { if (timer) update(); } +void InputField::finishPlaceholderAnimation() { + _a_placeholderShift.stop(); + a_placeholderLeft.finish(); + a_placeholderOpacity.finish(); + update(); +} + void InputField::step_border(float64 ms, bool timer) { float64 dt = ms / _st.duration; if (dt >= 1) { diff --git a/Telegram/SourceFiles/ui/flatinput.h b/Telegram/SourceFiles/ui/flatinput.h index 9b0ee960d..f730be8f8 100644 --- a/Telegram/SourceFiles/ui/flatinput.h +++ b/Telegram/SourceFiles/ui/flatinput.h @@ -343,6 +343,7 @@ public: } void updatePlaceholder(); void setPlaceholderHidden(bool forcePlaceholderHidden); + void finishPlaceholderAnimation(); void step_placeholderFg(float64 ms, bool timer); void step_placeholderShift(float64 ms, bool timer); diff --git a/Telegram/SourceFiles/ui/widgets/multi_select.cpp b/Telegram/SourceFiles/ui/widgets/multi_select.cpp index 373ad7e92..ef0336f7f 100644 --- a/Telegram/SourceFiles/ui/widgets/multi_select.cpp +++ b/Telegram/SourceFiles/ui/widgets/multi_select.cpp @@ -433,8 +433,8 @@ QString MultiSelect::getQuery() const { return _inner->getQuery(); } -void MultiSelect::addItem(uint64 itemId, const QString &text, const style::color &color, PaintRoundImage paintRoundImage) { - _inner->addItem(std_::make_unique(_st.item, itemId, text, color, std_::move(paintRoundImage))); +void MultiSelect::addItem(uint64 itemId, const QString &text, const style::color &color, PaintRoundImage paintRoundImage, AddItemWay way) { + _inner->addItem(std_::make_unique(_st.item, itemId, text, color, std_::move(paintRoundImage)), way); } void MultiSelect::setItemRemovedCallback(base::lambda_unique callback) { @@ -710,7 +710,7 @@ void MultiSelect::Inner::mousePressEvent(QMouseEvent *e) { } } -void MultiSelect::Inner::addItem(std_::unique_ptr item) { +void MultiSelect::Inner::addItem(std_::unique_ptr item, AddItemWay way) { auto wasEmpty = _items.empty(); item->setUpdateCallback([this, item = item.get()] { auto itemRect = item->paintArea(width() - _st.padding.left() - _st.padding.top()); @@ -723,7 +723,12 @@ void MultiSelect::Inner::addItem(std_::unique_ptr item) { if (wasEmpty) { updateHasAnyItems(true); } - _items.back()->showAnimated(); + if (way != AddItemWay::SkipAnimation) { + _items.back()->showAnimated(); + } else { + _field->finishPlaceholderAnimation(); + finishHeightAnimation(); + } } void MultiSelect::Inner::computeItemsGeometry(int newWidth) { @@ -765,16 +770,23 @@ void MultiSelect::Inner::updateItemsGeometry() { if (newHeight == _newHeight) return; _newHeight = newHeight; - _height.start([this] { - auto newHeight = _height.current(_newHeight); - if (auto heightDelta = newHeight - height()) { - resize(width(), newHeight); - if (_resizedCallback) { - _resizedCallback(heightDelta); - } - update(); + _height.start([this] { updateHeightStep(); }, height(), _newHeight, _st.item.duration); +} + +void MultiSelect::Inner::updateHeightStep() { + auto newHeight = _height.current(_newHeight); + if (auto heightDelta = newHeight - height()) { + resize(width(), newHeight); + if (_resizedCallback) { + _resizedCallback(heightDelta); } - }, height(), _newHeight, _st.item.duration); + update(); + } +} + +void MultiSelect::Inner::finishHeightAnimation() { + _height.finish(); + updateHeightStep(); } void MultiSelect::Inner::setItemText(uint64 itemId, const QString &text) { diff --git a/Telegram/SourceFiles/ui/widgets/multi_select.h b/Telegram/SourceFiles/ui/widgets/multi_select.h index 6226fc4fc..01854db33 100644 --- a/Telegram/SourceFiles/ui/widgets/multi_select.h +++ b/Telegram/SourceFiles/ui/widgets/multi_select.h @@ -40,8 +40,12 @@ public: void setSubmittedCallback(base::lambda_unique callback); void setResizedCallback(base::lambda_unique callback); + enum class AddItemWay { + Default, + SkipAnimation, + }; using PaintRoundImage = base::lambda_unique; - void addItem(uint64 itemId, const QString &text, const style::color &color, PaintRoundImage paintRoundImage); + void addItem(uint64 itemId, const QString &text, const style::color &color, PaintRoundImage paintRoundImage, AddItemWay way = AddItemWay::Default); void setItemText(uint64 itemId, const QString &text); void setItemRemovedCallback(base::lambda_unique callback); @@ -82,7 +86,7 @@ public: void setSubmittedCallback(base::lambda_unique callback); class Item; - void addItem(std_::unique_ptr item); + void addItem(std_::unique_ptr item, AddItemWay way); void setItemText(uint64 itemId, const QString &text); void setItemRemovedCallback(base::lambda_unique callback); @@ -120,6 +124,8 @@ private: updateSelection(QPoint(-1, -1)); } void updateCursor(); + void updateHeightStep(); + void finishHeightAnimation(); enum class ChangeActiveWay { Default, SkipSetFocus, diff --git a/Telegram/SourceFiles/ui/widgets/widget_slide_wrap.h b/Telegram/SourceFiles/ui/widgets/widget_slide_wrap.h index 380e65209..4c8ebc0ec 100644 --- a/Telegram/SourceFiles/ui/widgets/widget_slide_wrap.h +++ b/Telegram/SourceFiles/ui/widgets/widget_slide_wrap.h @@ -30,7 +30,7 @@ public: using UpdateCallback = base::lambda_unique; WidgetSlideWrap(QWidget *parent, Widget *entity , style::margins entityPadding - , UpdateCallback &&updateCallback + , UpdateCallback updateCallback , int duration = st::widgetSlideDuration) : TWidget(parent) , _entity(entity) , _padding(entityPadding) diff --git a/Telegram/SourceFiles/ui/widgets/widgets.style b/Telegram/SourceFiles/ui/widgets/widgets.style index 8e34c14e4..d4ab8f7a1 100644 --- a/Telegram/SourceFiles/ui/widgets/widgets.style +++ b/Telegram/SourceFiles/ui/widgets/widgets.style @@ -92,11 +92,11 @@ MultiSelect { itemSkip: pixels; field: InputField; + fieldMinWidth: pixels; fieldIcon: icon; fieldIconSkip: pixels; fieldCancel: IconButton; fieldCancelSkip: pixels; - fieldMinWidth: pixels; } widgetSlideDuration: 200;