diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index bb41134c14..f631e837be 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -294,6 +294,8 @@ PRIVATE boxes/peer_list_box.h boxes/peer_list_controllers.cpp boxes/peer_list_controllers.h + boxes/peer_list_widgets.cpp + boxes/peer_list_widgets.h boxes/peer_lists_box.cpp boxes/peer_lists_box.h boxes/passcode_box.cpp diff --git a/Telegram/SourceFiles/boxes/peer_list_widgets.cpp b/Telegram/SourceFiles/boxes/peer_list_widgets.cpp new file mode 100644 index 0000000000..3a2b8ca37e --- /dev/null +++ b/Telegram/SourceFiles/boxes/peer_list_widgets.cpp @@ -0,0 +1,389 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "boxes/peer_list_widgets.h" + +#include "ui/painter.h" +#include "ui/widgets/buttons.h" +#include "ui/wrap/vertical_layout.h" +#include "styles/style_boxes.h" + +namespace { + +using State = std::unique_ptr; + +} // namespace + +PeerListWidgets::PeerListWidgets( + not_null parent, + not_null controller) +: Ui::RpWidget(parent) +, _controller(controller) +, _st(controller->computeListSt()) { + _content = base::make_unique_q(this); + parent->sizeValue() | rpl::start_with_next([this](const QSize &size) { + _content->resizeToWidth(size.width()); + resize(size.width(), _content->height()); + }, lifetime()); +} + +crl::time PeerListWidgets::paintRow( + Painter &p, + crl::time now, + bool selected, + not_null row) { + const auto &st = row->computeSt(_st.item); + + row->lazyInitialize(st); + const auto outerWidth = _content->width(); + const auto w = outerWidth; + + auto refreshStatusAt = row->refreshStatusTime(); + if (refreshStatusAt > 0 && now >= refreshStatusAt) { + row->refreshStatus(); + refreshStatusAt = row->refreshStatusTime(); + } + const auto refreshStatusIn = (refreshStatusAt > 0) + ? std::max(refreshStatusAt - now, crl::time(1)) + : 0; + + const auto peer = row->special() ? nullptr : row->peer().get(); + row->paintUserpic( + p, + st, + st.photoPosition.x(), + st.photoPosition.y(), + outerWidth); + + p.setPen(st::contactsNameFg); + + const auto skipRight = st.photoPosition.x(); + const auto rightActionSize = row->rightActionSize(); + const auto rightActionMargins = rightActionSize.isEmpty() + ? QMargins() + : row->rightActionMargins(); + const auto &name = row->name(); + const auto namePosition = st.namePosition; + const auto namex = namePosition.x(); + const auto namey = namePosition.y(); + auto namew = outerWidth - namex - skipRight; + if (!rightActionSize.isEmpty() + && (namey < rightActionMargins.top() + rightActionSize.height()) + && (namey + st.nameStyle.font->height + > rightActionMargins.top())) { + namew -= rightActionMargins.left() + + rightActionSize.width() + + rightActionMargins.right() + - skipRight; + } + const auto statusx = st.statusPosition.x(); + const auto statusy = st.statusPosition.y(); + auto statusw = outerWidth - statusx - skipRight; + if (!rightActionSize.isEmpty() + && (statusy < rightActionMargins.top() + rightActionSize.height()) + && (statusy + st::contactsStatusFont->height + > rightActionMargins.top())) { + statusw -= rightActionMargins.left() + + rightActionSize.width() + + rightActionMargins.right() + - skipRight; + } + namew -= row->paintNameIconGetWidth( + p, + [=] { updateRow(row); }, + now, + namex, + namey, + name.maxWidth(), + namew, + w, + selected); + auto nameCheckedRatio = row->disabled() ? 0. : row->checkedRatio(); + p.setPen(anim::pen(st.nameFg, st.nameFgChecked, nameCheckedRatio)); + name.drawLeftElided(p, namex, namey, namew, w); + + p.setFont(st::contactsStatusFont); + row->paintStatusText(p, st, statusx, statusy, statusw, w, selected); + + row->elementsPaint(p, outerWidth, selected, 0); + + return refreshStatusIn; +} + +void PeerListWidgets::appendRow(std::unique_ptr row) { + Expects(row != nullptr); + + if (_rowsById.find(row->id()) == _rowsById.cend()) { + const auto raw = row.get(); + const auto &st = raw->computeSt(_st.item); + raw->setAbsoluteIndex(_rows.size()); + _rows.push_back(std::move(row)); + + const auto widget = _content->add( + object_ptr::fromRaw( + Ui::CreateSimpleSettingsButton( + _content.get(), + st.button.ripple, + st.button.textBgOver))); + widget->resize(widget->width(), st.height); + widget->paintRequest() | rpl::start_with_next([=, this] { + auto p = Painter(widget); + const auto selected = widget->isOver() || widget->isDown(); + paintRow(p, crl::now(), selected, raw); + }, widget->lifetime()); + + widget->setClickedCallback([this, raw] { + _controller->rowClicked(raw); + }); + } +} + +PeerListRow *PeerListWidgets::findRow(PeerListRowId id) { + const auto it = _rowsById.find(id); + return (it == _rowsById.cend()) ? nullptr : it->second.get(); +} + +void PeerListWidgets::updateRow(not_null row) { + const auto it = ranges::find_if( + _rows, + [row](const auto &r) { return r.get() == row; }); + if (it != _rows.end()) { + const auto index = std::distance(_rows.begin(), it); + if (const auto widget = _content->widgetAt(index)) { + widget->update(); + } + } +} + +int PeerListWidgets::fullRowsCount() { + return _rows.size(); +} + +[[nodiscard]] not_null PeerListWidgets::rowAt(int index) { + Expects(index >= 0 && index < _rows.size()); + + return _rows[index].get(); +} + +void PeerListWidgets::refreshRows() { + _content->resizeToWidth(width()); + resize(width(), _content->height()); +} + +void PeerListWidgetsDelegate::setContent(PeerListWidgets *content) { + _content = content; +} + +void PeerListWidgetsDelegate::setUiShow( + std::shared_ptr uiShow) { + _uiShow = std::move(uiShow); +} + +void PeerListWidgetsDelegate::peerListSetHideEmpty(bool hide) { + Unexpected("...PeerListWidgetsDelegate::peerListSetHideEmpty"); +} + +void PeerListWidgetsDelegate::peerListAppendRow( + std::unique_ptr row) { + _content->appendRow(std::move(row)); +} + +void PeerListWidgetsDelegate::peerListAppendSearchRow( + std::unique_ptr row) { + Unexpected("...PeerListWidgetsDelegate::peerListAppendSearchRow"); +} + +void PeerListWidgetsDelegate::peerListAppendFoundRow( + not_null row) { + Unexpected("...PeerListWidgetsDelegate::peerListAppendFoundRow"); +} + +void PeerListWidgetsDelegate::peerListPrependRow( + std::unique_ptr row) { + Unexpected("...PeerListWidgetsDelegate::peerListPrependRow"); +} + +void PeerListWidgetsDelegate::peerListPrependRowFromSearchResult( + not_null row) { + Unexpected( + "...PeerListWidgetsDelegate::peerListPrependRowFromSearchResult"); +} + +PeerListRow* PeerListWidgetsDelegate::peerListFindRow(PeerListRowId id) { + return _content->findRow(id); +} + +auto PeerListWidgetsDelegate::peerListLastRowMousePosition() +-> std::optional { + Unexpected("...PeerListWidgetsDelegate::peerListLastRowMousePosition"); +} + +void PeerListWidgetsDelegate::peerListUpdateRow(not_null row) { + _content->updateRow(row); +} + +void PeerListWidgetsDelegate::peerListRemoveRow(not_null row) { + Unexpected("...PeerListWidgetsDelegate::peerListRemoveRow"); +} + +void PeerListWidgetsDelegate::peerListConvertRowToSearchResult( + not_null row) { + Unexpected( + "...PeerListWidgetsDelegate::peerListConvertRowToSearchResult"); +} + +void PeerListWidgetsDelegate::peerListSetRowChecked( + not_null row, + bool checked) { + Unexpected("...PeerListWidgetsDelegate::peerListSetRowChecked"); +} + +void PeerListWidgetsDelegate::peerListSetRowHidden( + not_null row, + bool hidden) { + Unexpected("...PeerListWidgetsDelegate::peerListSetRowHidden"); +} + +void PeerListWidgetsDelegate::peerListSetForeignRowChecked( + not_null row, + bool checked, + anim::type animated) { +} + +int PeerListWidgetsDelegate::peerListFullRowsCount() { + return _content->fullRowsCount(); +} + +not_null PeerListWidgetsDelegate::peerListRowAt(int index) { + return _content->rowAt(index); +} + +int PeerListWidgetsDelegate::peerListSearchRowsCount() { + Unexpected("...PeerListWidgetsDelegate::peerListSearchRowsCount"); +} + +not_null PeerListWidgetsDelegate::peerListSearchRowAt(int) { + Unexpected("...PeerListWidgetsDelegate::peerListSearchRowAt"); +} + +void PeerListWidgetsDelegate::peerListRefreshRows() { + _content->refreshRows(); +} + +void PeerListWidgetsDelegate::peerListSetDescription( + object_ptr) { + Unexpected("...PeerListWidgetsDelegate::peerListSetDescription"); +} + +void PeerListWidgetsDelegate::peerListSetSearchNoResults( + object_ptr) { + Unexpected("...PeerListWidgetsDelegate::peerListSetSearchNoResults"); +} + +void PeerListWidgetsDelegate::peerListSetAboveWidget( + object_ptr) { + Unexpected("...PeerListWidgetsDelegate::peerListSetAboveWidget"); +} + +void PeerListWidgetsDelegate::peerListSetAboveSearchWidget( + object_ptr) { + Unexpected("...PeerListWidgetsDelegate::peerListSetAboveSearchWidget"); +} + +void PeerListWidgetsDelegate::peerListSetBelowWidget( + object_ptr) { + Unexpected("...PeerListWidgetsDelegate::peerListSetBelowWidget"); +} + +void PeerListWidgetsDelegate::peerListSetSearchMode(PeerListSearchMode mode) { + Unexpected("...PeerListWidgetsDelegate::peerListSetSearchMode"); +} + +void PeerListWidgetsDelegate::peerListMouseLeftGeometry() { + Unexpected("...PeerListWidgetsDelegate::peerListMouseLeftGeometry"); +} + +void PeerListWidgetsDelegate::peerListSortRows( + Fn) { + Unexpected("...PeerListWidgetsDelegate::peerListSortRows"); +} + +int PeerListWidgetsDelegate::peerListPartitionRows( + Fn border) { + Unexpected("...PeerListWidgetsDelegate::peerListPartitionRows"); +} + +State PeerListWidgetsDelegate::peerListSaveState() const { + Unexpected("...PeerListWidgetsDelegate::peerListSaveState"); + return nullptr; +} + +void PeerListWidgetsDelegate::peerListRestoreState( + std::unique_ptr state) { + Unexpected("...PeerListWidgetsDelegate::peerListRestoreState"); +} + +void PeerListWidgetsDelegate::peerListShowRowMenu( + not_null row, + bool highlightRow, + Fn)> destroyed) { +} + +void PeerListWidgetsDelegate::peerListSelectSkip(int direction) { + // _content->selectSkip(direction); +} + +void PeerListWidgetsDelegate::peerListPressLeftToContextMenu(bool shown) { + Unexpected("...PeerListWidgetsDelegate::peerListPressLeftToContextMenu"); +} + +bool PeerListWidgetsDelegate::peerListTrackRowPressFromGlobal(QPoint) { + return false; +} + +std::shared_ptr PeerListWidgetsDelegate::peerListUiShow() { + Expects(_uiShow != nullptr); + return _uiShow; +} + +void PeerListWidgetsDelegate::peerListAddSelectedPeerInBunch( + not_null peer) { + Unexpected("...PeerListWidgetsDelegate::peerListAddSelectedPeerInBunch"); +} + +void PeerListWidgetsDelegate::peerListAddSelectedRowInBunch( + not_null row) { + Unexpected("...PeerListWidgetsDelegate::peerListAddSelectedRowInBunch"); +} + +void PeerListWidgetsDelegate::peerListFinishSelectedRowsBunch() { + Unexpected("...PeerListWidgetsDelegate::peerListFinishSelectedRowsBunch"); +} + +void PeerListWidgetsDelegate::peerListSetTitle(rpl::producer title) { + Unexpected("...PeerListWidgetsDelegate::peerListSetTitle"); +} + +void PeerListWidgetsDelegate::peerListSetAdditionalTitle( + rpl::producer title) { + Unexpected("...PeerListWidgetsDelegate::peerListSetAdditionalTitle"); +} + +bool PeerListWidgetsDelegate::peerListIsRowChecked( + not_null row) { + Unexpected("...PeerListWidgetsDelegate::peerListIsRowChecked"); + return false; +} + +void PeerListWidgetsDelegate::peerListScrollToTop() { + Unexpected("...PeerListWidgetsDelegate::peerListScrollToTop"); +} + +int PeerListWidgetsDelegate::peerListSelectedRowsCount() { + Unexpected("...PeerListWidgetsDelegate::peerListSelectedRowsCount"); + return 0; +} diff --git a/Telegram/SourceFiles/boxes/peer_list_widgets.h b/Telegram/SourceFiles/boxes/peer_list_widgets.h new file mode 100644 index 0000000000..d139487bca --- /dev/null +++ b/Telegram/SourceFiles/boxes/peer_list_widgets.h @@ -0,0 +1,110 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "boxes/peer_list_box.h" + +namespace Ui { +class VerticalLayout; +} // namespace Ui + +class PeerListWidgets : public Ui::RpWidget { +public: + PeerListWidgets( + not_null parent, + not_null controller); + + crl::time paintRow( + Painter &p, + crl::time now, + bool selected, + not_null row); + void appendRow(std::unique_ptr row); + PeerListRow* findRow(PeerListRowId id); + void updateRow(not_null row); + int fullRowsCount(); + [[nodiscard]] not_null rowAt(int index); + void refreshRows(); + +private: + const not_null _controller; + const style::PeerList &_st; + base::unique_qptr _content; + + std::vector> _rows; + std::map> _rowsById; + std::map>> _rowsByPeer; +}; + +class PeerListWidgetsDelegate : public PeerListDelegate { +public: + void setContent(PeerListWidgets *content); + void setUiShow(std::shared_ptr uiShow); + + void peerListSetHideEmpty(bool hide) 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; + PeerListRow *peerListFindRow(PeerListRowId id) override; + std::optional peerListLastRowMousePosition() 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; + void peerListSetRowHidden( + not_null row, + bool hidden) override; + void peerListSetForeignRowChecked( + not_null row, + bool checked, + anim::type animated) override; + int peerListFullRowsCount() override; + not_null peerListRowAt(int index) override; + int peerListSearchRowsCount() override; + not_null peerListSearchRowAt(int index) override; + void peerListRefreshRows() override; + void peerListSetDescription(object_ptr) override; + void peerListSetSearchNoResults(object_ptr) override; + void peerListSetAboveWidget(object_ptr) override; + void peerListSetAboveSearchWidget(object_ptr) override; + void peerListSetBelowWidget(object_ptr) override; + void peerListSetSearchMode(PeerListSearchMode mode) override; + void peerListMouseLeftGeometry() override; + void peerListSortRows( + Fn) override; + int peerListPartitionRows(Fn border) override; + std::unique_ptr peerListSaveState() const override; + void peerListRestoreState(std::unique_ptr state) override; + void peerListShowRowMenu( + not_null row, + bool highlightRow, + Fn)> destroyed = nullptr) override; + void peerListSelectSkip(int direction) override; + void peerListPressLeftToContextMenu(bool shown) override; + bool peerListTrackRowPressFromGlobal(QPoint globalPosition) override; + std::shared_ptr peerListUiShow() override; + void peerListAddSelectedPeerInBunch(not_null peer) override; + void peerListAddSelectedRowInBunch(not_null row) override; + void peerListFinishSelectedRowsBunch() override; + void peerListSetTitle(rpl::producer title) override; + void peerListSetAdditionalTitle(rpl::producer title) override; + bool peerListIsRowChecked(not_null row) override; + void peerListScrollToTop() override; + int peerListSelectedRowsCount() override; + +private: + PeerListWidgets *_content = nullptr; + std::shared_ptr _uiShow; + +};