/* 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 "profile/profile_block_peer_list.h" #include "ui/effects/ripple_animation.h" #include "ui/text_options.h" #include "data/data_peer.h" #include "main/main_session.h" #include "styles/style_profile.h" #include "styles/style_widgets.h" namespace Profile { PeerListWidget::Item::Item(PeerData *peer) : peer(peer) { } PeerListWidget::Item::~Item() = default; PeerListWidget::PeerListWidget(QWidget *parent, PeerData *peer, const QString &title, const style::PeerListItem &st, const QString &removeText) : BlockWidget(parent, peer, title) , _st(st) , _removeText(removeText) , _removeWidth(st::normalFont->width(_removeText)) { setMouseTracking(true); subscribe(Auth().downloaderTaskFinished(), [this] { update(); }); } int PeerListWidget::resizeGetHeight(int newWidth) { auto newHeight = getListTop(); newHeight += _items.size() * _st.height; return newHeight + _st.bottom; } void PeerListWidget::visibleTopBottomUpdated(int visibleTop, int visibleBottom) { _visibleTop = visibleTop; _visibleBottom = visibleBottom; if (_preloadMoreCallback) { if (_visibleTop + PreloadHeightsCount * (_visibleBottom - _visibleTop) > height()) { _preloadMoreCallback(); } } } void PeerListWidget::paintContents(Painter &p) { auto left = getListLeft(); auto top = getListTop(); auto memberRowWidth = rowWidth(); auto from = floorclamp(_visibleTop - top, _st.height, 0, _items.size()); auto to = ceilclamp(_visibleBottom - top, _st.height, 0, _items.size()); for (auto i = from; i < to; ++i) { auto y = top + i * _st.height; auto selected = (_pressed >= 0) ? (i == _pressed) : (i == _selected); auto selectedRemove = selected && _selectedRemove; if (_pressed >= 0 && !_pressedRemove) { selectedRemove = false; } paintItem(p, left, y, _items[i], selected, selectedRemove); } } void PeerListWidget::paintItem(Painter &p, int x, int y, Item *item, bool selected, bool selectedKick) { if (_updateItemCallback) { _updateItemCallback(item); } auto memberRowWidth = rowWidth(); if (selected) { paintOutlinedRect(p, x, y, memberRowWidth, _st.height); } if (auto &ripple = item->ripple) { ripple->paint(p, x + _st.button.outlineWidth, y, width()); if (ripple->empty()) { ripple.reset(); } } int skip = _st.photoPosition.x(); item->peer->paintUserpicLeft(p, x + _st.photoPosition.x(), y + _st.photoPosition.y(), width(), _st.photoSize); if (item->name.isEmpty()) { item->name.setText( st::msgNameStyle, App::peerName(item->peer), Ui::NameTextOptions()); } int nameLeft = x + _st.namePosition.x(); int nameTop = y + _st.namePosition.y(); int nameWidth = memberRowWidth - _st.namePosition.x() - skip; if (item->hasRemoveLink && selected) { p.setFont(selectedKick ? st::normalFont->underline() : st::normalFont); p.setPen(st::windowActiveTextFg); p.drawTextLeft(nameLeft + nameWidth - _removeWidth, nameTop, width(), _removeText, _removeWidth); nameWidth -= _removeWidth + skip; } if (item->adminState != Item::AdminState::None) { nameWidth -= st::profileMemberAdminIcon.width(); auto iconLeft = nameLeft + qMin(nameWidth, item->name.maxWidth()); auto &icon = (item->adminState == Item::AdminState::Creator) ? (selected ? st::profileMemberCreatorIconOver : st::profileMemberCreatorIcon) : (selected ? st::profileMemberAdminIconOver : st::profileMemberAdminIcon); icon.paint(p, QPoint(iconLeft, nameTop), width()); } p.setPen(st::profileMemberNameFg); item->name.drawLeftElided(p, nameLeft, nameTop, nameWidth, width()); if (item->statusHasOnlineColor) { p.setPen(_st.statusFgActive); } else { p.setPen(selected ? _st.statusFgOver : _st.statusFg); } p.setFont(st::normalFont); p.drawTextLeft(x + _st.statusPosition.x(), y + _st.statusPosition.y(), width(), item->statusText); } void PeerListWidget::paintOutlinedRect(Painter &p, int x, int y, int w, int h) const { auto outlineWidth = _st.button.outlineWidth; if (outlineWidth) { p.fillRect(rtlrect(x, y, outlineWidth, h, width()), _st.button.outlineFgOver); } p.fillRect(rtlrect(x + outlineWidth, y, w - outlineWidth, h, width()), _st.button.textBgOver); } void PeerListWidget::mouseMoveEvent(QMouseEvent *e) { _mousePosition = e->globalPos(); updateSelection(); } void PeerListWidget::mousePressEvent(QMouseEvent *e) { _mousePosition = e->globalPos(); updateSelection(); _pressButton = e->button(); _pressed = _selected; _pressedRemove = _selectedRemove; if (_pressed >= 0 && !_pressedRemove) { auto item = _items[_pressed]; if (!item->ripple) { auto memberRowWidth = rowWidth(); auto mask = Ui::RippleAnimation::rectMask(QSize(memberRowWidth - _st.button.outlineWidth, _st.height)); item->ripple = std::make_unique(_st.button.ripple, std::move(mask), [this, index = _pressed] { repaintRow(index); }); } auto left = getListLeft() + _st.button.outlineWidth; auto top = getListTop() + _st.height * _pressed; item->ripple->add(e->pos() - QPoint(left, top)); } } void PeerListWidget::mouseReleaseEvent(QMouseEvent *e) { mousePressReleased(e->button()); } void PeerListWidget::mousePressReleased(Qt::MouseButton button) { repaintRow(_pressed); auto pressed = std::exchange(_pressed, -1); auto pressedRemove = base::take(_pressedRemove); if (pressed >= 0 && pressed < _items.size()) { if (auto &ripple = _items[pressed]->ripple) { ripple->lastStop(); } if (pressed == _selected && pressedRemove == _selectedRemove && button == Qt::LeftButton) { InvokeQueued(this, [this, pressedRemove, peer = _items[pressed]->peer] { if (auto &callback = (pressedRemove ? _removedCallback : _selectedCallback)) { callback(peer); } }); } } setCursor(_selectedRemove ? style::cur_pointer : style::cur_default); repaintSelectedRow(); } void PeerListWidget::enterEventHook(QEvent *e) { _mousePosition = QCursor::pos(); updateSelection(); } void PeerListWidget::leaveEventHook(QEvent *e) { _mousePosition = QPoint(-1, -1); updateSelection(); } void PeerListWidget::updateSelection() { auto selected = -1; auto selectedKick = false; auto mouse = mapFromGlobal(_mousePosition); if (rtl()) mouse.setX(width() - mouse.x()); auto left = getListLeft(); auto top = getListTop(); auto memberRowWidth = rowWidth(); if (mouse.x() >= left && mouse.x() < left + memberRowWidth && mouse.y() >= top) { selected = (mouse.y() - top) / _st.height; if (selected >= _items.size()) { selected = -1; } else if (_items[selected]->hasRemoveLink) { int skip = _st.photoPosition.x(); int nameLeft = left + _st.namePosition.x(); int nameTop = top + _selected * _st.height + _st.namePosition.y(); int nameWidth = memberRowWidth - _st.namePosition.x() - skip; if (mouse.x() >= nameLeft + nameWidth - _removeWidth && mouse.x() < nameLeft + nameWidth) { if (mouse.y() >= nameTop && mouse.y() < nameTop + st::normalFont->height) { selectedKick = true; } } } } setSelected(selected, selectedKick); } void PeerListWidget::setSelected(int selected, bool selectedRemove) { if (_selected == selected && _selectedRemove == selectedRemove) { return; } repaintSelectedRow(); if (_selectedRemove != selectedRemove) { _selectedRemove = selectedRemove; if (_pressed < 0) { setCursor(_selectedRemove ? style::cur_pointer : style::cur_default); } } if (_selected != selected) { _selected = selected; repaintSelectedRow(); } } void PeerListWidget::repaintSelectedRow() { repaintRow(_selected); } void PeerListWidget::repaintRow(int index) { if (index >= 0) { auto left = getListLeft(); rtlupdate(left, getListTop() + index * _st.height, width() - left, _st.height); } } int PeerListWidget::getListLeft() const { return _st.left; } int PeerListWidget::rowWidth() const { return _st.maximalWidth ? qMin(width() - getListLeft(), _st.maximalWidth) : width() - getListLeft(); } void PeerListWidget::preloadPhotos() { int top = getListTop(); int preloadFor = (_visibleBottom - _visibleTop) * PreloadHeightsCount; int from = floorclamp(_visibleTop - top, _st.height, 0, _items.size()); int to = ceilclamp(_visibleBottom + preloadFor - top, _st.height, 0, _items.size()); for (int i = from; i < to; ++i) { _items[i]->peer->loadUserpic(); } } void PeerListWidget::refreshVisibility() { setVisible(!_items.isEmpty()); } } // namespace Profile