diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index cc5f87755..d852ce950 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1188,6 +1188,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_search_found_results#one" = "Found {count} message"; "lng_search_found_results#other" = "Found {count} messages"; "lng_search_global_results" = "Global search results"; +"lng_search_messages_from" = "Show messages from"; "lng_media_save_progress" = "{ready} of {total} {mb}"; "lng_mediaview_save_as" = "Save As..."; diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 193561e50..44329463c 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -301,25 +301,7 @@ contactsMultiSelect: MultiSelect { hiding: 1000; } - item: MultiSelectItem { - padding: margins(6px, 7px, 12px, 0px); - maxWidth: 128px; - height: 32px; - style: defaultTextStyle; - textBg: contactsBgOver; - textFg: windowFg; - textActiveBg: activeButtonBg; - textActiveFg: activeButtonFg; - deleteFg: activeButtonFg; - deleteCross: CrossAnimation { - size: 32px; - skip: 10px; - stroke: 2px; - minScale: 0.3; - } - duration: 150; - minScale: 0.3; - } + item: defaultMultiSelectItem; itemSkip: 8px; field: contactsSearchField; diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index 08b79eeaf..920767b1e 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -112,6 +112,10 @@ dialogsSearchFrom: IconButton(dialogsCalendar) { icon: icon {{ "dialogs_search_from", dialogsMenuIconFg }}; iconOver: icon {{ "dialogs_search_from", dialogsMenuIconFgOver }}; } +dialogsSearchFromPadding: margins(10px, 10px, 10px, 10px); +dialogsSearchFromBubble: MultiSelectItem(defaultMultiSelectItem) { + maxWidth: 240px; +} dialogsFilter: FlatInput(defaultFlatInput) { font: font(fsize); diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 887688d40..bb72f8a7f 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -22,8 +22,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "dialogs/dialogs_indexed_list.h" #include "dialogs/dialogs_layout.h" +#include "dialogs/dialogs_search_from_controllers.h" #include "styles/style_dialogs.h" #include "styles/style_chat_helpers.h" +#include "boxes/contacts_box.h" #include "ui/widgets/buttons.h" #include "ui/widgets/popup_menu.h" #include "data/data_drafts.h" @@ -37,6 +39,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "auth_session.h" #include "window/notifications_manager.h" #include "window/window_controller.h" +#include "ui/widgets/multi_select.h" namespace { @@ -45,6 +48,11 @@ constexpr auto kStartReorderThreshold = 30; } // namespace +class DialogsInner::SearchFromBubble : public Ui::MultiSelect::Item { +public: + using Item::Item; +}; + struct DialogsInner::ImportantSwitch { Dialogs::RippleRow row; }; @@ -123,8 +131,18 @@ int DialogsInner::peerSearchOffset() const { } int DialogsInner::searchedOffset() const { - int result = peerSearchOffset() + (_peerSearchResults.empty() ? 0 : ((_peerSearchResults.size() * st::dialogsRowHeight) + st::searchedBarHeight)); - if (_searchInPeer) result += st::dialogsRowHeight; + auto result = peerSearchOffset() + (_peerSearchResults.empty() ? 0 : ((_peerSearchResults.size() * st::dialogsRowHeight) + st::searchedBarHeight)); + if (_searchInPeer) { + result += searchInPeerSkip(); + } + return result; +} + +int DialogsInner::searchInPeerSkip() const { + auto result = st::dialogsRowHeight; + if (_searchFromUserBubble) { + result += st::lineWidth + st::dialogsSearchFromPadding.top() + _searchFromUserBubble->rect().height() + st::dialogsSearchFromPadding.bottom(); + } return result; } @@ -300,8 +318,8 @@ void DialogsInner::paintRegion(Painter &p, const QRegion ®ion, bool paintingO } if (_searchInPeer) { - paintSearchInPeer(p, fullWidth, paintingOther); - p.translate(0, st::dialogsRowHeight); + paintSearchInPeer(p, fullWidth, paintingOther, ms); + p.translate(0, searchInPeerSkip()); if (_state == FilteredState && _searchResults.empty()) { p.fillRect(0, 0, fullWidth, st::searchedBarHeight, st::searchedBarBg); if (!paintingOther) { @@ -409,9 +427,13 @@ void DialogsInner::paintPeerSearchResult(Painter &p, const PeerSearchResult *res peer->dialogName().drawElided(p, rectForName.left(), rectForName.top(), rectForName.width()); } -void DialogsInner::paintSearchInPeer(Painter &p, int fullWidth, bool onlyBackground) const { - QRect fullRect(0, 0, fullWidth, st::dialogsRowHeight); +void DialogsInner::paintSearchInPeer(Painter &p, int fullWidth, bool onlyBackground, TimeMs ms) const { + auto height = searchInPeerSkip(); + auto fullRect = QRect(0, 0, fullWidth, height); p.fillRect(fullRect, st::dialogsBg); + if (_searchFromUserBubble) { + p.fillRect(QRect(0, st::dialogsRowHeight, width(), st::lineWidth), st::shadowFg); + } if (onlyBackground) return; _searchInPeer->paintUserpicLeft(p, st::dialogsPadding.x(), st::dialogsPadding.y(), getFullWidth(), st::dialogsPhotoSize); @@ -432,14 +454,19 @@ void DialogsInner::paintSearchInPeer(Painter &p, int fullWidth, bool onlyBackgro p.setPen(st::dialogsNameFg); _searchInPeer->nameText.drawElided(p, rectForName.left(), rectForName.top(), rectForName.width()); + + if (_searchFromUserBubble) { + _searchFromUserBubble->paint(p, width(), ms); + } } void DialogsInner::activate() { } void DialogsInner::mouseMoveEvent(QMouseEvent *e) { + auto position = e->pos(); _mouseSelection = true; - updateSelected(e->pos()); + updateSelected(position); } void DialogsInner::clearIrrelevantState() { @@ -465,6 +492,15 @@ void DialogsInner::updateSelected(QPoint localPos) { if (updateReorderPinned(localPos)) { return; } + + if (_searchFromUserBubble) { + if (_searchFromUserBubble->rect().contains(localPos)) { + _searchFromUserBubble->mouseMoveEvent(localPos - _searchFromUserBubble->rect().topLeft()); + } else { + _searchFromUserBubble->leaveEvent(); + } + } + if (!_mouseSelection) { return; } @@ -542,10 +578,26 @@ void DialogsInner::updateSelected(QPoint localPos) { } } +void DialogsInner::handleSearchFromUserClick() { + Expects(_searchFromUserBubble != nullptr); + if (_searchFromUserBubble->isOverDelete()) { + searchFromUserChanged.notify(nullptr); + } else { + Dialogs::ShowSearchFromBox(_searchInPeer, base::lambda_guarded(this, [this](gsl::not_null user) { + Ui::hideLayer(); + searchFromUserChanged.notify(user); + })); + } +} + void DialogsInner::mousePressEvent(QMouseEvent *e) { _mouseSelection = true; updateSelected(e->pos()); + if (_searchFromUserBubble && _searchFromUserBubble->rect().contains(e->pos())) { + return handleSearchFromUserClick(); + } + _pressButton = e->button(); setPressed(_selected); setImportantSwitchPressed(_importantSwitchSelected); @@ -904,6 +956,13 @@ void DialogsInner::resizeEvent(QResizeEvent *e) { _addContactLnk->move((width() - _addContactLnk->width()) / 2, (st::noContactsHeight + st::noContactsFont->height) / 2); auto widthForCancelButton = qMax(width() + otherWidth(), st::dialogsWidthMin); _cancelSearchInPeer->moveToLeft(widthForCancelButton - st::dialogsFilterSkip - st::dialogsFilterPadding.x() - _cancelSearchInPeer->width(), (st::dialogsRowHeight - st::dialogsCancelSearchInPeer.height) / 2); + updateSearchFromBubble(); +} + +void DialogsInner::updateSearchFromBubble() { + if (_searchFromUserBubble) { + _searchFromUserBubble->setPosition(st::dialogsSearchFromPadding.left(), st::dialogsRowHeight + st::lineWidth + st::dialogsSearchFromPadding.top(), width(), st::dialogsSearchFromPadding.left()); + } } void DialogsInner::onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRow) { @@ -1134,6 +1193,9 @@ void DialogsInner::updateSelectedRow(PeerData *peer) { void DialogsInner::leaveEventHook(QEvent *e) { setMouseTracking(false); clearSelection(); + if (_searchFromUserBubble) { + _searchFromUserBubble->leaveEvent(); + } } void DialogsInner::dragLeft() { @@ -1233,15 +1295,8 @@ void DialogsInner::onFilterUpdate(QString newFilter, bool force) { newFilter = words.isEmpty() ? QString() : words.join(' '); if (newFilter != _filter || force) { _filter = newFilter; - if (!_searchInPeer && _filter.isEmpty()) { - _state = DefaultState; - _hashtagResults.clear(); - _filterResults.clear(); - _peerSearchResults.clear(); - _searchResults.clear(); - _lastSearchDate = 0; - _lastSearchPeer = 0; - _lastSearchId = _lastSearchMigratedId = 0; + if (_filter.isEmpty() && !_searchFromUser) { + clearFilter(); } else { QStringList::const_iterator fb = words.cbegin(), fe = words.cend(), fi; @@ -1316,8 +1371,8 @@ void DialogsInner::onFilterUpdate(QString newFilter, bool force) { } } } + refresh(true); } - refresh(true); setMouseSelection(false, true); } if (_state != DefaultState) { @@ -1695,9 +1750,17 @@ bool DialogsInner::hasFilteredResults() const { return !_filterResults.isEmpty() && _hashtagResults.empty(); } -void DialogsInner::searchInPeer(PeerData *peer) { +void DialogsInner::searchInPeer(PeerData *peer, UserData *from) { _searchInPeer = peer ? (peer->migrateTo() ? peer->migrateTo() : peer) : nullptr; _searchInMigrated = _searchInPeer ? _searchInPeer->migrateFrom() : nullptr; + _searchFromUser = from; + if (_searchFromUser) { + _searchFromUserBubble = std::make_unique(st::dialogsSearchFromBubble, _searchFromUser->id, App::peerName(_searchFromUser), st::activeButtonBg, PaintUserpicCallback(_searchFromUser)); + _searchFromUserBubble->setUpdateCallback([this] { update(0, st::dialogsRowHeight + st::lineWidth, width(), searchInPeerSkip() - st::dialogsRowHeight - st::lineWidth); }); + updateSearchFromBubble(); + } else { + _searchFromUserBubble.reset(); + } if (_searchInPeer) { onHashtagFilterUpdate(QStringRef()); _cancelSearchInPeer->show(); @@ -1708,7 +1771,7 @@ void DialogsInner::searchInPeer(PeerData *peer) { } void DialogsInner::clearFilter() { - if (_state == FilteredState || _state == SearchedState) { + if (_state == FilteredState || _state == SearchedState || _searchInPeer) { if (_searchInPeer) { _state = FilteredState; } else { diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index fbb8685cf..08647fc94 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -96,7 +96,7 @@ public: State state() const; bool hasFilteredResults() const; - void searchInPeer(PeerData *peer); + void searchInPeer(PeerData *peer, UserData *from); void onFilterUpdate(QString newFilter, bool force = false); void onHashtagFilterUpdate(QStringRef newFilter); @@ -108,6 +108,8 @@ public: } void setVisibleTopBottom(int visibleTop, int visibleBottom) override; + base::Observable searchFromUserChanged; + void notify_userIsContactChanged(UserData *user, bool fromThisApp); void notify_historyMuteUpdated(History *history); @@ -187,14 +189,17 @@ private: int filteredOffset() const; int peerSearchOffset() const; int searchedOffset() const; + int searchInPeerSkip() const; void paintDialog(Painter &p, Dialogs::Row *row, int fullWidth, PeerData *active, PeerData *selected, bool onlyBackground, TimeMs ms); void paintPeerSearchResult(Painter &p, const PeerSearchResult *result, int fullWidth, bool active, bool selected, bool onlyBackground, TimeMs ms) const; - void paintSearchInPeer(Painter &p, int fullWidth, bool onlyBackground) const; + void paintSearchInPeer(Painter &p, int fullWidth, bool onlyBackground, TimeMs ms) const; void clearSelection(); void clearSearchResults(bool clearPeerSearchResults = true); void updateSelectedRow(PeerData *peer = 0); + void updateSearchFromBubble(); + void handleSearchFromUserClick(); Dialogs::IndexedList *shownDialogs() const { return (Global::DialogsMode() == Dialogs::Mode::Important) ? _dialogsImportant.get() : _dialogs.get(); @@ -279,6 +284,9 @@ private: PeerData *_searchInPeer = nullptr; PeerData *_searchInMigrated = nullptr; + UserData *_searchFromUser = nullptr; + class SearchFromBubble; // Just a wrap for Ui::MultiSelect::Item. + std::unique_ptr _searchFromUserBubble; PeerData *_menuPeer = nullptr; Ui::PopupMenu *_menu = nullptr; diff --git a/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.cpp b/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.cpp new file mode 100644 index 000000000..da2f9225d --- /dev/null +++ b/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.cpp @@ -0,0 +1,134 @@ +/* +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 "dialogs/dialogs_search_from_controllers.h" + +#include "lang/lang_keys.h" +#include "observer_peer.h" +#include "auth_session.h" +#include "apiwrap.h" + +namespace Dialogs { + +void ShowSearchFromBox(PeerData *peer, base::lambda)> callback) { + auto createController = [peer, callback = std::move(callback)]()->std::unique_ptr { + if (peer) { + if (auto chat = peer->asChat()) { + return std::make_unique(chat, std::move(callback)); + } else if (auto group = peer->asMegagroup()) { + return std::make_unique(group, std::move(callback)); + } + } + return nullptr; + }; + if (auto controller = createController()) { + Ui::show(Box(std::move(controller), [](PeerListBox *box) { + box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); }); + }), KeepOtherLayers); + } +} + +ChatSearchFromController::ChatSearchFromController(gsl::not_null chat, base::lambda)> callback) : PeerListController() +, _chat(chat) +, _callback(std::move(callback)) { +} + +void ChatSearchFromController::prepare() { + setSearchNoResultsText(lang(lng_blocked_list_not_found)); + delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled); + delegate()->peerListSetTitle(langFactory(lng_search_messages_from)); + + rebuildRows(); + + subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(Notify::PeerUpdate::Flag::MembersChanged, [this](const Notify::PeerUpdate &update) { + if (update.peer == _chat) { + rebuildRows(); + } + })); +} + +void ChatSearchFromController::rowClicked(gsl::not_null row) { + Expects(row->peer()->isUser()); + _callback(row->peer()->asUser()); +} + +void ChatSearchFromController::rebuildRows() { + auto ms = getms(); + auto wasEmpty = !delegate()->peerListFullRowsCount(); + + auto now = unixtime(); + QMultiMap ordered; + if (_chat->noParticipantInfo()) { + AuthSession::Current().api().requestFullPeer(_chat); + } else if (!_chat->participants.isEmpty()) { + for (auto i = _chat->participants.cbegin(), e = _chat->participants.cend(); i != e; ++i) { + auto user = i.key(); + ordered.insertMulti(App::onlineForSort(user, now), user); + } + } + for_const (auto user, _chat->lastAuthors) { + if (user->isInaccessible()) continue; + appendRow(user); + if (!ordered.isEmpty()) { + ordered.remove(App::onlineForSort(user, now), user); + } + } + if (!ordered.isEmpty()) { + for (auto i = ordered.cend(), b = ordered.cbegin(); i != b;) { + appendRow(*(--i)); + } + } + checkForEmptyRows(); + delegate()->peerListRefreshRows(); +} + +void ChatSearchFromController::checkForEmptyRows() { + if (delegate()->peerListFullRowsCount()) { + setDescriptionText(QString()); + } else { + setDescriptionText(lang(lng_contacts_loading)); + } +} + +void ChatSearchFromController::appendRow(gsl::not_null user) { + if (!delegate()->peerListFindRow(user->id)) { + delegate()->peerListAppendRow(std::make_unique(user)); + } +} + +ChannelSearchFromController::ChannelSearchFromController(gsl::not_null channel, base::lambda)> callback) : ParticipantsBoxController(channel, ParticipantsBoxController::Role::Members) +, _callback(std::move(callback)) { +} + +void ChannelSearchFromController::prepare() { + ParticipantsBoxController::prepare(); + delegate()->peerListSetTitle(langFactory(lng_search_messages_from)); +} + +void ChannelSearchFromController::rowClicked(gsl::not_null row) { + Expects(row->peer()->isUser()); + _callback(row->peer()->asUser()); +} + +std::unique_ptr ChannelSearchFromController::createRow(gsl::not_null user) const { + return std::make_unique(user); +} + +} // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.h b/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.h new file mode 100644 index 000000000..c0926dd10 --- /dev/null +++ b/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.h @@ -0,0 +1,62 @@ +/* +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 "boxes/peer_list_box.h" +#include "profile/profile_channel_controllers.h" + +namespace Dialogs { + +void ShowSearchFromBox(PeerData *peer, base::lambda)> callback); + +class ChatSearchFromController : public PeerListController, protected base::Subscriber { +public: + ChatSearchFromController(gsl::not_null chat, base::lambda)> callback); + + void prepare() override; + void rowClicked(gsl::not_null row) override; + +private: + void rebuildRows(); + void checkForEmptyRows(); + void appendRow(gsl::not_null user); + + gsl::not_null _chat; + base::lambda)> _callback; + +}; + +class ChannelSearchFromController : public Profile::ParticipantsBoxController { +public: + ChannelSearchFromController(gsl::not_null channel, base::lambda)> callback); + + void prepare() override; + void rowClicked(gsl::not_null row) override; + +protected: + std::unique_ptr createRow(gsl::not_null user) const override; + +private: + base::lambda)> _callback; + +}; + +} // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 466dc607c..ed62d77e8 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "dialogs/dialogs_widget.h" #include "dialogs/dialogs_inner_widget.h" +#include "dialogs/dialogs_search_from_controllers.h" #include "styles/style_dialogs.h" #include "ui/widgets/buttons.h" #include "lang/lang_keys.h" @@ -32,7 +33,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "auth_session.h" #include "messenger.h" #include "ui/effects/widget_fade_wrap.h" +#include "boxes/peer_list_box.h" #include "window/window_controller.h" +#include "profile/profile_channel_controllers.h" class DialogsWidget::UpdateButton : public Ui::RippleButton { public: @@ -83,7 +86,7 @@ void DialogsWidget::UpdateButton::paintEvent(QPaintEvent *e) { DialogsWidget::DialogsWidget(QWidget *parent, gsl::not_null controller) : Window::AbstractSectionWidget(parent, controller) , _mainMenuToggle(this, st::dialogsMenuToggle) , _filter(this, st::dialogsFilter, langFactory(lng_dlg_filter)) -, _searchFromUser(this, object_ptr(this, st::dialogsSearchFrom)) +, _chooseFromUser(this, object_ptr(this, st::dialogsSearchFrom)) , _jumpToDate(this, object_ptr(this, st::dialogsCalendar)) , _cancelSearch(this, st::dialogsCancelSearch) , _lockUnlock(this, st::dialogsLock) @@ -97,6 +100,10 @@ DialogsWidget::DialogsWidget(QWidget *parent, gsl::not_null connect(_inner, SIGNAL(completeHashtag(QString)), this, SLOT(onCompleteHashtag(QString))); connect(_inner, SIGNAL(refreshHashtags()), this, SLOT(onFilterCursorMoved())); connect(_inner, SIGNAL(cancelSearchInPeer()), this, SLOT(onCancelSearchInPeer())); + subscribe(_inner->searchFromUserChanged, [this](UserData *user) { + setSearchInPeer(_searchInPeer, user); + onFilterUpdate(true); + }); connect(_scroll, SIGNAL(geometryChanged()), _inner, SLOT(onParentGeometryChanged())); connect(_scroll, SIGNAL(scrolled()), this, SLOT(onListScroll())); connect(_filter, SIGNAL(cancelled()), this, SLOT(onCancel())); @@ -114,7 +121,7 @@ DialogsWidget::DialogsWidget(QWidget *parent, gsl::not_null _cancelSearch->setClickedCallback([this] { onCancelSearch(); }); _jumpToDate->entity()->setClickedCallback([this] { if (_searchInPeer) this->controller()->showJumpToDate(_searchInPeer, QDate()); }); - _searchFromUser->entity()->setClickedCallback([this] { if (_searchInPeer->isChat() || _searchInPeer->isMegagroup()) showSearchFrom(); }); + _chooseFromUser->entity()->setClickedCallback([this] { showSearchFrom(); }); _lockUnlock->setVisible(Global::LocalPasscode()); subscribe(Global::RefLocalPasscodeChanged(), [this] { updateLockUnlockVisibility(); }); _lockUnlock->setClickedCallback([this] { @@ -144,6 +151,7 @@ DialogsWidget::DialogsWidget(QWidget *parent, gsl::not_null _filter->customUpDown(true); updateJumpToDateVisibility(true); + updateSearchFromVisibility(true); } #ifndef TDESKTOP_DISABLE_AUTOUPDATE @@ -241,7 +249,7 @@ void DialogsWidget::showAnimated(Window::SlideDirection direction, const Window: _filter->hide(); _cancelSearch->hideFast(); _jumpToDate->hideFast(); - _searchFromUser->hideFast(); + _chooseFromUser->hideFast(); _lockUnlock->hide(); int delta = st::slideShift; @@ -270,6 +278,7 @@ void DialogsWidget::animationCallback() { _filter->show(); updateLockUnlockVisibility(); updateJumpToDateVisibility(true); + updateSearchFromVisibility(true); onFilterUpdate(); if (App::wnd()) App::wnd()->setInnerFocus(); @@ -450,8 +459,8 @@ void DialogsWidget::onDraggingScrollTimer() { } bool DialogsWidget::onSearchMessages(bool searchCache) { - QString q = _filter->getLastText().trimmed(); - if (q.isEmpty()) { + auto q = _filter->getLastText().trimmed(); + if (q.isEmpty() && !_searchFromUser) { MTP::cancel(base::take(_searchRequest)); MTP::cancel(base::take(_peerSearchRequest)); return true; @@ -460,17 +469,20 @@ bool DialogsWidget::onSearchMessages(bool searchCache) { SearchCache::const_iterator i = _searchCache.constFind(q); if (i != _searchCache.cend()) { _searchQuery = q; + _searchQueryFrom = _searchFromUser; _searchFull = _searchFullMigrated = false; MTP::cancel(base::take(_searchRequest)); searchReceived(_searchInPeer ? DialogsSearchPeerFromStart : DialogsSearchFromStart, i.value(), 0); return true; } - } else if (_searchQuery != q) { + } else if (_searchQuery != q || _searchQueryFrom != _searchFromUser) { _searchQuery = q; + _searchQueryFrom = _searchFromUser; _searchFull = _searchFullMigrated = false; MTP::cancel(base::take(_searchRequest)); if (_searchInPeer) { - _searchRequest = MTP::send(MTPmessages_Search(MTP_flags(0), _searchInPeer->input, MTP_string(_searchQuery), MTP_inputUserEmpty(), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, DialogsSearchPeerFromStart), rpcFail(&DialogsWidget::searchFailed, DialogsSearchPeerFromStart)); + auto flags = _searchQueryFrom ? MTP_flags(MTPmessages_Search::Flag::f_from_id) : MTP_flags(0); + _searchRequest = MTP::send(MTPmessages_Search(flags, _searchInPeer->input, MTP_string(_searchQuery), _searchQueryFrom ? _searchQueryFrom->inputUser : MTP_inputUserEmpty(), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, DialogsSearchPeerFromStart), rpcFail(&DialogsWidget::searchFailed, DialogsSearchPeerFromStart)); } else { _searchRequest = MTP::send(MTPmessages_SearchGlobal(MTP_string(_searchQuery), MTP_int(0), MTP_inputPeerEmpty(), MTP_int(0), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, DialogsSearchFromStart), rpcFail(&DialogsWidget::searchFailed, DialogsSearchFromStart)); } @@ -532,7 +544,8 @@ void DialogsWidget::onSearchMore() { auto offsetPeer = _inner->lastSearchPeer(); auto offsetId = _inner->lastSearchId(); if (_searchInPeer) { - _searchRequest = MTP::send(MTPmessages_Search(MTP_flags(0), _searchInPeer->input, MTP_string(_searchQuery), MTP_inputUserEmpty(), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(offsetId), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, offsetId ? DialogsSearchPeerFromOffset : DialogsSearchPeerFromStart), rpcFail(&DialogsWidget::searchFailed, offsetId ? DialogsSearchPeerFromOffset : DialogsSearchPeerFromStart)); + auto flags = _searchQueryFrom ? MTP_flags(MTPmessages_Search::Flag::f_from_id) : MTP_flags(0); + _searchRequest = MTP::send(MTPmessages_Search(flags, _searchInPeer->input, MTP_string(_searchQuery), _searchQueryFrom ? _searchQueryFrom->inputUser : MTP_inputUserEmpty(), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(offsetId), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, offsetId ? DialogsSearchPeerFromOffset : DialogsSearchPeerFromStart), rpcFail(&DialogsWidget::searchFailed, offsetId ? DialogsSearchPeerFromOffset : DialogsSearchPeerFromStart)); } else { _searchRequest = MTP::send(MTPmessages_SearchGlobal(MTP_string(_searchQuery), MTP_int(offsetDate), offsetPeer ? offsetPeer->input : MTP_inputPeerEmpty(), MTP_int(offsetId), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, offsetId ? DialogsSearchFromOffset : DialogsSearchFromStart), rpcFail(&DialogsWidget::searchFailed, offsetId ? DialogsSearchFromOffset : DialogsSearchFromStart)); } @@ -541,7 +554,8 @@ void DialogsWidget::onSearchMore() { } } else if (_searchInMigrated && !_searchFullMigrated) { auto offsetMigratedId = _inner->lastSearchMigratedId(); - _searchRequest = MTP::send(MTPmessages_Search(MTP_flags(0), _searchInMigrated->input, MTP_string(_searchQuery), MTP_inputUserEmpty(), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(offsetMigratedId), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, offsetMigratedId ? DialogsSearchMigratedFromOffset : DialogsSearchMigratedFromStart), rpcFail(&DialogsWidget::searchFailed, offsetMigratedId ? DialogsSearchMigratedFromOffset : DialogsSearchMigratedFromStart)); + auto flags = _searchQueryFrom ? MTP_flags(MTPmessages_Search::Flag::f_from_id) : MTP_flags(0); + _searchRequest = MTP::send(MTPmessages_Search(flags, _searchInMigrated->input, MTP_string(_searchQuery), _searchQueryFrom ? _searchQueryFrom->inputUser : MTP_inputUserEmpty(), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(offsetMigratedId), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, offsetMigratedId ? DialogsSearchMigratedFromOffset : DialogsSearchMigratedFromStart), rpcFail(&DialogsWidget::searchFailed, offsetMigratedId ? DialogsSearchMigratedFromOffset : DialogsSearchMigratedFromStart)); } } } @@ -783,9 +797,7 @@ void DialogsWidget::onFilterUpdate(bool force) { auto filterText = _filter->getLastText(); _inner->onFilterUpdate(filterText, force); if (filterText.isEmpty()) { - _searchCache.clear(); - _searchQueries.clear(); - _searchQuery = QString(); + clearSearchCache(); _cancelSearch->hideAnimated(); } else { _cancelSearch->showAnimated(); @@ -805,19 +817,42 @@ void DialogsWidget::searchInPeer(PeerData *peer) { onFilterUpdate(true); } -void DialogsWidget::setSearchInPeer(PeerData *peer) { +void DialogsWidget::setSearchInPeer(PeerData *peer, UserData *from) { + auto searchInPeerUpdated = false; auto newSearchInPeer = peer ? (peer->migrateTo() ? peer->migrateTo() : peer) : nullptr; _searchInMigrated = newSearchInPeer ? newSearchInPeer->migrateFrom() : nullptr; - if (newSearchInPeer != _searchInPeer) { + searchInPeerUpdated = (newSearchInPeer != _searchInPeer); + if (searchInPeerUpdated) { _searchInPeer = newSearchInPeer; + from = nullptr; controller()->searchInPeerChanged().notify(_searchInPeer, true); updateJumpToDateVisibility(); + } else if (!_searchInPeer) { + from = nullptr; } - _inner->searchInPeer(_searchInPeer); + if (_searchFromUser != from || searchInPeerUpdated) { + _searchFromUser = from; + updateSearchFromVisibility(); + clearSearchCache(); + } + _inner->searchInPeer(_searchInPeer, _searchFromUser); +} + +void DialogsWidget::clearSearchCache() { + _searchCache.clear(); + _searchQueries.clear(); + _searchQuery = QString(); + _searchQueryFrom = nullptr; + MTP::cancel(base::take(_searchRequest)); } void DialogsWidget::showSearchFrom() { - + auto peer = _searchInPeer; + Dialogs::ShowSearchFromBox(peer, base::lambda_guarded(this, [this, peer](gsl::not_null user) { + Ui::hideLayer(); + setSearchInPeer(peer, user); + onFilterUpdate(true); + })); } void DialogsWidget::onFilterCursorMoved(int from, int to) { @@ -883,12 +918,14 @@ void DialogsWidget::updateJumpToDateVisibility(bool fast) { } else { _jumpToDate->toggleAnimated(jumpToDateVisible); } +} - auto searchFromUserVisible = _searchInPeer && (_searchInPeer->isChat() || _searchInPeer->isMegagroup()); +void DialogsWidget::updateSearchFromVisibility(bool fast) { + auto searchFromUserVisible = _searchInPeer && (_searchInPeer->isChat() || _searchInPeer->isMegagroup()) && !_searchFromUser; if (fast) { - _searchFromUser->toggleFast(searchFromUserVisible); + _chooseFromUser->toggleFast(searchFromUserVisible); } else { - _searchFromUser->toggleAnimated(searchFromUserVisible); + _chooseFromUser->toggleAnimated(searchFromUserVisible); } } @@ -909,10 +946,11 @@ void DialogsWidget::updateControlsGeometry() { _filter->setGeometryToLeft(filterLeft, filterTop, filterWidth, _filter->height()); auto mainMenuLeft = anim::interpolate(st::dialogsFilterPadding.x(), (smallLayoutWidth - _mainMenuToggle->width()) / 2, smallLayoutRatio); _mainMenuToggle->moveToLeft(mainMenuLeft, filterAreaTop + st::dialogsFilterPadding.y()); - _lockUnlock->moveToLeft(filterLeft + filterWidth + st::dialogsFilterPadding.x(), filterAreaTop + st::dialogsFilterPadding.y()); - _cancelSearch->moveToLeft(filterLeft + filterWidth - _cancelSearch->width(), _filter->y()); - _jumpToDate->moveToLeft(filterLeft + filterWidth - _jumpToDate->width(), _filter->y()); - _searchFromUser->moveToLeft(filterLeft + filterWidth - _jumpToDate->width() - _searchFromUser->width(), _filter->y()); + auto right = filterLeft + filterWidth; + _lockUnlock->moveToLeft(right + st::dialogsFilterPadding.x(), filterAreaTop + st::dialogsFilterPadding.y()); + _cancelSearch->moveToLeft(right - _cancelSearch->width(), _filter->y()); + right -= _jumpToDate->width(); _jumpToDate->moveToLeft(right, _filter->y()); + right -= _chooseFromUser->width(); _chooseFromUser->moveToLeft(right, _filter->y()); auto scrollTop = filterAreaTop + filterAreaHeight; auto addToScroll = App::main() ? App::main()->contentScrollAddToY() : 0; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index 90491918c..1ff4d5965 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -150,11 +150,13 @@ private: void searchReceived(DialogsSearchRequestType type, const MTPmessages_Messages &result, mtpRequestId requestId); void peerSearchReceived(const MTPcontacts_Found &result, mtpRequestId requestId); - void setSearchInPeer(PeerData *peer); + void setSearchInPeer(PeerData *peer, UserData *from = nullptr); void showSearchFrom(); void showMainMenu(); + void clearSearchCache(); void updateLockUnlockVisibility(); void updateJumpToDateVisibility(bool fast = false); + void updateSearchFromVisibility(bool fast = false); void updateControlsGeometry(); void updateForwardBar(); @@ -180,7 +182,7 @@ private: object_ptr _forwardCancel = { nullptr }; object_ptr _mainMenuToggle; object_ptr _filter; - object_ptr> _searchFromUser; + object_ptr> _chooseFromUser; object_ptr> _jumpToDate; object_ptr _cancelSearch; object_ptr _lockUnlock; @@ -195,6 +197,7 @@ private: PeerData *_searchInPeer = nullptr; PeerData *_searchInMigrated = nullptr; + UserData *_searchFromUser = nullptr; QTimer _searchTimer; @@ -203,6 +206,7 @@ private: mtpRequestId _peerSearchRequest = 0; QString _searchQuery; + UserData *_searchQueryFrom = nullptr; bool _searchFull = false; bool _searchFullMigrated = false; mtpRequestId _searchRequest = 0; diff --git a/Telegram/SourceFiles/profile/profile_channel_controllers.h b/Telegram/SourceFiles/profile/profile_channel_controllers.h index 4728b056d..5177f88c2 100644 --- a/Telegram/SourceFiles/profile/profile_channel_controllers.h +++ b/Telegram/SourceFiles/profile/profile_channel_controllers.h @@ -65,6 +65,9 @@ public: template static void HandleParticipant(const MTPChannelParticipant &participant, Role role, gsl::not_null additional, Callback callback); +protected: + virtual std::unique_ptr createRow(gsl::not_null user) const; + private: static std::unique_ptr CreateSearchController(gsl::not_null channel, Role role, gsl::not_null additional); @@ -78,7 +81,6 @@ private: bool appendRow(gsl::not_null user); bool prependRow(gsl::not_null user); bool removeRow(gsl::not_null user); - std::unique_ptr createRow(gsl::not_null user) const; void refreshCustomStatus(gsl::not_null row) const; bool feedMegagroupLastParticipants(); diff --git a/Telegram/SourceFiles/ui/widgets/multi_select.cpp b/Telegram/SourceFiles/ui/widgets/multi_select.cpp index 38aecd493..a60f29ac5 100644 --- a/Telegram/SourceFiles/ui/widgets/multi_select.cpp +++ b/Telegram/SourceFiles/ui/widgets/multi_select.cpp @@ -34,89 +34,7 @@ constexpr int kWideScale = 3; } // namespace -class MultiSelect::Inner::Item { -public: - Item(const style::MultiSelectItem &st, uint64 id, const QString &text, style::color color, PaintRoundImage &&paintRoundImage); - - uint64 id() const { - return _id; - } - int getWidth() const { - return _width; - } - QRect rect() const { - return QRect(_x, _y, _width, _st.height); - } - bool isOverDelete() const { - return _overDelete; - } - void setActive(bool active) { - _active = active; - } - void setPosition(int x, int y, int outerWidth, int maxVisiblePadding); - QRect paintArea(int outerWidth) const; - - void setUpdateCallback(base::lambda updateCallback) { - _updateCallback = updateCallback; - } - void setText(const QString &text); - void paint(Painter &p, int outerWidth, TimeMs ms); - - void mouseMoveEvent(QPoint point); - void leaveEvent(); - - void showAnimated() { - setVisibleAnimated(true); - } - void hideAnimated() { - setVisibleAnimated(false); - } - bool hideFinished() const { - return (_hiding && !_visibility.animating()); - } - - -private: - void setOver(bool over); - void paintOnce(Painter &p, int x, int y, int outerWidth, TimeMs ms); - void paintDeleteButton(Painter &p, int x, int y, int outerWidth, float64 overOpacity); - bool paintCached(Painter &p, int x, int y, int outerWidth); - void prepareCache(); - void setVisibleAnimated(bool visible); - - const style::MultiSelectItem &_st; - - uint64 _id; - struct SlideAnimation { - SlideAnimation(base::lambda updateCallback, int fromX, int toX, int y, float64 duration) - : fromX(fromX) - , toX(toX) - , y(y) { - x.start(updateCallback, fromX, toX, duration); - } - Animation x; - int fromX, toX; - int y; - }; - std::vector _copies; - int _x = -1; - int _y = -1; - int _width = 0; - Text _text; - style::color _color; - bool _over = false; - QPixmap _cache; - Animation _visibility; - Animation _overOpacity; - bool _overDelete = false; - bool _active = false; - PaintRoundImage _paintRoundImage; - base::lambda _updateCallback; - bool _hiding = false; - -}; - -MultiSelect::Inner::Item::Item(const style::MultiSelectItem &st, uint64 id, const QString &text, style::color color, PaintRoundImage &&paintRoundImage) +MultiSelect::Item::Item(const style::MultiSelectItem &st, uint64 id, const QString &text, style::color color, PaintRoundImage &&paintRoundImage) : _st(st) , _id(id) , _color(color) @@ -124,13 +42,13 @@ MultiSelect::Inner::Item::Item(const style::MultiSelectItem &st, uint64 id, cons setText(text); } -void MultiSelect::Inner::Item::setText(const QString &text) { +void MultiSelect::Item::setText(const QString &text) { _text.setText(_st.style, text, _textNameOptions); _width = _st.height + _st.padding.left() + _text.maxWidth() + _st.padding.right(); accumulate_min(_width, _st.maxWidth); } -void MultiSelect::Inner::Item::paint(Painter &p, int outerWidth, TimeMs ms) { +void MultiSelect::Item::paint(Painter &p, int outerWidth, TimeMs ms) { if (!_cache.isNull() && !_visibility.animating(ms)) { if (_hiding) { return; @@ -158,7 +76,7 @@ void MultiSelect::Inner::Item::paint(Painter &p, int outerWidth, TimeMs ms) { } } -void MultiSelect::Inner::Item::paintOnce(Painter &p, int x, int y, int outerWidth, TimeMs ms) { +void MultiSelect::Item::paintOnce(Painter &p, int x, int y, int outerWidth, TimeMs ms) { if (!_cache.isNull()) { paintCached(p, x, y, outerWidth); return; @@ -198,7 +116,7 @@ void MultiSelect::Inner::Item::paintOnce(Painter &p, int x, int y, int outerWidt _text.drawLeftElided(p, x + textLeft, y + _st.padding.top(), textWidth, outerWidth); } -void MultiSelect::Inner::Item::paintDeleteButton(Painter &p, int x, int y, int outerWidth, float64 overOpacity) { +void MultiSelect::Item::paintDeleteButton(Painter &p, int x, int y, int outerWidth, float64 overOpacity) { p.setOpacity(overOpacity); p.setPen(Qt::NoPen); @@ -213,7 +131,7 @@ void MultiSelect::Inner::Item::paintDeleteButton(Painter &p, int x, int y, int o p.setOpacity(1.); } -bool MultiSelect::Inner::Item::paintCached(Painter &p, int x, int y, int outerWidth) { +bool MultiSelect::Item::paintCached(Painter &p, int x, int y, int outerWidth) { PainterHighQualityEnabler hq(p); auto opacity = _visibility.current(_hiding ? 0. : 1.); @@ -227,18 +145,18 @@ bool MultiSelect::Inner::Item::paintCached(Painter &p, int x, int y, int outerWi return true; } -void MultiSelect::Inner::Item::mouseMoveEvent(QPoint point) { +void MultiSelect::Item::mouseMoveEvent(QPoint point) { if (!_cache.isNull()) return; _overDelete = QRect(0, 0, _st.height, _st.height).contains(point); setOver(true); } -void MultiSelect::Inner::Item::leaveEvent() { +void MultiSelect::Item::leaveEvent() { _overDelete = false; setOver(false); } -void MultiSelect::Inner::Item::setPosition(int x, int y, int outerWidth, int maxVisiblePadding) { +void MultiSelect::Item::setPosition(int x, int y, int outerWidth, int maxVisiblePadding) { if (_x >= 0 && _y >= 0 && (_x != x || _y != y)) { // Make an animation if it is not the first setPosition(). auto found = false; @@ -277,7 +195,7 @@ void MultiSelect::Inner::Item::setPosition(int x, int y, int outerWidth, int max _y = y; } -QRect MultiSelect::Inner::Item::paintArea(int outerWidth) const { +QRect MultiSelect::Item::paintArea(int outerWidth) const { if (_copies.empty()) { return rect(); } @@ -293,7 +211,7 @@ QRect MultiSelect::Inner::Item::paintArea(int outerWidth) const { return QRect(0, yMin, outerWidth, yMax - yMin + _st.height); } -void MultiSelect::Inner::Item::prepareCache() { +void MultiSelect::Item::prepareCache() { if (!_cache.isNull()) return; t_assert(!_visibility.animating()); @@ -309,7 +227,7 @@ void MultiSelect::Inner::Item::prepareCache() { _cache = App::pixmapFromImageInPlace(std::move(data)); } -void MultiSelect::Inner::Item::setVisibleAnimated(bool visible) { +void MultiSelect::Item::setVisibleAnimated(bool visible) { _hiding = !visible; prepareCache(); auto from = visible ? 0. : 1.; @@ -318,7 +236,7 @@ void MultiSelect::Inner::Item::setVisibleAnimated(bool visible) { _visibility.start(_updateCallback, from, to, _st.duration, transition); } -void MultiSelect::Inner::Item::setOver(bool over) { +void MultiSelect::Item::setOver(bool over) { if (over != _over) { _over = over; _overOpacity.start(_updateCallback, _over ? 0. : 1., _over ? 1. : 0., _st.duration); @@ -407,7 +325,7 @@ void MultiSelect::addItem(uint64 itemId, const QString &text, style::color color } void MultiSelect::addItemInBunch(uint64 itemId, const QString &text, style::color color, PaintRoundImage paintRoundImage) { - _inner->addItemInBunch(std::make_unique(_st.item, itemId, text, color, std::move(paintRoundImage))); + _inner->addItemInBunch(std::make_unique(_st.item, itemId, text, color, std::move(paintRoundImage))); } void MultiSelect::finishItemsBunch() { diff --git a/Telegram/SourceFiles/ui/widgets/multi_select.h b/Telegram/SourceFiles/ui/widgets/multi_select.h index 7724362e1..8f1c208f6 100644 --- a/Telegram/SourceFiles/ui/widgets/multi_select.h +++ b/Telegram/SourceFiles/ui/widgets/multi_select.h @@ -57,6 +57,8 @@ public: QVector getItems() const; bool hasItem(uint64 itemId) const; + class Item; + protected: int resizeGetHeight(int newWidth) override; bool eventFilter(QObject *o, QEvent *e) override; @@ -91,7 +93,6 @@ public: void setQueryChangedCallback(base::lambda callback); void setSubmittedCallback(base::lambda callback); - class Item; void addItemInBunch(std::unique_ptr item); void finishItemsBunch(AddItemWay way); void setItemText(uint64 itemId, const QString &text); @@ -176,4 +177,87 @@ private: }; + +class MultiSelect::Item { +public: + Item(const style::MultiSelectItem &st, uint64 id, const QString &text, style::color color, PaintRoundImage &&paintRoundImage); + + uint64 id() const { + return _id; + } + int getWidth() const { + return _width; + } + QRect rect() const { + return QRect(_x, _y, _width, _st.height); + } + bool isOverDelete() const { + return _overDelete; + } + void setActive(bool active) { + _active = active; + } + void setPosition(int x, int y, int outerWidth, int maxVisiblePadding); + QRect paintArea(int outerWidth) const; + + void setUpdateCallback(base::lambda updateCallback) { + _updateCallback = updateCallback; + } + void setText(const QString &text); + void paint(Painter &p, int outerWidth, TimeMs ms); + + void mouseMoveEvent(QPoint point); + void leaveEvent(); + + void showAnimated() { + setVisibleAnimated(true); + } + void hideAnimated() { + setVisibleAnimated(false); + } + bool hideFinished() const { + return (_hiding && !_visibility.animating()); + } + + +private: + void setOver(bool over); + void paintOnce(Painter &p, int x, int y, int outerWidth, TimeMs ms); + void paintDeleteButton(Painter &p, int x, int y, int outerWidth, float64 overOpacity); + bool paintCached(Painter &p, int x, int y, int outerWidth); + void prepareCache(); + void setVisibleAnimated(bool visible); + + const style::MultiSelectItem &_st; + + uint64 _id; + struct SlideAnimation { + SlideAnimation(base::lambda updateCallback, int fromX, int toX, int y, float64 duration) + : fromX(fromX) + , toX(toX) + , y(y) { + x.start(updateCallback, fromX, toX, duration); + } + Animation x; + int fromX, toX; + int y; + }; + std::vector _copies; + int _x = -1; + int _y = -1; + int _width = 0; + Text _text; + style::color _color; + bool _over = false; + QPixmap _cache; + Animation _visibility; + Animation _overOpacity; + bool _overDelete = false; + bool _active = false; + PaintRoundImage _paintRoundImage; + base::lambda _updateCallback; + bool _hiding = false; + +}; + } // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/widgets.style b/Telegram/SourceFiles/ui/widgets/widgets.style index 18ed3a9ba..aa7ba7cf9 100644 --- a/Telegram/SourceFiles/ui/widgets/widgets.style +++ b/Telegram/SourceFiles/ui/widgets/widgets.style @@ -741,6 +741,26 @@ defaultIconButton: IconButton { iconPosition: point(-1px, -1px); } +defaultMultiSelectItem: MultiSelectItem { + padding: margins(6px, 7px, 12px, 0px); + maxWidth: 128px; + height: 32px; + style: defaultTextStyle; + textBg: contactsBgOver; + textFg: windowFg; + textActiveBg: activeButtonBg; + textActiveFg: activeButtonFg; + deleteFg: activeButtonFg; + deleteCross: CrossAnimation { + size: 32px; + skip: 10px; + stroke: 2px; + minScale: 0.3; + } + duration: 150; + minScale: 0.3; +} + widgetSlideDuration: 200; widgetFadeDuration: 200; diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index 4a1b5ad94..405567936 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -144,6 +144,8 @@ <(src_loc)/dialogs/dialogs_list.h <(src_loc)/dialogs/dialogs_row.cpp <(src_loc)/dialogs/dialogs_row.h +<(src_loc)/dialogs/dialogs_search_from_controllers.cpp +<(src_loc)/dialogs/dialogs_search_from_controllers.h <(src_loc)/dialogs/dialogs_widget.cpp <(src_loc)/dialogs/dialogs_widget.h <(src_loc)/history/history.cpp