diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 679d0c1f5b..dde60abd40 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -333,6 +333,29 @@ contactsMarginBottom: 4px; membersMarginTop: 10px; membersMarginBottom: 10px; +peerListBox: PeerList(defaultPeerList) { + padding: margins( + 0px, + membersMarginTop, + 0px, + membersMarginBottom); + item: PeerListItem(defaultPeerListItem) { + height: 56px; + photoSize: contactsPhotoSize; + photoPosition: point(16px, 7px); + namePosition: point(74px, 9px); + statusPosition: point(74px, 30px); + button: OutlineButton(defaultPeerListButton) { + textBg: contactsBg; + textBgOver: contactsBgOver; + ripple: contactsRipple; + } + statusFg: contactsStatusFg; + statusFgOver: contactsStatusFgOver; + statusFgActive: contactsStatusFgOnline; + } +} + localStorageBoxSkip: 10px; shareRowsTop: 12px; diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index e018bd1a10..da03a385ea 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -22,10 +22,12 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "styles/style_boxes.h" #include "styles/style_dialogs.h" +#include "styles/style_widgets.h" #include "auth_session.h" #include "mainwidget.h" #include "ui/widgets/multi_select.h" #include "ui/widgets/labels.h" +#include "ui/widgets/scroll_area.h" #include "ui/effects/round_checkbox.h" #include "ui/effects/ripple_animation.h" #include "ui/wrap/slide_wrap.h" @@ -34,7 +36,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "storage/file_download.h" #include "window/themes/window_theme.h" -PeerListBox::PeerListBox(QWidget*, std::unique_ptr controller, base::lambda)> init) +PeerListBox::PeerListBox( + QWidget*, + std::unique_ptr controller, + base::lambda)> init) : _controller(std::move(controller)) , _init(std::move(init)) { Expects(_controller != nullptr); @@ -49,12 +54,12 @@ void PeerListBox::createMultiSelect() { | rpl::start( [this](int) { updateScrollSkips(); }, lifetime()); - _select->entity()->setSubmittedCallback([this](bool chtrlShiftEnter) { _inner->submitted(); }); + _select->entity()->setSubmittedCallback([this](bool chtrlShiftEnter) { content()->submitted(); }); _select->entity()->setQueryChangedCallback([this](const QString &query) { searchQueryChanged(query); }); _select->entity()->setItemRemovedCallback([this](uint64 itemId) { if (auto peer = App::peerLoaded(itemId)) { if (auto row = peerListFindRow(peer->id)) { - _inner->changeCheckState(row, false, PeerListRow::SetStyle::Animated); + content()->changeCheckState(row, false, PeerListRow::SetStyle::Animated); update(); } _controller->itemDeselectedHook(peer); @@ -82,7 +87,13 @@ void PeerListBox::updateScrollSkips() { } void PeerListBox::prepare() { - _inner = setInnerWidget(object_ptr(this, _controller.get()), st::boxLayerScroll); + setContent(setInnerWidget( + object_ptr( + this, + _controller.get(), + st::peerListBox), + st::boxLayerScroll)); + content()->resizeToWidth(st::boxWideWidth); _controller->setDelegate(this); @@ -93,7 +104,10 @@ void PeerListBox::prepare() { onScrollToY(0); } - connect(_inner, SIGNAL(mustScrollTo(int, int)), this, SLOT(onScrollToY(int, int))); + content()->scrollToRequests() + | rpl::start([this](Ui::ScrollToRequest request) { + onScrollToY(request.ymin, request.ymax); + }, lifetime()); if (_init) { _init(this); @@ -102,13 +116,13 @@ void PeerListBox::prepare() { void PeerListBox::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Down) { - _inner->selectSkip(1); + content()->selectSkip(1); } else if (e->key() == Qt::Key_Up) { - _inner->selectSkip(-1); + content()->selectSkip(-1); } else if (e->key() == Qt::Key_PageDown) { - _inner->selectSkipPage(height(), 1); + content()->selectSkipPage(height(), 1); } else if (e->key() == Qt::Key_PageUp) { - _inner->selectSkipPage(height(), -1); + content()->selectSkipPage(height(), -1); } else if (e->key() == Qt::Key_Escape && _select && !_select->entity()->getQuery().isEmpty()) { _select->entity()->clearQuery(); } else { @@ -118,7 +132,7 @@ void PeerListBox::keyPressEvent(QKeyEvent *e) { void PeerListBox::searchQueryChanged(const QString &query) { onScrollToY(0); - _inner->searchQueryChanged(query); + content()->searchQueryChanged(query); } void PeerListBox::resizeEvent(QResizeEvent *e) { @@ -131,7 +145,7 @@ void PeerListBox::resizeEvent(QResizeEvent *e) { updateScrollSkips(); } - _inner->resizeToWidth(width()); + content()->resizeToWidth(width()); } void PeerListBox::paintEvent(QPaintEvent *e) { @@ -143,53 +157,19 @@ void PeerListBox::paintEvent(QPaintEvent *e) { void PeerListBox::setInnerFocus() { if (!_select || _select->isHiddenOrHiding()) { - _inner->setFocus(); + content()->setFocus(); } else { _select->entity()->setInnerFocus(); } } -void PeerListBox::peerListAppendRow(std::unique_ptr row) { - _inner->appendRow(std::move(row)); -} - -void PeerListBox::peerListAppendSearchRow(std::unique_ptr row) { - _inner->appendSearchRow(std::move(row)); -} - -void PeerListBox::peerListAppendFoundRow(not_null row) { - _inner->appendFoundRow(row); -} - -void PeerListBox::peerListPrependRow(std::unique_ptr row) { - _inner->prependRow(std::move(row)); -} - -void PeerListBox::peerListPrependRowFromSearchResult(not_null row) { - _inner->prependRowFromSearchResult(row); -} - -PeerListRow *PeerListBox::peerListFindRow(PeerListRowId id) { - return _inner->findRow(id); -} - -void PeerListBox::peerListUpdateRow(not_null row) { - _inner->updateRow(row); -} - -void PeerListBox::peerListRemoveRow(not_null row) { - _inner->removeRow(row); -} - -void PeerListBox::peerListConvertRowToSearchResult(not_null row) { - _inner->convertRowToSearchResult(row); -} - -void PeerListBox::peerListSetRowChecked(not_null row, bool checked) { +void PeerListBox::peerListSetRowChecked( + not_null row, + bool checked) { auto peer = row->peer(); if (checked) { addSelectItem(peer, PeerListRow::SetStyle::Animated); - _inner->changeCheckState(row, checked, PeerListRow::SetStyle::Animated); + PeerListContentDelegate::peerListSetRowChecked(row, checked); peerListUpdateRow(row); // This call deletes row from _searchRows. @@ -201,40 +181,13 @@ void PeerListBox::peerListSetRowChecked(not_null row, bool checked } } -int PeerListBox::peerListFullRowsCount() { - return _inner->fullRowsCount(); -} - -not_null PeerListBox::peerListRowAt(int index) { - return _inner->rowAt(index); -} - -void PeerListBox::peerListRefreshRows() { - _inner->refreshRows(); -} - void PeerListBox::peerListScrollToTop() { onScrollToY(0); } -void PeerListBox::peerListSetDescription(object_ptr description) { - _inner->setDescription(std::move(description)); -} - -void PeerListBox::peerListSetSearchLoading(object_ptr loading) { - _inner->setSearchLoading(std::move(loading)); -} - -void PeerListBox::peerListSetSearchNoResults(object_ptr noResults) { - _inner->setSearchNoResults(std::move(noResults)); -} - -void PeerListBox::peerListSetAboveWidget(object_ptr aboveWidget) { - _inner->setAboveWidget(std::move(aboveWidget)); -} - void PeerListBox::peerListSetSearchMode(PeerListSearchMode mode) { - _inner->setSearchMode(mode); + PeerListContentDelegate::peerListSetSearchMode(mode); + auto selectVisible = (mode != PeerListSearchMode::Disabled); if (selectVisible && !_select) { createMultiSelect(); @@ -247,22 +200,6 @@ void PeerListBox::peerListSetSearchMode(PeerListSearchMode mode) { } } -void PeerListBox::peerListSortRows(base::lambda compare) { - _inner->reorderRows([compare = std::move(compare)](auto &&begin, auto &&end) { - std::sort(begin, end, [&compare](auto &&a, auto &&b) { - return compare(*a, *b); - }); - }); -} - -void PeerListBox::peerListPartitionRows(base::lambda border) { - _inner->reorderRows([border = std::move(border)](auto &&begin, auto &&end) { - std::stable_partition(begin, end, [&border](auto &¤t) { - return border(*current); - }); - }); -} - PeerListController::PeerListController(std::unique_ptr searchController) : _searchController(std::move(searchController)) { if (_searchController) { _searchController->setDelegate(this); @@ -418,18 +355,25 @@ void PeerListRow::invalidatePixmapsCache() { } } -void PeerListRow::paintStatusText(Painter &p, int x, int y, int availableWidth, int outerWidth, bool selected) { +void PeerListRow::paintStatusText( + Painter &p, + const style::PeerListItem &st, + int x, + int y, + int availableWidth, + int outerWidth, + bool selected) { auto statusHasOnlineColor = (_statusType == PeerListRow::StatusType::Online); p.setFont(st::contactsStatusFont); - p.setPen(statusHasOnlineColor ? st::contactsStatusFgOnline : (selected ? st::contactsStatusFgOver : st::contactsStatusFg)); + p.setPen(statusHasOnlineColor ? st.statusFgActive : (selected ? st.statusFgOver : st.statusFg)); _status.drawLeftElided(p, x, y, availableWidth, outerWidth); } template -void PeerListRow::addRipple(QSize size, QPoint point, UpdateCallback updateCallback) { +void PeerListRow::addRipple(const style::PeerListItem &st, QSize size, QPoint point, UpdateCallback updateCallback) { if (!_ripple) { auto mask = Ui::RippleAnimation::rectMask(size); - _ripple = std::make_unique(st::contactsRipple, std::move(mask), std::move(updateCallback)); + _ripple = std::make_unique(st.button.ripple, std::move(mask), std::move(updateCallback)); } _ripple->add(point); } @@ -449,18 +393,29 @@ void PeerListRow::paintRipple(Painter &p, TimeMs ms, int x, int y, int outerWidt } } -void PeerListRow::paintUserpic(Painter &p, TimeMs ms, int x, int y, int outerWidth) { +void PeerListRow::paintUserpic( + Painter &p, + const style::PeerListItem &st, + TimeMs ms, + int x, + int y, + int outerWidth) { if (_disabledState == State::DisabledChecked) { - paintDisabledCheckUserpic(p, x, y, outerWidth); + paintDisabledCheckUserpic(p, st, x, y, outerWidth); } else if (_checkbox) { _checkbox->paint(p, ms, x, y, outerWidth); } else { - peer()->paintUserpicLeft(p, x, y, outerWidth, st::contactsPhotoSize); + peer()->paintUserpicLeft(p, x, y, outerWidth, st.photoSize); } } // Emulates Ui::RoundImageCheckbox::paint() in a checked state. -void PeerListRow::paintDisabledCheckUserpic(Painter &p, int x, int y, int outerWidth) const { +void PeerListRow::paintDisabledCheckUserpic( + Painter &p, + const style::PeerListItem &st, + int x, + int y, + int outerWidth) const { auto userpicRadius = st::contactsPhotoCheckbox.imageSmallRadius; auto userpicShift = st::contactsPhotoCheckbox.imageRadius - userpicRadius; auto userpicDiameter = st::contactsPhotoCheckbox.imageRadius * 2; @@ -512,7 +467,10 @@ void PeerListRow::lazyInitialize() { } void PeerListRow::createCheckbox(base::lambda updateCallback) { - _checkbox = std::make_unique(st::contactsPhotoCheckbox, std::move(updateCallback), PaintUserpicCallback(_peer)); + _checkbox = std::make_unique( + st::contactsPhotoCheckbox, + std::move(updateCallback), + PaintUserpicCallback(_peer)); } void PeerListRow::setCheckedInternal(bool checked, SetStyle style) { @@ -522,9 +480,14 @@ void PeerListRow::setCheckedInternal(bool checked, SetStyle style) { _checkbox->setChecked(checked, speed); } -PeerListBox::Inner::Inner(QWidget *parent, not_null controller) : TWidget(parent) +PeerListContent::PeerListContent( + QWidget *parent, + not_null controller, + const style::PeerList &st) +: RpWidget(parent) +, _st(st) , _controller(controller) -, _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) { +, _rowHeight(_st.item.height) { subscribe(Auth().downloaderTaskFinished(), [this] { update(); }); using UpdateFlag = Notify::PeerUpdate::Flag; @@ -543,7 +506,7 @@ PeerListBox::Inner::Inner(QWidget *parent, not_null control }); } -void PeerListBox::Inner::appendRow(std::unique_ptr row) { +void PeerListContent::appendRow(std::unique_ptr row) { Expects(row != nullptr); if (_rowsById.find(row->id()) == _rowsById.cend()) { row->setAbsoluteIndex(_rows.size()); @@ -552,7 +515,7 @@ void PeerListBox::Inner::appendRow(std::unique_ptr row) { } } -void PeerListBox::Inner::appendSearchRow(std::unique_ptr row) { +void PeerListContent::appendSearchRow(std::unique_ptr row) { Expects(row != nullptr); Expects(showingSearch()); if (_rowsById.find(row->id()) == _rowsById.cend()) { @@ -564,7 +527,7 @@ void PeerListBox::Inner::appendSearchRow(std::unique_ptr row) { } } -void PeerListBox::Inner::appendFoundRow(not_null row) { +void PeerListContent::appendFoundRow(not_null row) { Expects(showingSearch()); auto index = findRowIndex(row); if (index.value < 0) { @@ -572,13 +535,13 @@ void PeerListBox::Inner::appendFoundRow(not_null row) { } } -void PeerListBox::Inner::changeCheckState(not_null row, bool checked, PeerListRow::SetStyle style) { +void PeerListContent::changeCheckState(not_null row, bool checked, PeerListRow::SetStyle style) { row->setChecked(checked, style, [this, row] { updateRow(row); }); } -void PeerListBox::Inner::addRowEntry(not_null row) { +void PeerListContent::addRowEntry(not_null row) { _rowsById.emplace(row->id(), row); _rowsByPeer[row->peer()].push_back(row); if (addingToSearchIndex()) { @@ -590,18 +553,18 @@ void PeerListBox::Inner::addRowEntry(not_null row) { } } -void PeerListBox::Inner::invalidatePixmapsCache() { +void PeerListContent::invalidatePixmapsCache() { auto invalidate = [](auto &&row) { row->invalidatePixmapsCache(); }; base::for_each(_rows, invalidate); base::for_each(_searchRows, invalidate); } -bool PeerListBox::Inner::addingToSearchIndex() const { +bool PeerListContent::addingToSearchIndex() const { // If we started indexing already, we continue. return (_searchMode != PeerListSearchMode::Disabled) || !_searchIndex.empty(); } -void PeerListBox::Inner::addToSearchIndex(not_null row) { +void PeerListContent::addToSearchIndex(not_null row) { if (row->isSearchResult()) { return; } @@ -613,7 +576,7 @@ void PeerListBox::Inner::addToSearchIndex(not_null row) { } } -void PeerListBox::Inner::removeFromSearchIndex(not_null row) { +void PeerListContent::removeFromSearchIndex(not_null row) { auto &nameFirstChars = row->nameFirstChars(); if (!nameFirstChars.empty()) { for_const (auto ch, row->nameFirstChars()) { @@ -630,7 +593,7 @@ void PeerListBox::Inner::removeFromSearchIndex(not_null row) { } } -void PeerListBox::Inner::prependRow(std::unique_ptr row) { +void PeerListContent::prependRow(std::unique_ptr row) { Expects(row != nullptr); if (_rowsById.find(row->id()) == _rowsById.cend()) { addRowEntry(row.get()); @@ -639,7 +602,7 @@ void PeerListBox::Inner::prependRow(std::unique_ptr row) { } } -void PeerListBox::Inner::prependRowFromSearchResult(not_null row) { +void PeerListContent::prependRowFromSearchResult(not_null row) { if (!row->isSearchResult()) { return; } @@ -658,26 +621,26 @@ void PeerListBox::Inner::prependRowFromSearchResult(not_null row) } } -void PeerListBox::Inner::refreshIndices() { +void PeerListContent::refreshIndices() { auto index = 0; for (auto &row : _rows) { row->setAbsoluteIndex(index++); } } -void PeerListBox::Inner::removeRowAtIndex(std::vector> &from, int index) { +void PeerListContent::removeRowAtIndex(std::vector> &from, int index) { from.erase(from.begin() + index); for (auto i = index, count = int(from.size()); i != count; ++i) { from[i]->setAbsoluteIndex(i); } } -PeerListRow *PeerListBox::Inner::findRow(PeerListRowId id) { +PeerListRow *PeerListContent::findRow(PeerListRowId id) { auto it = _rowsById.find(id); return (it == _rowsById.cend()) ? nullptr : it->second.get(); } -void PeerListBox::Inner::removeRow(not_null row) { +void PeerListContent::removeRow(not_null row) { auto index = row->absoluteIndex(); auto isSearchResult = row->isSearchResult(); auto &eraseFrom = isSearchResult ? _searchRows : _rows; @@ -698,7 +661,7 @@ void PeerListBox::Inner::removeRow(not_null row) { restoreSelection(); } -void PeerListBox::Inner::convertRowToSearchResult(not_null row) { +void PeerListContent::convertRowToSearchResult(not_null row) { if (row->isSearchResult()) { return; } else if (!showingSearch() || !_controller->hasComplexSearch()) { @@ -715,44 +678,44 @@ void PeerListBox::Inner::convertRowToSearchResult(not_null row) { removeRowAtIndex(_rows, index); } -int PeerListBox::Inner::fullRowsCount() const { +int PeerListContent::fullRowsCount() const { return _rows.size(); } -not_null PeerListBox::Inner::rowAt(int index) const { +not_null PeerListContent::rowAt(int index) const { Expects(index >= 0 && index < _rows.size()); return _rows[index].get(); } -void PeerListBox::Inner::setDescription(object_ptr description) { +void PeerListContent::setDescription(object_ptr description) { _description = std::move(description); if (_description) { _description->setParent(this); } } -void PeerListBox::Inner::setSearchLoading(object_ptr loading) { +void PeerListContent::setSearchLoading(object_ptr loading) { _searchLoading = std::move(loading); if (_searchLoading) { _searchLoading->setParent(this); } } -void PeerListBox::Inner::setSearchNoResults(object_ptr noResults) { +void PeerListContent::setSearchNoResults(object_ptr noResults) { _searchNoResults = std::move(noResults); if (_searchNoResults) { _searchNoResults->setParent(this); } } -void PeerListBox::Inner::setAboveWidget(object_ptr aboveWidget) { +void PeerListContent::setAboveWidget(object_ptr aboveWidget) { _aboveWidget = std::move(aboveWidget); if (_aboveWidget) { _aboveWidget->setParent(this); } } -int PeerListBox::Inner::labelHeight() const { +int PeerListContent::labelHeight() const { auto computeLabelHeight = [](auto &label) { if (!label) { return 0; @@ -771,8 +734,8 @@ int PeerListBox::Inner::labelHeight() const { return computeLabelHeight(_description); } -void PeerListBox::Inner::refreshRows() { - resizeToWidth(st::boxWideWidth); +void PeerListContent::refreshRows() { + resizeToWidth(width()); if (_visibleBottom > 0) { checkScrollForPreload(); } @@ -780,7 +743,7 @@ void PeerListBox::Inner::refreshRows() { update(); } -void PeerListBox::Inner::setSearchMode(PeerListSearchMode mode) { +void PeerListContent::setSearchMode(PeerListSearchMode mode) { if (_searchMode != mode) { if (!addingToSearchIndex()) { for_const (auto &row, _rows) { @@ -790,7 +753,11 @@ void PeerListBox::Inner::setSearchMode(PeerListSearchMode mode) { _searchMode = mode; if (_controller->hasComplexSearch()) { if (!_searchLoading) { - setSearchLoading(object_ptr(this, lang(lng_contacts_loading), Ui::FlatLabel::InitType::Simple, st::membersAbout)); + setSearchLoading(object_ptr( + this, + lang(lng_contacts_loading), + Ui::FlatLabel::InitType::Simple, + st::membersAbout)); } } else { clearSearchRows(); @@ -798,17 +765,17 @@ void PeerListBox::Inner::setSearchMode(PeerListSearchMode mode) { } } -void PeerListBox::Inner::clearSearchRows() { +void PeerListContent::clearSearchRows() { while (!_searchRows.empty()) { removeRow(_searchRows.back().get()); } } -void PeerListBox::Inner::paintEvent(QPaintEvent *e) { +void PeerListContent::paintEvent(QPaintEvent *e) { QRect r(e->rect()); Painter p(this); - p.fillRect(r, st::contactsBg); + p.fillRect(r, _st.item.button.textBg); auto rowsTopCached = rowsTop(); auto ms = getms(); @@ -827,7 +794,7 @@ void PeerListBox::Inner::paintEvent(QPaintEvent *e) { } } -int PeerListBox::Inner::resizeGetHeight(int newWidth) { +int PeerListContent::resizeGetHeight(int newWidth) { _aboveHeight = 0; if (_aboveWidget) { _aboveWidget->resizeToWidth(newWidth); @@ -839,6 +806,7 @@ int PeerListBox::Inner::resizeGetHeight(int newWidth) { _aboveHeight = _aboveWidget->height(); } } + auto rowsCount = shownRowsCount(); auto labelTop = rowsTop() + qMax(1, shownRowsCount()) * _rowHeight; if (_description) { _description->moveToLeft(st::contactsPadding.left(), labelTop + st::membersAboutLimitPadding.top(), newWidth); @@ -852,20 +820,22 @@ int PeerListBox::Inner::resizeGetHeight(int newWidth) { _searchLoading->moveToLeft(st::contactsPadding.left(), labelTop + st::membersAboutLimitPadding.top(), newWidth); _searchLoading->setVisible(showingSearch() && _filterResults.empty() && _controller->isSearchLoading()); } - return labelTop + labelHeight() + st::membersMarginBottom; + auto label = labelHeight(); + return ((label > 0 || rowsCount > 0) ? (labelTop + label) : 0) + + _st.padding.bottom(); } -void PeerListBox::Inner::enterEventHook(QEvent *e) { +void PeerListContent::enterEventHook(QEvent *e) { setMouseTracking(true); } -void PeerListBox::Inner::leaveEventHook(QEvent *e) { +void PeerListContent::leaveEventHook(QEvent *e) { _mouseSelection = false; setMouseTracking(false); setSelected(Selected()); } -void PeerListBox::Inner::mouseMoveEvent(QMouseEvent *e) { +void PeerListContent::mouseMoveEvent(QMouseEvent *e) { auto position = e->globalPos(); if (_mouseSelection || _lastMousePosition != position) { _lastMousePosition = position; @@ -874,7 +844,7 @@ void PeerListBox::Inner::mouseMoveEvent(QMouseEvent *e) { } } -void PeerListBox::Inner::mousePressEvent(QMouseEvent *e) { +void PeerListContent::mousePressEvent(QMouseEvent *e) { _mouseSelection = true; _lastMousePosition = e->globalPos(); updateSelection(); @@ -893,12 +863,12 @@ void PeerListBox::Inner::mousePressEvent(QMouseEvent *e) { } else { auto size = QSize(width(), _rowHeight); auto point = mapFromGlobal(QCursor::pos()) - QPoint(0, getRowTop(_selected.index)); - row->addRipple(size, point, std::move(updateCallback)); + row->addRipple(_st.item, size, point, std::move(updateCallback)); } } } -void PeerListBox::Inner::mouseReleaseEvent(QMouseEvent *e) { +void PeerListContent::mouseReleaseEvent(QMouseEvent *e) { updateRow(_pressed.index); updateRow(_selected.index); @@ -915,7 +885,7 @@ void PeerListBox::Inner::mouseReleaseEvent(QMouseEvent *e) { } } -void PeerListBox::Inner::setPressed(Selected pressed) { +void PeerListContent::setPressed(Selected pressed) { if (auto row = getRow(_pressed.index)) { row->stopLastRipple(); row->stopLastActionRipple(); @@ -923,7 +893,7 @@ void PeerListBox::Inner::setPressed(Selected pressed) { _pressed = pressed; } -void PeerListBox::Inner::paintRow(Painter &p, TimeMs ms, RowIndex index) { +void PeerListContent::paintRow(Painter &p, TimeMs ms, RowIndex index) { auto row = getRow(index); Assert(row != nullptr); row->lazyInitialize(); @@ -934,17 +904,26 @@ void PeerListBox::Inner::paintRow(Painter &p, TimeMs ms, RowIndex index) { auto selected = (active.index == index); auto actionSelected = (selected && active.action); - p.fillRect(0, 0, width(), _rowHeight, selected ? st::contactsBgOver : st::contactsBg); + auto &bg = selected + ? _st.item.button.textBgOver + : _st.item.button.textBg; + p.fillRect(0, 0, width(), _rowHeight, bg); row->paintRipple(p, ms, 0, 0, width()); - row->paintUserpic(p, ms, st::contactsPadding.left(), st::contactsPadding.top(), width()); + row->paintUserpic( + p, + _st.item, + ms, + _st.item.photoPosition.x(), + _st.item.photoPosition.y(), + width()); p.setPen(st::contactsNameFg); auto actionSize = row->actionSize(); auto actionMargins = actionSize.isEmpty() ? QMargins() : row->actionMargins(); auto &name = row->name(); - auto namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(); - auto namew = width() - namex - st::contactsPadding.right(); + auto namex = _st.item.namePosition.x(); + auto namew = width() - namex - _st.item.photoPosition.x(); if (!actionSize.isEmpty()) { namew -= actionMargins.left() + actionSize.width() + actionMargins.right(); } @@ -952,14 +931,14 @@ void PeerListBox::Inner::paintRow(Painter &p, TimeMs ms, RowIndex index) { if (row->needsVerifiedIcon()) { auto icon = &st::dialogsVerifiedIcon; namew -= icon->width(); - icon->paint(p, namex + qMin(name.maxWidth(), namew), st::contactsPadding.top() + st::contactsNameTop, width()); + icon->paint(p, namex + qMin(name.maxWidth(), namew), _st.item.namePosition.y(), width()); } auto nameCheckedRatio = row->disabled() ? 0. : row->checkedRatio(); p.setPen(anim::pen(st::contactsNameFg, st::contactsNameCheckedFg, nameCheckedRatio)); - name.drawLeftElided(p, namex, st::contactsPadding.top() + st::contactsNameTop, namew, width()); + name.drawLeftElided(p, namex, _st.item.namePosition.y(), namew, width()); if (!actionSize.isEmpty()) { - auto actionLeft = width() - st::contactsPadding.right() - actionMargins.right() - actionSize.width(); + auto actionLeft = width() - _st.item.photoPosition.x() - actionMargins.right() - actionSize.width(); auto actionTop = actionMargins.top(); row->paintAction(p, ms, actionLeft, actionTop, width(), actionSelected); } @@ -975,22 +954,22 @@ void PeerListBox::Inner::paintRow(Painter &p, TimeMs ms, RowIndex index) { if (highlightedWidth > availableWidth) { highlightedPart = st::contactsStatusFont->elided(highlightedPart, availableWidth); } - p.setPen(st::contactsStatusFgOnline); - p.drawTextLeft(namex, st::contactsPadding.top() + st::contactsStatusTop, width(), highlightedPart); + p.setPen(_st.item.statusFgActive); + p.drawTextLeft(_st.item.statusPosition.x(), _st.item.statusPosition.y(), width(), highlightedPart); } else { grayedPart = st::contactsStatusFont->elided(grayedPart, availableWidth - highlightedWidth); auto grayedWidth = st::contactsStatusFont->width(grayedPart); - p.setPen(st::contactsStatusFgOnline); - p.drawTextLeft(namex, st::contactsPadding.top() + st::contactsStatusTop, width(), highlightedPart); - p.setPen(selected ? st::contactsStatusFgOver : st::contactsStatusFg); - p.drawTextLeft(namex + highlightedWidth, st::contactsPadding.top() + st::contactsStatusTop, width(), grayedPart); + p.setPen(_st.item.statusFgActive); + p.drawTextLeft(_st.item.statusPosition.x(), _st.item.statusPosition.y(), width(), highlightedPart); + p.setPen(selected ? _st.item.statusFgOver : _st.item.statusFg); + p.drawTextLeft(_st.item.statusPosition.x() + highlightedWidth, _st.item.statusPosition.y(), width(), grayedPart); } } else { - row->paintStatusText(p, namex, st::contactsPadding.top() + st::contactsStatusTop, statusw, width(), selected); + row->paintStatusText(p, _st.item, _st.item.statusPosition.x(), _st.item.statusPosition.y(), statusw, width(), selected); } } -void PeerListBox::Inner::selectSkip(int direction) { +void PeerListContent::selectSkip(int direction) { if (_pressed.index.value >= 0) { return; } @@ -1049,19 +1028,19 @@ void PeerListBox::Inner::selectSkip(int direction) { if (newSelectedIndex >= 0) { auto top = (newSelectedIndex > 0) ? getRowTop(RowIndex(newSelectedIndex)) : 0; auto bottom = (newSelectedIndex + 1 < rowsCount) ? getRowTop(RowIndex(newSelectedIndex + 1)) : height(); - emit mustScrollTo(top, bottom); + _scrollToRequests.fire({ top, bottom }); } update(); } -void PeerListBox::Inner::selectSkipPage(int height, int direction) { +void PeerListContent::selectSkipPage(int height, int direction) { auto rowsToSkip = height / _rowHeight; if (!rowsToSkip) return; selectSkip(rowsToSkip * direction); } -void PeerListBox::Inner::loadProfilePhotos() { +void PeerListContent::loadProfilePhotos() { if (_visibleTop >= _visibleBottom) return; auto yFrom = _visibleTop; @@ -1086,13 +1065,13 @@ void PeerListBox::Inner::loadProfilePhotos() { } } -void PeerListBox::Inner::checkScrollForPreload() { +void PeerListContent::checkScrollForPreload() { if (_visibleBottom + PreloadHeightsCount * (_visibleBottom - _visibleTop) > height()) { _controller->loadMoreRows(); } } -void PeerListBox::Inner::searchQueryChanged(QString query) { +void PeerListContent::searchQueryChanged(QString query) { auto searchWordsList = TextUtilities::PrepareSearchWords(query); auto normalizedQuery = searchWordsList.isEmpty() ? QString() : searchWordsList.join(' '); if (_normalizedSearchQuery != normalizedQuery) { @@ -1144,7 +1123,7 @@ void PeerListBox::Inner::searchQueryChanged(QString query) { } } -void PeerListBox::Inner::setSearchQuery(const QString &query, const QString &normalizedQuery) { +void PeerListContent::setSearchQuery(const QString &query, const QString &normalizedQuery) { setSelected(Selected()); setPressed(Selected()); _searchQuery = query; @@ -1154,13 +1133,13 @@ void PeerListBox::Inner::setSearchQuery(const QString &query, const QString &nor clearSearchRows(); } -void PeerListBox::Inner::submitted() { +void PeerListContent::submitted() { if (auto row = getRow(_selected.index)) { _controller->rowClicked(row); } } -void PeerListBox::Inner::visibleTopBottomUpdated( +void PeerListContent::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { _visibleTop = visibleTop; @@ -1169,7 +1148,7 @@ void PeerListBox::Inner::visibleTopBottomUpdated( checkScrollForPreload(); } -void PeerListBox::Inner::setSelected(Selected selected) { +void PeerListContent::setSelected(Selected selected) { updateRow(_selected.index); if (_selected != selected) { _selected = selected; @@ -1178,12 +1157,12 @@ void PeerListBox::Inner::setSelected(Selected selected) { } } -void PeerListBox::Inner::restoreSelection() { +void PeerListContent::restoreSelection() { _lastMousePosition = QCursor::pos(); updateSelection(); } -void PeerListBox::Inner::updateSelection() { +void PeerListContent::updateSelection() { if (!_mouseSelection) return; auto point = mapFromGlobal(_lastMousePosition); @@ -1204,35 +1183,35 @@ void PeerListBox::Inner::updateSelection() { setSelected(selected); } -QRect PeerListBox::Inner::getActionRect(not_null row, RowIndex index) const { +QRect PeerListContent::getActionRect(not_null row, RowIndex index) const { auto actionSize = row->actionSize(); if (actionSize.isEmpty()) { return QRect(); } auto actionMargins = row->actionMargins(); - auto actionRight = st::contactsPadding.right() + actionMargins.right(); + auto actionRight = _st.item.photoPosition.x() + actionMargins.right(); auto actionTop = actionMargins.top(); auto actionLeft = width() - actionRight - actionSize.width(); auto rowTop = getRowTop(index); return myrtlrect(actionLeft, rowTop + actionTop, actionSize.width(), actionSize.height()); } -int PeerListBox::Inner::rowsTop() const { - return _aboveHeight + st::membersMarginTop; +int PeerListContent::rowsTop() const { + return _aboveHeight + _st.padding.top(); } -int PeerListBox::Inner::getRowTop(RowIndex index) const { +int PeerListContent::getRowTop(RowIndex index) const { if (index.value >= 0) { return rowsTop() + index.value * _rowHeight; } return -1; } -void PeerListBox::Inner::updateRow(not_null row, RowIndex hint) { +void PeerListContent::updateRow(not_null row, RowIndex hint) { updateRow(findRowIndex(row, hint)); } -void PeerListBox::Inner::updateRow(RowIndex index) { +void PeerListContent::updateRow(RowIndex index) { if (index.value < 0) { return; } @@ -1249,12 +1228,12 @@ void PeerListBox::Inner::updateRow(RowIndex index) { } template -bool PeerListBox::Inner::enumerateShownRows(Callback callback) { +bool PeerListContent::enumerateShownRows(Callback callback) { return enumerateShownRows(0, shownRowsCount(), std::move(callback)); } template -bool PeerListBox::Inner::enumerateShownRows(int from, int to, Callback callback) { +bool PeerListContent::enumerateShownRows(int from, int to, Callback callback) { Assert(0 <= from); Assert(from <= to); if (showingSearch()) { @@ -1275,7 +1254,7 @@ bool PeerListBox::Inner::enumerateShownRows(int from, int to, Callback callback) return true; } -PeerListRow *PeerListBox::Inner::getRow(RowIndex index) { +PeerListRow *PeerListContent::getRow(RowIndex index) { if (index.value >= 0) { if (showingSearch()) { if (index.value < _filterResults.size()) { @@ -1288,7 +1267,7 @@ PeerListRow *PeerListBox::Inner::getRow(RowIndex index) { return nullptr; } -PeerListBox::Inner::RowIndex PeerListBox::Inner::findRowIndex(not_null row, RowIndex hint) { +PeerListContent::RowIndex PeerListContent::findRowIndex(not_null row, RowIndex hint) { if (!showingSearch()) { Assert(!row->isSearchResult()); return RowIndex(row->absoluteIndex()); @@ -1309,7 +1288,7 @@ PeerListBox::Inner::RowIndex PeerListBox::Inner::findRowIndex(not_nullsecond) { diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index 0733163185..81dd5a0763 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -20,10 +20,16 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #pragma once +#include +#include "ui/rp_widget.h" #include "boxes/abstract_box.h" #include "mtproto/sender.h" #include "base/timer.h" +namespace style { +struct PeerList; +} // namespace style + namespace Ui { class RippleAnimation; class RoundImageCheckbox; @@ -31,6 +37,7 @@ class MultiSelect; template class SlideWrap; class FlatLabel; +struct ScrollToRequest; } // namespace Ui namespace Notify { @@ -135,10 +142,20 @@ public: void invalidatePixmapsCache(); template - void addRipple(QSize size, QPoint point, UpdateCallback updateCallback); + void addRipple( + const style::PeerListItem &st, + QSize size, + QPoint point, + UpdateCallback updateCallback); void stopLastRipple(); void paintRipple(Painter &p, TimeMs ms, int x, int y, int outerWidth); - void paintUserpic(Painter &p, TimeMs ms, int x, int y, int outerWidth); + void paintUserpic( + Painter &p, + const style::PeerListItem &st, + TimeMs ms, + int x, + int y, + int outerWidth); float64 checkedRatio(); void setNameFirstChars(const OrderedSet &nameFirstChars) { @@ -149,7 +166,14 @@ public: } virtual void lazyInitialize(); - virtual void paintStatusText(Painter &p, int x, int y, int availableWidth, int outerWidth, bool selected); + virtual void paintStatusText( + Painter &p, + const style::PeerListItem &st, + int x, + int y, + int availableWidth, + int outerWidth, + bool selected); protected: bool isInitialized() const { @@ -159,7 +183,12 @@ protected: private: void createCheckbox(base::lambda updateCallback); void setCheckedInternal(bool checked, SetStyle style); - void paintDisabledCheckUserpic(Painter &p, int x, int y, int outerWidth) const; + void paintDisabledCheckUserpic( + Painter &p, + const style::PeerListItem &st, + int x, + int y, + int outerWidth) const; void setStatusText(const QString &text); PeerListRowId _id = 0; @@ -323,78 +352,14 @@ private: }; -class PeerListBox : public BoxContent, public PeerListDelegate { +class PeerListContent + : public Ui::RpWidget + , private base::Subscriber { public: - PeerListBox(QWidget*, std::unique_ptr controller, base::lambda)> init); - - void peerListSetTitle(base::lambda title) override { - setTitle(std::move(title)); - } - void peerListSetAdditionalTitle(base::lambda title) override { - setAdditionalTitle(std::move(title)); - } - void peerListSetDescription(object_ptr description) override; - void peerListSetSearchLoading(object_ptr loading) override; - void peerListSetSearchNoResults(object_ptr noResults) override; - void peerListSetAboveWidget(object_ptr aboveWidget) override; - void peerListSetSearchMode(PeerListSearchMode mode) override; - void peerListAppendRow(std::unique_ptr row) override; - void peerListAppendSearchRow(std::unique_ptr row) override; - void peerListAppendFoundRow(not_null row) override; - void peerListPrependRow(std::unique_ptr row) override; - void peerListPrependRowFromSearchResult(not_null row) override; - void peerListUpdateRow(not_null row) override; - void peerListRemoveRow(not_null row) override; - void peerListConvertRowToSearchResult(not_null row) override; - void peerListSetRowChecked(not_null row, bool checked) override; - not_null peerListRowAt(int index) override; - bool peerListIsRowSelected(not_null peer) override; - int peerListSelectedRowsCount() override; - std::vector> peerListCollectSelectedRows() override; - void peerListRefreshRows() override; - void peerListScrollToTop() override; - int peerListFullRowsCount() override; - PeerListRow *peerListFindRow(PeerListRowId id) override; - void peerListSortRows(base::lambda compare) override; - void peerListPartitionRows(base::lambda border) override; - -protected: - void prepare() override; - void setInnerFocus() override; - - void keyPressEvent(QKeyEvent *e) override; - void resizeEvent(QResizeEvent *e) override; - void paintEvent(QPaintEvent *e) override; - -private: - void peerListAddSelectedRowInBunch(not_null peer) override { - addSelectItem(peer, PeerListRow::SetStyle::Fast); - } - void peerListFinishSelectedRowsBunch() override; - - void addSelectItem(not_null peer, PeerListRow::SetStyle style); - void createMultiSelect(); - int getTopScrollSkip() const; - void updateScrollSkips(); - void searchQueryChanged(const QString &query); - - object_ptr> _select = { nullptr }; - - class Inner; - QPointer _inner; - - std::unique_ptr _controller; - base::lambda _init; - bool _scrollBottomFixed = true; - -}; - -// This class is hold in header because it requires Qt preprocessing. -class PeerListBox::Inner : public TWidget, private base::Subscriber { - Q_OBJECT - -public: - Inner(QWidget *parent, not_null controller); + PeerListContent( + QWidget *parent, + not_null controller, + const style::PeerList &st); void selectSkip(int direction); void selectSkipPage(int height, int direction); @@ -436,8 +401,9 @@ public: refreshIndices(); } -signals: - void mustScrollTo(int ymin, int ymax); + rpl::producer scrollToRequests() const { + return _scrollToRequests.events(); + } protected: int resizeGetHeight(int newWidth) override; @@ -528,6 +494,7 @@ private: void clearSearchRows(); + const style::PeerList &_st; not_null _controller; PeerListSearchMode _searchMode = PeerListSearchMode::Disabled; @@ -539,6 +506,8 @@ private: Selected _pressed; bool _mouseSelection = false; + rpl::event_stream _scrollToRequests; + std::vector> _rows; std::map> _rowsById; std::map>> _rowsByPeer; @@ -560,3 +529,156 @@ private: std::vector> _searchRows; }; + +class PeerListContentDelegate : public PeerListDelegate { +public: + void setContent(PeerListContent *content) { + _content = content; + } + + void peerListAppendRow( + std::unique_ptr row) override { + _content->appendRow(std::move(row)); + } + void peerListAppendSearchRow( + std::unique_ptr row) override { + _content->appendSearchRow(std::move(row)); + } + void peerListAppendFoundRow( + not_null row) override { + _content->appendFoundRow(row); + } + void peerListPrependRow( + std::unique_ptr row) override { + _content->prependRow(std::move(row)); + } + void peerListPrependRowFromSearchResult( + not_null row) override { + _content->prependRowFromSearchResult(row); + } + PeerListRow *peerListFindRow(PeerListRowId id) override { + return _content->findRow(id); + } + void peerListUpdateRow(not_null row) override { + _content->updateRow(row); + } + void peerListRemoveRow(not_null row) override { + _content->removeRow(row); + } + void peerListConvertRowToSearchResult( + not_null row) override { + _content->convertRowToSearchResult(row); + } + void peerListSetRowChecked( + not_null row, + bool checked) override { + _content->changeCheckState( + row, + checked, + PeerListRow::SetStyle::Animated); + } + int peerListFullRowsCount() override { + return _content->fullRowsCount(); + } + not_null peerListRowAt(int index) override { + return _content->rowAt(index); + } + void peerListRefreshRows() override { + _content->refreshRows(); + } + void peerListSetDescription(object_ptr description) override { + _content->setDescription(std::move(description)); + } + void peerListSetSearchLoading(object_ptr loading) override { + _content->setSearchLoading(std::move(loading)); + } + void peerListSetSearchNoResults(object_ptr noResults) override { + _content->setSearchNoResults(std::move(noResults)); + } + void peerListSetAboveWidget(object_ptr aboveWidget) override { + _content->setAboveWidget(std::move(aboveWidget)); + } + void peerListSetSearchMode(PeerListSearchMode mode) override { + _content->setSearchMode(mode); + } + void peerListSortRows( + base::lambda compare) override { + _content->reorderRows([compare = std::move(compare)]( + auto &&begin, + auto &&end) { + std::sort(begin, end, [&compare](auto &&a, auto &&b) { + return compare(*a, *b); + }); + }); + } + void peerListPartitionRows( + base::lambda border) override { + _content->reorderRows([border = std::move(border)]( + auto &&begin, + auto &&end) { + std::stable_partition(begin, end, [&border]( + auto &¤t) { + return border(*current); + }); + }); + } + +protected: + not_null content() const { + return _content; + } + +private: + PeerListContent *_content = nullptr; + +}; + +class PeerListBox : public BoxContent, public PeerListContentDelegate { +public: + PeerListBox( + QWidget*, + std::unique_ptr controller, + base::lambda)> init); + + void peerListSetTitle(base::lambda title) override { + setTitle(std::move(title)); + } + void peerListSetAdditionalTitle(base::lambda title) override { + setAdditionalTitle(std::move(title)); + } + void peerListSetSearchMode(PeerListSearchMode mode) override; + void peerListSetRowChecked( + not_null row, + bool checked) override; + bool peerListIsRowSelected(not_null peer) override; + int peerListSelectedRowsCount() override; + std::vector> peerListCollectSelectedRows() override; + void peerListScrollToTop() override; + +protected: + void prepare() override; + void setInnerFocus() override; + + void keyPressEvent(QKeyEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + void paintEvent(QPaintEvent *e) override; + +private: + void peerListAddSelectedRowInBunch(not_null peer) override { + addSelectItem(peer, PeerListRow::SetStyle::Fast); + } + void peerListFinishSelectedRowsBunch() override; + + void addSelectItem(not_null peer, PeerListRow::SetStyle style); + void createMultiSelect(); + int getTopScrollSkip() const; + void updateScrollSkips(); + void searchQueryChanged(const QString &query); + + object_ptr> _select = { nullptr }; + + std::unique_ptr _controller; + base::lambda _init; + bool _scrollBottomFixed = true; + +}; diff --git a/Telegram/SourceFiles/calls/calls_box_controller.cpp b/Telegram/SourceFiles/calls/calls_box_controller.cpp index a1fc87aa4c..5872c5d080 100644 --- a/Telegram/SourceFiles/calls/calls_box_controller.cpp +++ b/Telegram/SourceFiles/calls/calls_box_controller.cpp @@ -77,7 +77,14 @@ public: return _items.front()->id; } - void paintStatusText(Painter &p, int x, int y, int availableWidth, int outerWidth, bool selected) override; + void paintStatusText( + Painter &p, + const style::PeerListItem &st, + int x, + int y, + int availableWidth, + int outerWidth, + bool selected) override; void addActionRipple(QPoint point, base::lambda updateCallback) override; void stopLastActionRipple() override; @@ -111,7 +118,7 @@ BoxController::Row::Row(HistoryItem *item) : PeerListRow(item->history()->peer, refreshStatus(); } -void BoxController::Row::paintStatusText(Painter &p, int x, int y, int availableWidth, int outerWidth, bool selected) { +void BoxController::Row::paintStatusText(Painter &p, const style::PeerListItem &st, int x, int y, int availableWidth, int outerWidth, bool selected) { auto icon = ([this] { switch (_type) { case Type::In: return &st::callArrowIn; @@ -125,7 +132,7 @@ void BoxController::Row::paintStatusText(Painter &p, int x, int y, int available x += shift; availableWidth -= shift; - PeerListRow::paintStatusText(p, x, y, availableWidth, outerWidth, selected); + PeerListRow::paintStatusText(p, st, x, y, availableWidth, outerWidth, selected); } void BoxController::Row::paintAction(Painter &p, TimeMs ms, int x, int y, int outerWidth, bool actionSelected) { diff --git a/Telegram/SourceFiles/history/history.style b/Telegram/SourceFiles/history/history.style index 9dc6b09db7..ceb1eb4e03 100644 --- a/Telegram/SourceFiles/history/history.style +++ b/Telegram/SourceFiles/history/history.style @@ -90,7 +90,7 @@ membersInnerDropdown: InnerDropdown(defaultInnerDropdown) { scrollMargin: margins(0px, 5px, 0px, 5px); scrollPadding: margins(0px, 3px, 0px, 3px); } -membersInnerItem: defaultProfileMemberItem; +membersInnerItem: defaultPeerListItem; historyFileOutImage: icon {{ "history_file_image", historyFileOutIconFg }}; historyFileOutImageSelected: icon {{ "history_file_image", historyFileOutIconFgSelected }}; diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index c4ef2e5a5b..e9af4fdad3 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -224,10 +224,12 @@ infoSharedMediaButton: infoProfileButton; infoSharedMediaBottomSkip: 12px; infoMembersHeader: 56px; -infoMembersItem: ProfilePeerListItem(defaultProfileMemberItem) { - photoPosition: point(18px, 6px); - namePosition: point(79px, 11px); - statusPosition: point(79px, 31px); +infoMembersList: PeerList(defaultPeerList) { + item: PeerListItem(defaultPeerListItem) { + photoPosition: point(18px, 6px); + namePosition: point(79px, 11px); + statusPosition: point(79px, 31px); + } } infoMembersButtonPosition: point(12px, 9px); infoMembersButtonIconPosition: point(6px, 6px); diff --git a/Telegram/SourceFiles/info/info_memento.cpp b/Telegram/SourceFiles/info/info_memento.cpp index 7c16a41c65..fc27318c23 100644 --- a/Telegram/SourceFiles/info/info_memento.cpp +++ b/Telegram/SourceFiles/info/info_memento.cpp @@ -139,6 +139,10 @@ void ContentWidget::scrollTopRestore(int scrollTop) { _scroll->scrollToY(scrollTop); } +void ContentWidget::scrollTo(const Ui::ScrollToRequest &request) { + _scroll->scrollTo(request); +} + bool ContentWidget::wheelEventFromFloatPlayer(QEvent *e) { return _scroll->viewportEvent(e); } diff --git a/Telegram/SourceFiles/info/info_memento.h b/Telegram/SourceFiles/info/info_memento.h index 70d692dc0b..688eb62a10 100644 --- a/Telegram/SourceFiles/info/info_memento.h +++ b/Telegram/SourceFiles/info/info_memento.h @@ -29,6 +29,7 @@ enum class SharedMediaType : char; namespace Ui { class ScrollArea; +struct ScrollToRequest; } // namespace Ui namespace Info { @@ -132,6 +133,8 @@ protected: int scrollTopSave() const; void scrollTopRestore(int scrollTop); + void scrollTo(const Ui::ScrollToRequest &request); + private: RpWidget *doSetInnerWidget( object_ptr inner, diff --git a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp index 56ffb84480..c28f3376a2 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp @@ -37,6 +37,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "styles/style_info.h" #include "ui/widgets/buttons.h" #include "ui/widgets/checkbox.h" +#include "ui/widgets/scroll_area.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" @@ -55,6 +56,7 @@ InnerWidget::InnerWidget( _content->heightValue() | rpl::start([this](int height) { TWidget::resizeToWidth(width()); + _desiredHeight.fire(countDesiredHeight()); }, lifetime()); } @@ -70,7 +72,7 @@ rpl::producer InnerWidget::canHideDetails() const { object_ptr InnerWidget::setupContent( RpWidget *parent, - rpl::producer &&wrapValue) const { + rpl::producer &&wrapValue) { auto result = object_ptr(parent); auto cover = result->add(object_ptr( result, @@ -97,10 +99,24 @@ object_ptr InnerWidget::setupContent( // } } if (_peer->isChat() || _peer->isMegagroup()) { - result->add(object_ptr( + _members = result->add(object_ptr( result, + _controller, std::move(wrapValue), - _peer)); + _peer) + ); + _members->scrollToRequests() + | rpl::start([this](Ui::ScrollToRequest request) { + auto min = (request.ymin < 0) + ? request.ymin + : mapFromGlobal(_members->mapToGlobal({ 0, request.ymin })).y(); + auto max = (request.ymin < 0) + ? mapFromGlobal(_members->mapToGlobal({ 0, 0 })).y() + : (request.ymax < 0) + ? request.ymax + : mapFromGlobal(_members->mapToGlobal({ 0, request.ymax })).y(); + _scrollToRequests.fire({ min, max }); + }, _members->lifetime()); } return std::move(result); } @@ -367,6 +383,12 @@ object_ptr> InnerWidget::createSlideSkipWidget( return Ui::CreateSlideSkipWidget(parent, st::infoProfileSkip); } +int InnerWidget::countDesiredHeight() const { + return _content->height() + (_members + ? (_members->desiredHeight() - _members->height()) + : 0); +} + void InnerWidget::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { diff --git a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.h b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.h index 8cd87d1f2d..6d04535687 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.h +++ b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.h @@ -31,6 +31,7 @@ namespace Ui { class VerticalLayout; template class SlideWrap; +struct ScrollToRequest; } // namespace Ui namespace Info { @@ -40,6 +41,7 @@ enum class Wrap; namespace Profile { class Memento; +class Members; class InnerWidget final : public Ui::RpWidget { public: @@ -61,6 +63,14 @@ public: void saveState(not_null memento); void restoreState(not_null memento); + rpl::producer scrollToRequests() const { + return _scrollToRequests.events(); + } + + rpl::producer desiredHeightValue() const override { + return _desiredHeight.events_starting_with(countDesiredHeight()); + } + protected: int resizeGetHeight(int newWidth) override; void visibleTopBottomUpdated( @@ -70,7 +80,7 @@ protected: private: object_ptr setupContent( RpWidget *parent, - rpl::producer &&wrapValue) const; + rpl::producer &&wrapValue); object_ptr setupDetails(RpWidget *parent) const; object_ptr setupSharedMedia(RpWidget *parent) const; object_ptr setupMuteToggle(RpWidget *parent) const; @@ -86,6 +96,8 @@ private: object_ptr> createSlideSkipWidget( RpWidget *parent) const; + int countDesiredHeight() const; + bool canHideDetailsEver() const; rpl::producer canHideDetails() const; @@ -94,8 +106,12 @@ private: int _minHeight = 0; + Members *_members = nullptr; object_ptr _content; + rpl::event_stream _scrollToRequests; + rpl::event_stream _desiredHeight; + }; } // namespace Profile diff --git a/Telegram/SourceFiles/info/profile/info_profile_members.cpp b/Telegram/SourceFiles/info/profile/info_profile_members.cpp index 92d0f719c1..48ce90df8a 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_members.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_members.cpp @@ -24,11 +24,14 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "info/profile/info_profile_values.h" #include "info/profile/info_profile_icon.h" #include "info/profile/info_profile_values.h" +#include "info/profile/info_profile_members_controllers.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 "ui/widgets/scroll_area.h" +#include "styles/style_boxes.h" #include "styles/style_info.h" #include "lang/lang_keys.h" #include "boxes/confirm_box.h" @@ -44,10 +47,12 @@ constexpr auto kEnableSearchMembersAfterCount = 50; Members::Members( QWidget *parent, + not_null controller, rpl::producer &&wrapValue, not_null peer) : RpWidget(parent) , _peer(peer) +, _controller(CreateMembersController(_peer)) , _labelWrap(this) , _label(setupHeader()) , _addMember(this, st::infoMembersAddMember) @@ -57,13 +62,30 @@ Members::Members( langFactory(lng_participant_filter)) , _search(this, st::infoMembersSearch) , _cancelSearch(this, st::infoMembersCancelSearch) -, _list(setupList(this)) { +, _list(setupList(this, _controller.get())) { setupButtons(); std::move(wrapValue) | rpl::start([this](Wrap wrap) { _wrap = wrap; updateSearchOverrides(); }, lifetime()); + setContent(_list.data()); + _controller->setDelegate(static_cast(this)); +} + +int Members::desiredHeight() const { + auto desired = st::infoMembersHeader; + auto count = [this] { + if (auto chat = _peer->asChat()) { + return chat->count; + } else if (auto channel = _peer->asChannel()) { + return channel->membersCount(); + } + return 0; + }(); + desired += qMax(count, _list->fullRowsCount()) + * st::infoMembersList.item.height; + return qMax(height(), desired); } object_ptr Members::setupHeader() { @@ -128,12 +150,24 @@ void Members::setupButtons() { } object_ptr Members::setupList( - RpWidget *parent) const { + RpWidget *parent, + not_null controller) const { auto result = object_ptr( parent, - _peer, - ::Profile::GroupMembersWidget::TitleVisibility::Hidden, - st::infoMembersItem); + controller, + st::infoMembersList); + result->scrollToRequests() + | rpl::start([this](Ui::ScrollToRequest request) { + auto addmin = (request.ymin < 0) + ? 0 + : st::infoMembersHeader; + auto addmax = (request.ymax < 0) + ? 0 + : st::infoMembersHeader; + _scrollToRequests.fire({ + request.ymin + addmin, + request.ymax + addmax }); + }, result->lifetime()); result->moveToLeft(0, st::infoMembersHeader); parent->widthValue() | rpl::start([list = result.data()](int newWidth) { @@ -141,7 +175,7 @@ object_ptr Members::setupList( }, result->lifetime()); result->heightValue() | rpl::start([parent](int listHeight) { - auto newHeight = (listHeight > 0) + auto newHeight = (listHeight > st::membersMarginBottom) ? (st::infoMembersHeader + listHeight) : 0; parent->resize(parent->width(), newHeight); @@ -182,6 +216,15 @@ int Members::resizeGetHeight(int newWidth) { st::infoMembersSearchTop, cancelLeft - fieldLeft, _searchField->height()); + connect(_searchField, &Ui::FlatInput::cancelled, this, [this] { + cancelSearch(); + }); + connect(_searchField, &Ui::FlatInput::changed, this, [this] { + applySearch(); + }); + connect(_searchField, &Ui::FlatInput::submitted, this, [this] { + forceSearchSubmit(); + }); _labelWrap->resize( searchCurrentLeft - st::infoBlockHeaderPosition.x(), @@ -267,7 +310,12 @@ void Members::cancelSearch() { } void Members::applySearch() { + peerListScrollToTop(); + content()->searchQueryChanged(_searchField->getLastText()); +} +void Members::forceSearchSubmit() { + content()->submitted(); } void Members::visibleTopBottomUpdated( @@ -276,6 +324,42 @@ void Members::visibleTopBottomUpdated( setChildVisibleTopBottom(_list, visibleTop, visibleBottom); } +void Members::peerListSetTitle(base::lambda title) { +} + +void Members::peerListSetAdditionalTitle( + base::lambda title) { +} + +bool Members::peerListIsRowSelected(not_null peer) { + return false; +} + +int Members::peerListSelectedRowsCount() { + return 0; +} + +std::vector> Members::peerListCollectSelectedRows() { + return {}; +} + +void Members::peerListScrollToTop() { + _scrollToRequests.fire({ -1, -1 }); +} + +void Members::peerListAddSelectedRowInBunch(not_null peer) { + Unexpected("Item selection in Info::Profile::Members."); +} + +void Members::peerListFinishSelectedRowsBunch() { +} + +void Members::peerListSetDescription( + object_ptr description) { + description.destroy(); +} + + } // namespace Profile } // namespace Info diff --git a/Telegram/SourceFiles/info/profile/info_profile_members.h b/Telegram/SourceFiles/info/profile/info_profile_members.h index cf06515f46..fdacc15d31 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_members.h +++ b/Telegram/SourceFiles/info/profile/info_profile_members.h @@ -21,16 +21,19 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #pragma once #include "ui/rp_widget.h" +#include "boxes/peer_list_box.h" namespace Ui { class FlatInput; class CrossButton; class IconButton; class FlatLabel; +struct ScrollToRequest; } // namespace Ui namespace Profile { class GroupMembersWidget; +class ParticipantsBoxController; } // namespace Profile namespace Info { @@ -39,13 +42,22 @@ enum class Wrap; namespace Profile { -class Members : public Ui::RpWidget { +class Members + : public Ui::RpWidget + , private PeerListContentDelegate { public: Members( QWidget *parent, + not_null controller, rpl::producer &&wrapValue, not_null peer); + rpl::producer scrollToRequests() const { + return _scrollToRequests.events(); + } + + int desiredHeight() const; + protected: void visibleTopBottomUpdated( int visibleTop, @@ -53,11 +65,26 @@ protected: int resizeGetHeight(int newWidth) override; private: - using ListWidget = ::Profile::GroupMembersWidget; + using ListWidget = PeerListContent; + + // PeerListContentDelegate interface. + void peerListSetTitle(base::lambda title) override; + void peerListSetAdditionalTitle( + base::lambda title) override; + bool peerListIsRowSelected(not_null peer) override; + int peerListSelectedRowsCount() override; + std::vector> peerListCollectSelectedRows() override; + void peerListScrollToTop() override; + void peerListAddSelectedRowInBunch( + not_null peer) override; + void peerListFinishSelectedRowsBunch() override; + void peerListSetDescription( + object_ptr description) override; object_ptr setupHeader(); object_ptr setupList( - RpWidget *parent) const; + RpWidget *parent, + not_null controller) const; void setupButtons(); void updateSearchOverrides(); @@ -67,10 +94,12 @@ private: void toggleSearch(); void cancelSearch(); void applySearch(); + void forceSearchSubmit(); void searchAnimationCallback(); Wrap _wrap; not_null _peer; + std::unique_ptr _controller; object_ptr _labelWrap; object_ptr _label; object_ptr _addMember; @@ -83,6 +112,8 @@ private: bool _searchShown = false; base::Timer _searchTimer; + rpl::event_stream _scrollToRequests; + }; } // namespace Profile diff --git a/Telegram/SourceFiles/info/profile/info_profile_members_controllers.cpp b/Telegram/SourceFiles/info/profile/info_profile_members_controllers.cpp new file mode 100644 index 0000000000..3aca0f4fa0 --- /dev/null +++ b/Telegram/SourceFiles/info/profile/info_profile_members_controllers.cpp @@ -0,0 +1,124 @@ +/* +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_controllers.h" + +#include "profile/profile_channel_controllers.h" +#include "lang/lang_keys.h" +#include "apiwrap.h" +#include "auth_session.h" +#include "observer_peer.h" + +namespace Info { +namespace Profile { +namespace { + +class ChatMembersController + : public PeerListController + , private base::Subscriber { +public: + ChatMembersController(not_null chat); + + void prepare() override; + void rowClicked(not_null row) override; + +private: + void rebuildRows(); + std::unique_ptr createRow(not_null user); + + not_null _chat; + +}; + +ChatMembersController::ChatMembersController(not_null chat) +: PeerListController() +, _chat(chat) { +} + +void ChatMembersController::prepare() { + setSearchNoResultsText(lang(lng_blocked_list_not_found)); + delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled); + delegate()->peerListSetTitle(langFactory(lng_channel_admins)); + + rebuildRows(); + if (!delegate()->peerListFullRowsCount()) { + Auth().api().requestFullPeer(_chat); + } + subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler( + Notify::PeerUpdate::Flag::MembersChanged, + [this](const Notify::PeerUpdate &update) { + if (update.peer == _chat) { + rebuildRows(); + } + })); +} + +void ChatMembersController::rebuildRows() { + if (_chat->participants.empty()) { + return; + } + + std::vector> users; + auto &participants = _chat->participants; + for (auto i = participants.cbegin(), e = participants.cend(); + i != e; + ++i) { + users.push_back(i.key()); + } + auto now = unixtime(); + base::sort(users, [now](auto a, auto b) { + return App::onlineForSort(a, now) + > App::onlineForSort(b, now); + }); + base::for_each(users, [this](not_null user) { + if (auto row = createRow(user)) { + delegate()->peerListAppendRow(std::move(row)); + } + }); + + delegate()->peerListRefreshRows(); +} + +std::unique_ptr ChatMembersController::createRow(not_null user) { + return std::make_unique(user); +} + +void ChatMembersController::rowClicked(not_null row) { + Ui::showPeerProfile(row->peer()); +} + +} // namespace + +std::unique_ptr CreateMembersController( + not_null peer) { + if (auto chat = peer->asChat()) { + return std::make_unique(chat); + } else if (auto channel = peer->asChannel()) { + using ChannelMembersController + = ::Profile::ParticipantsBoxController; + return std::make_unique( + channel, + ChannelMembersController::Role::Profile); + } + Unexpected("Peer type in CreateMembersController()"); +} + +} // namespace Profile +} // namespace Info diff --git a/Telegram/SourceFiles/info/profile/info_profile_members_controllers.h b/Telegram/SourceFiles/info/profile/info_profile_members_controllers.h new file mode 100644 index 0000000000..5f70094abe --- /dev/null +++ b/Telegram/SourceFiles/info/profile/info_profile_members_controllers.h @@ -0,0 +1,32 @@ +/* +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 + +class PeerListController; + +namespace Info { +namespace Profile { + +std::unique_ptr CreateMembersController( + not_null peer); + +} // namespace Profile +} // namespace Info diff --git a/Telegram/SourceFiles/info/profile/info_profile_widget.cpp b/Telegram/SourceFiles/info/profile/info_profile_widget.cpp index 2158d17274..fd48fd9325 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_widget.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_widget.cpp @@ -52,6 +52,15 @@ Widget::Widget( controller, peer)); _inner->move(0, 0); + _inner->scrollToRequests() + | rpl::start([this](Ui::ScrollToRequest request) { + if (request.ymin < 0) { + scrollTopRestore( + qMin(scrollTopSave(), request.ymax)); + } else { + scrollTo(request); + } + }, lifetime()); } Section Widget::section() const { diff --git a/Telegram/SourceFiles/profile/profile.style b/Telegram/SourceFiles/profile/profile.style index df2662daca..f21b350705 100644 --- a/Telegram/SourceFiles/profile/profile.style +++ b/Telegram/SourceFiles/profile/profile.style @@ -121,7 +121,7 @@ profileInviteLinkText: FlatLabel(profileBlockTextPart) { profileLimitReachedSkip: 6px; -profileMemberItem: ProfilePeerListItem(defaultProfileMemberItem) { +profileMemberItem: PeerListItem(defaultPeerListItem) { left: 8px; bottom: profileBlockMarginBottom; button: defaultLeftOutlineButton; diff --git a/Telegram/SourceFiles/profile/profile_block_group_members.cpp b/Telegram/SourceFiles/profile/profile_block_group_members.cpp index af374f5f97..137f4c8f90 100644 --- a/Telegram/SourceFiles/profile/profile_block_group_members.cpp +++ b/Telegram/SourceFiles/profile/profile_block_group_members.cpp @@ -39,7 +39,7 @@ GroupMembersWidget::GroupMembersWidget( QWidget *parent, PeerData *peer, TitleVisibility titleVisibility, - const style::ProfilePeerListItem &st) + const style::PeerListItem &st) : PeerListWidget(parent , peer , (titleVisibility == TitleVisibility::Visible) ? lang(lng_profile_participants_section) : QString() diff --git a/Telegram/SourceFiles/profile/profile_block_group_members.h b/Telegram/SourceFiles/profile/profile_block_group_members.h index 4710bd460f..8e2dc0aa1d 100644 --- a/Telegram/SourceFiles/profile/profile_block_group_members.h +++ b/Telegram/SourceFiles/profile/profile_block_group_members.h @@ -41,7 +41,7 @@ public: Visible, Hidden, }; - GroupMembersWidget(QWidget *parent, PeerData *peer, TitleVisibility titleVisibility = TitleVisibility::Visible, const style::ProfilePeerListItem &st = st::profileMemberItem); + GroupMembersWidget(QWidget *parent, PeerData *peer, TitleVisibility titleVisibility = TitleVisibility::Visible, const style::PeerListItem &st = st::profileMemberItem); int onlineCount() const { return _onlineCount; diff --git a/Telegram/SourceFiles/profile/profile_block_peer_list.cpp b/Telegram/SourceFiles/profile/profile_block_peer_list.cpp index ee19724536..fb04d58c62 100644 --- a/Telegram/SourceFiles/profile/profile_block_peer_list.cpp +++ b/Telegram/SourceFiles/profile/profile_block_peer_list.cpp @@ -33,7 +33,7 @@ PeerListWidget::Item::Item(PeerData *peer) : peer(peer) { PeerListWidget::Item::~Item() = default; -PeerListWidget::PeerListWidget(QWidget *parent, PeerData *peer, const QString &title, const style::ProfilePeerListItem &st, const QString &removeText) +PeerListWidget::PeerListWidget(QWidget *parent, PeerData *peer, const QString &title, const style::PeerListItem &st, const QString &removeText) : BlockWidget(parent, peer, title) , _st(st) , _removeText(removeText) diff --git a/Telegram/SourceFiles/profile/profile_block_peer_list.h b/Telegram/SourceFiles/profile/profile_block_peer_list.h index 1779131f6d..da920fdf4c 100644 --- a/Telegram/SourceFiles/profile/profile_block_peer_list.h +++ b/Telegram/SourceFiles/profile/profile_block_peer_list.h @@ -36,7 +36,7 @@ namespace Profile { class PeerListWidget : public BlockWidget { public: - PeerListWidget(QWidget *parent, PeerData *peer, const QString &title, const style::ProfilePeerListItem &st = st::profileMemberItem, const QString &removeText = QString()); + PeerListWidget(QWidget *parent, PeerData *peer, const QString &title, const style::PeerListItem &st = st::profileMemberItem, const QString &removeText = QString()); struct Item { explicit Item(PeerData *peer); @@ -136,7 +136,7 @@ private: void paintItem(Painter &p, int x, int y, Item *item, bool selected, bool selectedRemove, TimeMs ms); - const style::ProfilePeerListItem &_st; + const style::PeerListItem &_st; base::lambda _preloadMoreCallback; base::lambda _selectedCallback; diff --git a/Telegram/SourceFiles/profile/profile_channel_controllers.cpp b/Telegram/SourceFiles/profile/profile_channel_controllers.cpp index 210bfcb8e5..614478556c 100644 --- a/Telegram/SourceFiles/profile/profile_channel_controllers.cpp +++ b/Telegram/SourceFiles/profile/profile_channel_controllers.cpp @@ -38,7 +38,8 @@ constexpr auto kParticipantsPerPage = 200; } // namespace -ParticipantsBoxController::ParticipantsBoxController(not_null channel, Role role) : PeerListController(CreateSearchController(channel, role, &_additional)) +ParticipantsBoxController::ParticipantsBoxController(not_null channel, Role role) +: PeerListController(CreateSearchController(channel, role, &_additional)) , _channel(channel) , _role(role) { if (_channel->mgInfo) { @@ -46,10 +47,17 @@ ParticipantsBoxController::ParticipantsBoxController(not_null chan } } -std::unique_ptr ParticipantsBoxController::CreateSearchController(not_null channel, Role role, not_null additional) { +std::unique_ptr +ParticipantsBoxController::CreateSearchController( + not_null channel, + Role role, + not_null additional) { // In admins box complex search is used for adding new admins. if (role != Role::Admins || channel->canAddAdmins()) { - return std::make_unique(channel, role, additional); + return std::make_unique( + channel, + role, + additional); } return nullptr; } @@ -86,6 +94,7 @@ void ParticipantsBoxController::Start(not_null channel, Role role) } void ParticipantsBoxController::addNewItem() { + Expects(_role != Role::Profile); if (_role == Role::Members) { if (_channel->membersCount() >= Global::ChatSizeMax()) { Ui::show( @@ -131,7 +140,10 @@ std::unique_ptr ParticipantsBoxController::createSearchRow(not_null template void ParticipantsBoxController::HandleParticipant(const MTPChannelParticipant &participant, Role role, not_null additional, Callback callback) { - if ((role == Role::Members || role == Role::Admins) && participant.type() == mtpc_channelParticipantAdmin) { + if ((role == Role::Profile + || role == Role::Members + || role == Role::Admins) + && participant.type() == mtpc_channelParticipantAdmin) { auto &admin = participant.c_channelParticipantAdmin(); if (auto user = App::userLoaded(admin.vuser_id.v)) { additional->adminRights[user] = admin.vadmin_rights; @@ -152,13 +164,20 @@ void ParticipantsBoxController::HandleParticipant(const MTPChannelParticipant &p } callback(user); } - } else if ((role == Role::Members || role == Role::Admins) && participant.type() == mtpc_channelParticipantCreator) { + } else if ((role == Role::Profile + || role == Role::Members + || role == Role::Admins) + && participant.type() == mtpc_channelParticipantCreator) { auto &creator = participant.c_channelParticipantCreator(); if (auto user = App::userLoaded(creator.vuser_id.v)) { additional->creator = user; callback(user); } - } else if ((role == Role::Members || role == Role::Restricted || role == Role::Kicked) && participant.type() == mtpc_channelParticipantBanned) { + } else if ((role == Role::Profile + || role == Role::Members + || role == Role::Restricted + || role == Role::Kicked) + && participant.type() == mtpc_channelParticipantBanned) { auto &banned = participant.c_channelParticipantBanned(); if (auto user = App::userLoaded(banned.vuser_id.v)) { additional->restrictedRights[user] = banned.vbanned_rights; @@ -172,12 +191,16 @@ void ParticipantsBoxController::HandleParticipant(const MTPChannelParticipant &p } callback(user); } - } else if (role == Role::Members && participant.type() == mtpc_channelParticipant) { + } else if ((role == Role::Profile + || role == Role::Members) + && participant.type() == mtpc_channelParticipant) { auto &member = participant.c_channelParticipant(); if (auto user = App::userLoaded(member.vuser_id.v)) { callback(user); } - } else if (role == Role::Members && participant.type() == mtpc_channelParticipantSelf) { + } else if ((role == Role::Profile + || role == Role::Members) + && participant.type() == mtpc_channelParticipantSelf) { auto &member = participant.c_channelParticipantSelf(); if (auto user = App::userLoaded(member.vuser_id.v)) { callback(user); @@ -191,6 +214,7 @@ void ParticipantsBoxController::prepare() { auto titleKey = [this] { switch (_role) { case Role::Admins: return lng_channel_admins; + case Role::Profile: case Role::Members: return lng_profile_participants_section; case Role::Restricted: return lng_restricted_list_title; case Role::Kicked: return lng_banned_list_title; @@ -219,7 +243,7 @@ void ParticipantsBoxController::loadMoreRows() { } auto filter = [this] { - if (_role == Role::Members) { + if (_role == Role::Members || _role == Role::Profile) { return MTP_channelParticipantsRecent(); } else if (_role == Role::Admins) { return MTP_channelParticipantsAdmins(); @@ -261,7 +285,8 @@ void ParticipantsBoxController::loadMoreRows() { } bool ParticipantsBoxController::feedMegagroupLastParticipants() { - if (_role != Role::Members || _offset > 0) { + if ((_role != Role::Members && _role != Role::Profile) + || _offset > 0) { return false; } auto megagroup = _channel->asMegagroup(); @@ -322,7 +347,7 @@ void ParticipantsBoxController::rowActionClicked(not_null row) { auto user = row->peer()->asUser(); Expects(user != nullptr); - if (_role == Role::Members) { + if (_role == Role::Members || _role == Role::Profile) { kickMember(user); } else if (_role == Role::Admins) { showAdmin(user); @@ -617,6 +642,7 @@ bool ParticipantsBoxSearchController::loadMoreRows() { auto filter = [this] { switch (_role) { case Role::Admins: // Search for members, appoint as admin on found. + case Role::Profile: case Role::Members: return MTP_channelParticipantsSearch(MTP_string(_query)); case Role::Restricted: return MTP_channelParticipantsBanned(MTP_string(_query)); case Role::Kicked: return MTP_channelParticipantsKicked(MTP_string(_query)); diff --git a/Telegram/SourceFiles/profile/profile_channel_controllers.h b/Telegram/SourceFiles/profile/profile_channel_controllers.h index 968b7f513f..a05b680434 100644 --- a/Telegram/SourceFiles/profile/profile_channel_controllers.h +++ b/Telegram/SourceFiles/profile/profile_channel_controllers.h @@ -27,9 +27,14 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org namespace Profile { // Viewing admins, banned or restricted users list with search. -class ParticipantsBoxController : public PeerListController, private base::Subscriber, private MTP::Sender, public base::enable_weak_from_this { +class ParticipantsBoxController + : public PeerListController + , private base::Subscriber + , private MTP::Sender + , public base::enable_weak_from_this { public: enum class Role { + Profile, Members, Admins, Restricted, diff --git a/Telegram/SourceFiles/rpl/event_stream.h b/Telegram/SourceFiles/rpl/event_stream.h index bf8fc07261..17b1a2fc62 100644 --- a/Telegram/SourceFiles/rpl/event_stream.h +++ b/Telegram/SourceFiles/rpl/event_stream.h @@ -39,11 +39,11 @@ public: event_stream(event_stream &&other); template - void fire_forward(OtherValue &&value); - void fire(Value &&value) { + void fire_forward(OtherValue &&value) const; + void fire(Value &&value) const { return fire_forward(std::move(value)); } - void fire_copy(const Value &value) { + void fire_copy(const Value &value) const { return fire_forward(value); } producer events() const; @@ -76,7 +76,8 @@ inline event_stream::event_stream(event_stream &&other) template template -inline void event_stream::fire_forward(OtherValue &&value) { +inline void event_stream::fire_forward( + OtherValue &&value) const { if (!_consumers) { return; } diff --git a/Telegram/SourceFiles/ui/widgets/scroll_area.cpp b/Telegram/SourceFiles/ui/widgets/scroll_area.cpp index 4d184a0dc8..b13f0cff22 100644 --- a/Telegram/SourceFiles/ui/widgets/scroll_area.cpp +++ b/Telegram/SourceFiles/ui/widgets/scroll_area.cpp @@ -685,6 +685,10 @@ void ScrollArea::leaveEventHook(QEvent *e) { return QScrollArea::leaveEvent(e); } +void ScrollArea::scrollTo(ScrollToRequest request) { + scrollToY(request.ymin, request.ymax); +} + void ScrollArea::scrollToY(int toTop, int toBottom) { myEnsureResized(widget()); myEnsureResized(this); diff --git a/Telegram/SourceFiles/ui/widgets/scroll_area.h b/Telegram/SourceFiles/ui/widgets/scroll_area.h index f508ccb323..d097780f1a 100644 --- a/Telegram/SourceFiles/ui/widgets/scroll_area.h +++ b/Telegram/SourceFiles/ui/widgets/scroll_area.h @@ -34,21 +34,29 @@ enum class TouchScrollState { class ScrollArea; +struct ScrollToRequest { + ScrollToRequest(int ymin, int ymax) + : ymin(ymin) + , ymax(ymax) { + } + + int ymin = 0; + int ymax = 0; + +}; + class ScrollShadow : public QWidget { Q_OBJECT public: - ScrollShadow(ScrollArea *parent, const style::ScrollArea *st); void paintEvent(QPaintEvent *e); public slots: - void changeVisibility(bool shown); private: - const style::ScrollArea *_st; }; @@ -210,6 +218,8 @@ public: return _scrollTopUpdated.events_starting_with(scrollTop()); } + void scrollTo(ScrollToRequest request); + protected: bool eventFilter(QObject *obj, QEvent *e) override; diff --git a/Telegram/SourceFiles/ui/widgets/widgets.style b/Telegram/SourceFiles/ui/widgets/widgets.style index 5fc0cf3e71..cc7a845109 100644 --- a/Telegram/SourceFiles/ui/widgets/widgets.style +++ b/Telegram/SourceFiles/ui/widgets/widgets.style @@ -1052,7 +1052,7 @@ MediaPlayerButton { ripple: RippleAnimation; } -ProfilePeerListItem { +PeerListItem { left: pixels; bottom: pixels; height: pixels; @@ -1068,31 +1068,43 @@ ProfilePeerListItem { statusFgActive: color; } -defaultProfileMemberItem: ProfilePeerListItem { +PeerList { + padding: margins; + item: PeerListItem; +} + +defaultPeerListButton: OutlineButton { + outlineWidth: 0px; + + textBg: windowBg; + textBgOver: windowBgOver; + + textFg: windowSubTextFg; + textFgOver: windowSubTextFgOver; + + font: normalFont; + padding: margins(11px, 5px, 11px, 5px); + + ripple: defaultRippleAnimation; +} + +defaultPeerListItem: PeerListItem { 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; - } + button: defaultPeerListButton; statusFg: windowSubTextFg; statusFgOver: windowSubTextFgOver; statusFgActive: windowActiveTextFg; } +defaultPeerList: PeerList { + padding: margins(0px, 0px, 0px, 0px); + item: defaultPeerListItem; +} + InfoTopBar { height: pixels; back: IconButton; diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index 136c4f37be..2989b1cea6 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -233,6 +233,8 @@ <(src_loc)/info/profile/info_profile_inner_widget.h <(src_loc)/info/profile/info_profile_members.cpp <(src_loc)/info/profile/info_profile_members.h +<(src_loc)/info/profile/info_profile_members_controllers.cpp +<(src_loc)/info/profile/info_profile_members_controllers.h <(src_loc)/info/profile/info_profile_text.cpp <(src_loc)/info/profile/info_profile_text.h <(src_loc)/info/profile/info_profile_values.cpp