diff --git a/Telegram/Resources/art/sprite.png b/Telegram/Resources/art/sprite.png index 450efca53..c51595ddf 100644 Binary files a/Telegram/Resources/art/sprite.png and b/Telegram/Resources/art/sprite.png differ diff --git a/Telegram/Resources/art/sprite_200x.png b/Telegram/Resources/art/sprite_200x.png index ed5987c10..29705dde0 100644 Binary files a/Telegram/Resources/art/sprite_200x.png and b/Telegram/Resources/art/sprite_200x.png differ diff --git a/Telegram/Resources/basic.style b/Telegram/Resources/basic.style index 67e166752..7e75dd058 100644 --- a/Telegram/Resources/basic.style +++ b/Telegram/Resources/basic.style @@ -1354,10 +1354,7 @@ contactsStatusFg: #999999; contactsStatusFgOver: #7c99b2; contactsStatusFgOnline: #3b8dcc; contactsBgOver: overBg; -contactsBgActive: #6f9cbd; contactsCheckPosition: point(8px, 16px); -contactsCheckIcon: sprite(187px, 61px, 18px, 14px); -contactsCheckActiveIcon: sprite(187px, 75px, 18px, 14px); contactsNewItemHeight: 53px; contactsNewItemIcon: sprite(307px, 248px, 22px, 16px); contactsNewItemIconPosition: point(29px, 19px); diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 5cf04db88..1a6e30fed 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -68,14 +68,9 @@ aboutRevokePublicLabel: flatLabel(labelDefFlat) { textFg: windowTextFg; } -localStorageBoxSkip: 10px; - -shareRowsTop: 12px; -shareRowHeight: 108px; -sharePhotoTop: 6px; -sharePhotoCheckbox: RoundImageCheckbox { - imageRadius: 28px; - imageSmallRadius: 24px; +contactsPhotoCheckbox: RoundImageCheckbox { + imageRadius: 21px; + imageSmallRadius: 18px; selectWidth: 2px; selectFg: windowActiveBg; selectDuration: 150; @@ -85,6 +80,18 @@ sharePhotoCheckbox: RoundImageCheckbox { checkSmallRadius: 3px; checkIcon: icon {{ "default_checkbox_check", windowBg, point(3px, 6px) }}; } +contactsPhotoDisabledCheckFg: #bbbbbb; +contactsNameCheckedFg: #2b88b8; + +localStorageBoxSkip: 10px; + +shareRowsTop: 12px; +shareRowHeight: 108px; +sharePhotoTop: 6px; +sharePhotoCheckbox: RoundImageCheckbox(contactsPhotoCheckbox) { + imageRadius: 28px; + imageSmallRadius: 24px; +} shareNameFont: font(11px); shareNameFg: windowTextFg; shareNameActiveFg: btnYesColor; diff --git a/Telegram/SourceFiles/boxes/contactsbox.cpp b/Telegram/SourceFiles/boxes/contactsbox.cpp index b006dedbb..ab1470576 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.cpp +++ b/Telegram/SourceFiles/boxes/contactsbox.cpp @@ -23,6 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "dialogs/dialogs_indexed_list.h" #include "styles/style_dialogs.h" +#include "styles/style_boxes.h" #include "lang.h" #include "boxes/addcontactbox.h" #include "mainwidget.h" @@ -38,6 +39,14 @@ QString cantInviteError() { return lng_cant_invite_not_contact(lt_more_info, textcmdLink(qsl("https://telegram.me/spambot"), lang(lng_cant_more_info))); } +ContactsInner::ContactData::ContactData() : name(st::boxWideWidth) { +} + +ContactsInner::ContactData::ContactData(PeerData *peer, Ui::RoundImageCheckbox::UpdateCallback &&updateCallback) +: checkbox(std_::make_unique(st::contactsPhotoCheckbox, std_::move(updateCallback), PaintUserpicCallback(peer))) +, name(st::boxWideWidth) { +} + ContactsInner::ContactsInner(CreatingGroupType creating) : TWidget() , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _newItemHeight(creating == CreatingGroupNone ? st::contactsNewItemHeight : 0) @@ -71,7 +80,7 @@ ContactsInner::ContactsInner(ChatData *chat, MembersFilter membersFilter) : TWid , _chat(chat) , _membersFilter(membersFilter) , _allAdmins(this, lang(lng_chat_all_members_admins), !_chat->adminsEnabled(), st::contactsAdminCheckbox) -, _aboutWidth(st::boxWideWidth - st::contactsPadding.left() - st::contactsPadding.right() - st::contactsCheckPosition.x() * 2 - st::contactsCheckIcon.pxWidth()) +, _aboutWidth(st::boxWideWidth - st::contactsPadding.left() - st::contactsPadding.right()) , _aboutAllAdmins(st::boxTextFont, lang(lng_chat_about_all_admins), _defaultOptions, _aboutWidth) , _aboutAdmins(st::boxTextFont, lang(lng_chat_about_admins), _defaultOptions, _aboutWidth) , _customList((membersFilter == MembersFilterRecent) ? std_::unique_ptr() : std_::make_unique(Dialogs::SortMode::Add)) @@ -149,7 +158,7 @@ void ContactsInner::init() { } void ContactsInner::initList() { - if (!_chat || _membersFilter != MembersFilterAdmins) return; + if (!usingMultiSelect()) return; QList admins, others; admins.reserve(_chat->admins.size() + 1); @@ -370,41 +379,37 @@ ContactsInner::ContactData *ContactsInner::contactData(Dialogs::Row *row) { PeerData *peer = row->history()->peer; ContactsData::const_iterator i = _contactsData.constFind(peer); if (i == _contactsData.cend()) { - _contactsData.insert(peer, data = new ContactData()); + data = usingMultiSelect() ? new ContactData(peer, [this, peer] { updateRowWithPeer(peer); }) : new ContactData(); + _contactsData.insert(peer, data); if (peer->isUser()) { if (_chat) { if (_membersFilter == MembersFilterRecent) { - data->inchat = _chat->participants.contains(peer->asUser()); - } else { - data->inchat = false; + data->disabledChecked = _chat->participants.contains(peer->asUser()); } } else if (_creating == CreatingGroupGroup) { - data->inchat = (peerToUser(peer->id) == MTP::authedId()); + data->disabledChecked = (peerToUser(peer->id) == MTP::authedId()); } else if (_channel) { - data->inchat = (peerToUser(peer->id) == MTP::authedId()) || _already.contains(peer->asUser()); - } else { - data->inchat = false; + data->disabledChecked = (peerToUser(peer->id) == MTP::authedId()) || _already.contains(peer->asUser()); } - } else { - data->inchat = false; } - data->onlineColor = false; - data->check = _checkedContacts.contains(peer); + if (usingMultiSelect() && _checkedContacts.contains(peer)) { + data->checkbox->setChecked(true, Ui::RoundImageCheckbox::SetStyle::Fast); + } data->name.setText(st::contactsNameFont, peer->name, _textNameOptions); if (peer->isUser()) { - data->online = App::onlineText(peer->asUser(), _time); - data->onlineColor = App::onlineColorUse(peer->asUser(), _time); + data->statusText = App::onlineText(peer->asUser(), _time); + data->statusHasOnlineColor = App::onlineColorUse(peer->asUser(), _time); } else if (peer->isChat()) { ChatData *chat = peer->asChat(); if (!chat->amIn()) { - data->online = lang(lng_chat_status_unaccessible); + data->statusText = lang(lng_chat_status_unaccessible); } else { - data->online = lng_chat_status_members(lt_count, chat->count); + data->statusText = lng_chat_status_members(lt_count, chat->count); } } else if (peer->isMegagroup()) { - data->online = lang(lng_group_status); + data->statusText = lang(lng_group_status); } else if (peer->isChannel()) { - data->online = lang(lng_channel_status); + data->statusText = lang(lng_channel_status); } } else { data = i.value(); @@ -417,49 +422,58 @@ ContactsInner::ContactData *ContactsInner::contactData(Dialogs::Row *row) { void ContactsInner::paintDialog(Painter &p, PeerData *peer, ContactData *data, bool sel) { UserData *user = peer->asUser(); - bool inverse = data->inchat || data->check; if (_chat && _membersFilter == MembersFilterAdmins) { - inverse = false; if (_allAdmins.checked() || peer->id == peerFromUser(_chat->creator) || _saving) { sel = false; } } else { - if (data->inchat || data->check || selectedCount() >= Global::MegagroupSizeMax()) { + if (data->disabledChecked || selectedCount() >= Global::MegagroupSizeMax()) { sel = false; } } - p.fillRect(0, 0, width(), _rowHeight, inverse ? st::contactsBgActive : (sel ? st::contactsBgOver : st::white)); - p.setPen(inverse ? st::white : st::black); - peer->paintUserpicLeft(p, st::contactsPhotoSize, st::contactsPadding.left(), st::contactsPadding.top(), width()); - int32 namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(); - int32 iconw = (_chat || _creating != CreatingGroupNone) ? (st::contactsCheckPosition.x() * 2 + st::contactsCheckIcon.pxWidth()) : 0; - int32 namew = width() - namex - st::contactsPadding.right() - iconw; + auto paintDisabledCheck = data->disabledChecked; + if (_chat && _membersFilter == MembersFilterAdmins) { + if (peer->id == peerFromUser(_chat->creator) || _allAdmins.checked()) { + paintDisabledCheck = true; + } + } + + auto checkedRatio = 0.; + p.fillRect(0, 0, width(), _rowHeight, sel ? st::contactsBgOver : st::white); + if (paintDisabledCheck) { + paintDisabledCheckUserpic(p, peer, st::contactsPadding.left(), st::contactsPadding.top(), width()); + } else if (usingMultiSelect()) { + checkedRatio = data->checkbox->checkedAnimationRatio(); + data->checkbox->paint(p, st::contactsPadding.left(), st::contactsPadding.top(), width()); + } else { + peer->paintUserpicLeft(p, st::contactsPhotoSize, st::contactsPadding.left(), st::contactsPadding.top(), width()); + } + + int namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(); + int namew = width() - namex - st::contactsPadding.right(); if (peer->isVerified()) { auto icon = &st::dialogsVerifiedIcon; namew -= icon->width(); icon->paint(p, namex + qMin(data->name.maxWidth(), namew), st::contactsPadding.top() + st::contactsNameTop, width()); } + if (checkedRatio > 0) { + if (checkedRatio < 1) { + p.setPen(style::interpolate(st::black, st::contactsNameCheckedFg, checkedRatio)); + } else { + p.setPen(st::contactsNameCheckedFg); + } + } else { + p.setPen(st::black); + } data->name.drawLeftElided(p, namex, st::contactsPadding.top() + st::contactsNameTop, namew, width()); - if (_chat || (_creating != CreatingGroupNone && (!_channel || _membersFilter != MembersFilterAdmins))) { - if (_chat && _membersFilter == MembersFilterAdmins) { - if (sel || data->check || peer->id == peerFromUser(_chat->creator) || _allAdmins.checked()) { - if (!data->check || peer->id == peerFromUser(_chat->creator) || _allAdmins.checked()) p.setOpacity(0.5); - p.drawSpriteRight(st::contactsPadding.right() + st::contactsCheckPosition.x(), st::contactsPadding.top() + st::contactsCheckPosition.y(), width(), st::contactsCheckIcon); - if (!data->check || peer->id == peerFromUser(_chat->creator) || _allAdmins.checked()) p.setOpacity(1); - } - } else if (sel || data->check) { - p.drawSpriteRight(st::contactsPadding.right() + st::contactsCheckPosition.x(), st::contactsPadding.top() + st::contactsCheckPosition.y(), width(), (data->check ? st::contactsCheckActiveIcon : st::contactsCheckIcon)); - } - } - - bool uname = (user || peer->isChannel()) && (data->online.at(0) == '@'); + bool uname = (user || peer->isChannel()) && (data->statusText.at(0) == '@'); p.setFont(st::contactsStatusFont->f); - if (uname && !data->inchat && !data->check && !_lastQuery.isEmpty() && peer->userName().startsWith(_lastQuery, Qt::CaseInsensitive)) { - int32 availw = width() - namex - st::contactsPadding.right() - iconw; + if (uname && !_lastQuery.isEmpty() && peer->userName().startsWith(_lastQuery, Qt::CaseInsensitive)) { + int availw = width() - namex - st::contactsPadding.right(); QString first = '@' + peer->userName().mid(0, _lastQuery.size()), second = peer->userName().mid(_lastQuery.size()); - int32 w = st::contactsStatusFont->width(first); + int w = st::contactsStatusFont->width(first); if (w >= availw || second.isEmpty()) { p.setPen(st::contactsStatusFgOnline); p.drawTextLeft(namex, st::contactsPadding.top() + st::contactsStatusTop, width(), st::contactsStatusFont->elided(first, availw)); @@ -472,24 +486,57 @@ void ContactsInner::paintDialog(Painter &p, PeerData *peer, ContactData *data, b p.drawTextLeft(namex + w, st::contactsPadding.top() + st::contactsStatusTop, width() + w, second); } } else { - if (inverse) { - p.setPen(st::white); - } else if ((user && (uname || data->onlineColor)) || (peer->isChannel() && uname)) { + if ((user && (uname || data->statusHasOnlineColor)) || (peer->isChannel() && uname)) { p.setPen(st::contactsStatusFgOnline); } else { p.setPen(sel ? st::contactsStatusFgOver : st::contactsStatusFg); } - p.drawTextLeft(namex, st::contactsPadding.top() + st::contactsStatusTop, width(), data->online); + p.drawTextLeft(namex, st::contactsPadding.top() + st::contactsStatusTop, width(), data->statusText); } } +// Emulates Ui::RoundImageCheckbox::paint() in a checked state. +void ContactsInner::paintDisabledCheckUserpic(Painter &p, PeerData *peer, int x, int y, int outerWidth) const { + auto userpicRadius = st::contactsPhotoCheckbox.imageSmallRadius; + auto userpicShift = st::contactsPhotoCheckbox.imageRadius - userpicRadius; + auto userpicDiameter = st::contactsPhotoCheckbox.imageRadius * 2; + auto userpicLeft = x + userpicShift; + auto userpicTop = y + userpicShift; + auto userpicEllipse = rtlrect(x, y, userpicDiameter, userpicDiameter, outerWidth); + auto userpicBorderPen = st::contactsPhotoDisabledCheckFg->p; + userpicBorderPen.setWidth(st::contactsPhotoCheckbox.selectWidth); + + auto iconDiameter = 2 * st::contactsPhotoCheckbox.checkRadius; + auto iconLeft = x + userpicDiameter + st::contactsPhotoCheckbox.selectWidth - iconDiameter; + auto iconTop = y + userpicDiameter + st::contactsPhotoCheckbox.selectWidth - iconDiameter; + auto iconEllipse = rtlrect(iconLeft, iconTop, iconDiameter, iconDiameter, outerWidth); + auto iconBorderPen = st::contactsPhotoCheckbox.checkBorder->p; + iconBorderPen.setWidth(st::contactsPhotoCheckbox.selectWidth); + + peer->paintUserpicLeft(p, userpicRadius * 2, userpicLeft, userpicTop, width()); + + p.setRenderHint(QPainter::HighQualityAntialiasing, true); + + p.setPen(userpicBorderPen); + p.setBrush(Qt::NoBrush); + p.drawEllipse(userpicEllipse); + + p.setPen(iconBorderPen); + p.setBrush(st::contactsPhotoDisabledCheckFg); + p.drawEllipse(iconEllipse); + + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + + st::contactsPhotoCheckbox.checkIcon.paint(p, iconEllipse.topLeft(), outerWidth); +} + void ContactsInner::paintEvent(QPaintEvent *e) { QRect r(e->rect()); Painter p(this); p.setClipRect(r); _time = unixtime(); - p.fillRect(r, st::white->b); + p.fillRect(r, st::white); int32 yFrom = r.y(), yTo = r.y() + r.height(); if (_filter.isEmpty()) { @@ -500,8 +547,7 @@ void ContactsInner::paintEvent(QPaintEvent *e) { p.fillRect(0, _newItemHeight - st::contactsPadding.bottom() - st::lineWidth, width(), st::lineWidth, st::shadowColor); p.setPen(st::black); p.drawTextLeft(st::contactsPadding.left(), st::contactsNewItemTop, width(), lang(lng_chat_all_members_admins)); - int32 iconw = st::contactsCheckPosition.x() * 2 + st::contactsCheckIcon.pxWidth(); - int32 aboutw = width() - st::contactsPadding.left() - st::contactsPadding.right() - iconw; + int aboutw = width() - st::contactsPadding.left() - st::contactsPadding.right(); (_allAdmins.checked() ? _aboutAllAdmins : _aboutAdmins).draw(p, st::contactsPadding.left(), st::contactsNewItemHeight + st::contactsAboutTop, aboutw); } else { p.fillRect(0, 0, width(), st::contactsNewItemHeight, (_newItemSel ? st::contactsBgOver : st::white)->b); @@ -556,8 +602,7 @@ void ContactsInner::paintEvent(QPaintEvent *e) { p.fillRect(0, _newItemHeight - st::contactsPadding.bottom() - st::lineWidth, width(), st::lineWidth, st::shadowColor); p.setPen(st::black); p.drawTextLeft(st::contactsPadding.left(), st::contactsNewItemTop, width(), lang(lng_chat_all_members_admins)); - int32 iconw = st::contactsCheckPosition.x() * 2 + st::contactsCheckIcon.pxWidth(); - int32 aboutw = width() - st::contactsPadding.left() - st::contactsPadding.right() - iconw; + int aboutw = width() - st::contactsPadding.left() - st::contactsPadding.right(); (_allAdmins.checked() ? _aboutAllAdmins : _aboutAdmins).draw(p, st::contactsPadding.left(), st::contactsNewItemHeight + st::contactsAboutTop, aboutw); p.translate(0, _newItemHeight); } else if (cContactsReceived() && !_searching) { @@ -618,27 +663,72 @@ void ContactsInner::enterEvent(QEvent *e) { setMouseTracking(true); } -void ContactsInner::updateSelectedRow() { +int ContactsInner::getSelectedRowTop() const { if (_filter.isEmpty()) { - if (_newItemSel) { - update(0, 0, width(), st::contactsNewItemHeight); - } if (_sel) { - update(0, _newItemHeight + _sel->pos() * _rowHeight, width(), _rowHeight); - } - if (_byUsernameSel >= 0) { - update(0, _newItemHeight + _contacts->size() * _rowHeight + st::searchedBarHeight + _byUsernameSel * _rowHeight, width(), _rowHeight); + return _newItemHeight + (_sel->pos() * _rowHeight); + } else if (_byUsernameSel >= 0) { + return _newItemHeight + (_contacts->size() * _rowHeight) + st::searchedBarHeight + (_byUsernameSel * _rowHeight); } } else { if (_filteredSel >= 0) { - update(0, _filteredSel * _rowHeight, width(), _rowHeight); + return (_filteredSel * _rowHeight); + } else if (_byUsernameSel >= 0) { + return (_filtered.size() * _rowHeight + st::searchedBarHeight + _byUsernameSel * _rowHeight); } - if (_byUsernameSel >= 0) { - update(0, _filtered.size() * _rowHeight + st::searchedBarHeight + _byUsernameSel * _rowHeight, width(), _rowHeight); + } + return -1; +} + +void ContactsInner::updateSelectedRow() { + if (_filter.isEmpty() && _newItemSel) { + update(0, 0, width(), st::contactsNewItemHeight); + } else { + auto rowTop = getSelectedRowTop(); + if (rowTop >= 0) { + updateRowWithTop(rowTop); } } } +void ContactsInner::updateRowWithTop(int rowTop) { + update(0, rowTop, width(), _rowHeight); +} + +int ContactsInner::getRowTopWithPeer(PeerData *peer) const { + if (_filter.isEmpty()) { + for (auto i = _contacts->cbegin(), end = _contacts->cend(); i != end; ++i) { + if ((*i)->history()->peer == peer) { + return _newItemHeight + ((*i)->pos() * _rowHeight); + } + } + for (auto i = 0, count = _byUsername.size(); i != count; ++i) { + if (_byUsername[i] == peer) { + return _newItemHeight + (_contacts->size() * _rowHeight) + st::searchedBarHeight + (i * _rowHeight); + } + } + } else { + for (auto i = 0, count = _filtered.size(); i != count; ++i) { + if (_filtered[i]->history()->peer == peer) { + return (i * _rowHeight); + } + } + for (auto i = 0, count = _byUsernameFiltered.size(); i != count; ++i) { + if (_byUsernameFiltered[i] == peer) { + return (_contacts->size() * _rowHeight) + st::searchedBarHeight + (i * _rowHeight); + } + } + } + return -1; +} + +void ContactsInner::updateRowWithPeer(PeerData *peer) { + auto rowTop = getRowTopWithPeer(peer); + if (rowTop >= 0) { + updateRowWithTop(rowTop); + } +} + void ContactsInner::leaveEvent(QEvent *e) { _mouseSel = false; setMouseTracking(false); @@ -668,19 +758,19 @@ void ContactsInner::mousePressEvent(QMouseEvent *e) { void ContactsInner::chooseParticipant() { if (_saving) return; bool addingAdmin = (_channel && _membersFilter == MembersFilterAdmins); - if (!addingAdmin && (_chat || _creating != CreatingGroupNone)) { + if (!addingAdmin && usingMultiSelect()) { _time = unixtime(); if (_filter.isEmpty()) { if (_byUsernameSel >= 0 && _byUsernameSel < _byUsername.size()) { - if (d_byUsername[_byUsernameSel]->inchat) return; + if (d_byUsername[_byUsernameSel]->disabledChecked) return; changeCheckState(d_byUsername[_byUsernameSel], _byUsername[_byUsernameSel]); } else { - if (!_sel || contactData(_sel)->inchat) return; + if (!_sel || contactData(_sel)->disabledChecked) return; changeCheckState(_sel); } } else { if (_byUsernameSel >= 0 && _byUsernameSel < _byUsernameFiltered.size()) { - if (d_byUsernameFiltered[_byUsernameSel]->inchat) return; + if (d_byUsernameFiltered[_byUsernameSel]->disabledChecked) return; changeCheckState(d_byUsernameFiltered[_byUsernameSel], _byUsernameFiltered[_byUsernameSel]); ContactData *moving = d_byUsernameFiltered[_byUsernameSel]; @@ -703,7 +793,7 @@ void ContactsInner::chooseParticipant() { } } } else { - if (_filteredSel < 0 || _filteredSel >= _filtered.size() || contactData(_filtered[_filteredSel])->inchat) return; + if (_filteredSel < 0 || _filteredSel >= _filtered.size() || contactData(_filtered[_filteredSel])->disabledChecked) return; changeCheckState(_filtered[_filteredSel]); } emit selectAllQuery(); @@ -770,13 +860,15 @@ void ContactsInner::changeCheckState(Dialogs::Row *row) { } void ContactsInner::changeCheckState(ContactData *data, PeerData *peer) { + t_assert(usingMultiSelect()); + int32 cnt = _selCount; - if (data->check) { - data->check = false; + if (data->checkbox->checked()) { + data->checkbox->setChecked(false); _checkedContacts.remove(peer); --_selCount; } else if (selectedCount() < ((_channel && _channel->isMegagroup()) ? Global::MegagroupSizeMax() : Global::ChatSizeMax())) { - data->check = true; + data->checkbox->setChecked(true); _checkedContacts.insert(peer, true); ++_selCount; } else if (_channel && !_channel->isMegagroup()) { @@ -933,14 +1025,14 @@ void ContactsInner::updateFilter(QString filter) { } _filteredSel = -1; if (!_filtered.isEmpty()) { - for (_filteredSel = 0; (_filteredSel < _filtered.size()) && contactData(_filtered[_filteredSel])->inchat;) { + for (_filteredSel = 0; (_filteredSel < _filtered.size()) && contactData(_filtered[_filteredSel])->disabledChecked;) { ++_filteredSel; } if (_filteredSel == _filtered.size()) _filteredSel = -1; } _byUsernameSel = -1; if (_filteredSel < 0 && !_byUsernameFiltered.isEmpty()) { - for (_byUsernameSel = 0; (_byUsernameSel < _byUsernameFiltered.size()) && d_byUsernameFiltered[_byUsernameSel]->inchat;) { + for (_byUsernameSel = 0; (_byUsernameSel < _byUsernameFiltered.size()) && d_byUsernameFiltered[_byUsernameSel]->disabledChecked;) { ++_byUsernameSel; } if (_byUsernameSel == _byUsernameFiltered.size()) _byUsernameSel = -1; @@ -993,20 +1085,20 @@ void ContactsInner::peopleReceived(const QString &query, const QVector _byUsernameFiltered.reserve(already + people.size()); d_byUsernameFiltered.reserve(already + people.size()); for (QVector::const_iterator i = people.cbegin(), e = people.cend(); i != e; ++i) { - PeerId peerId = peerFromMTP(*i); - int32 j = 0; + auto peerId = peerFromMTP(*i); + int j = 0; for (; j < already; ++j) { if (_byUsernameFiltered[j]->id == peerId) break; } if (j == already) { - PeerData *p = App::peer(peerId); - if (!p) continue; + auto peer = App::peer(peerId); + if (!peer) continue; if (_channel || _chat || _creating != CreatingGroupNone) { - if (p->isUser()) { - if (p->asUser()->botInfo) { + if (peer->isUser()) { + if (peer->asUser()->botInfo) { if (_chat || _creating == CreatingGroupGroup) { // skip bot's that can't be invited to groups - if (p->asUser()->botInfo->cantJoinGroups) continue; + if (peer->asUser()->botInfo->cantJoinGroups) continue; } if (_channel) { if (!_channel->isMegagroup() && _membersFilter != MembersFilterAdmins) continue; @@ -1016,25 +1108,27 @@ void ContactsInner::peopleReceived(const QString &query, const QVector continue; // skip } } else if (sharingBotGame()) { - if (!p->canWrite()) { + if (!peer->canWrite()) { continue; } - if (auto channel = p->asChannel()) { + if (auto channel = peer->asChannel()) { if (channel->isBroadcast()) { continue; } } } - ContactData *d = new ContactData(); - _byUsernameDatas.push_back(d); - d->inchat = _chat ? _chat->participants.contains(p->asUser()) : ((_creating == CreatingGroupGroup || _channel) ? (p == App::self()) : false); - d->check = _checkedContacts.contains(p); - d->name.setText(st::contactsNameFont, p->name, _textNameOptions); - d->online = '@' + p->userName(); + auto data = usingMultiSelect() ? new ContactData(peer, [this, peer] { updateRowWithPeer(peer); }) : new ContactData(); + _byUsernameDatas.push_back(data); + data->disabledChecked = _chat ? _chat->participants.contains(peer->asUser()) : ((_creating == CreatingGroupGroup || _channel) ? (peer == App::self()) : false); + if (usingMultiSelect() && _checkedContacts.contains(peer)) { + data->checkbox->setChecked(true, Ui::RoundImageCheckbox::SetStyle::Fast); + } + data->name.setText(st::contactsNameFont, peer->name, _textNameOptions); + data->statusText = '@' + peer->userName(); - _byUsernameFiltered.push_back(p); - d_byUsernameFiltered.push_back(d); + _byUsernameFiltered.push_back(peer); + d_byUsernameFiltered.push_back(data); } } _searching = false; @@ -1160,33 +1254,33 @@ void ContactsInner::selectSkip(int32 dir) { _byUsernameSel = -1; } if (dir > 0) { - for (auto i = _contacts->cfind(_sel), end = _contacts->cend(); i != end && contactData(*i)->inchat; ++i) { + for (auto i = _contacts->cfind(_sel), end = _contacts->cend(); i != end && contactData(*i)->disabledChecked; ++i) { _sel = *i; } - if (_sel && contactData(_sel)->inchat) { + if (_sel && contactData(_sel)->disabledChecked) { _sel = nullptr; } if (!_sel) { if (!_byUsername.isEmpty()) { if (_byUsernameSel < 0) _byUsernameSel = 0; - for (; _byUsernameSel < _byUsername.size() && d_byUsername[_byUsernameSel]->inchat;) { + for (; _byUsernameSel < _byUsername.size() && d_byUsername[_byUsernameSel]->disabledChecked;) { ++_byUsernameSel; } if (_byUsernameSel == _byUsername.size()) _byUsernameSel = -1; } } } else { - while (_byUsernameSel >= 0 && d_byUsername[_byUsernameSel]->inchat) { + while (_byUsernameSel >= 0 && d_byUsername[_byUsernameSel]->disabledChecked) { --_byUsernameSel; } if (_byUsernameSel < 0) { if (!_contacts->isEmpty()) { if (!_newItemSel && !_sel) _sel = *(_contacts->cend() - 1); if (_sel) { - for (auto i = _contacts->cfind(_sel), b = _contacts->cbegin(); i != b && contactData(*i)->inchat; --i) { + for (auto i = _contacts->cfind(_sel), b = _contacts->cbegin(); i != b && contactData(*i)->disabledChecked; --i) { _sel = *i; } - if (contactData(_sel)->inchat) { + if (contactData(_sel)->disabledChecked) { _sel = nullptr; } } @@ -1215,27 +1309,27 @@ void ContactsInner::selectSkip(int32 dir) { _byUsernameSel = -1; } if (dir > 0) { - while (_filteredSel >= 0 && _filteredSel < _filtered.size() && contactData(_filtered[_filteredSel])->inchat) { + while (_filteredSel >= 0 && _filteredSel < _filtered.size() && contactData(_filtered[_filteredSel])->disabledChecked) { ++_filteredSel; } if (_filteredSel < 0 || _filteredSel >= _filtered.size()) { _filteredSel = -1; if (!_byUsernameFiltered.isEmpty()) { if (_byUsernameSel < 0) _byUsernameSel = 0; - for (; _byUsernameSel < _byUsernameFiltered.size() && d_byUsernameFiltered[_byUsernameSel]->inchat;) { + for (; _byUsernameSel < _byUsernameFiltered.size() && d_byUsernameFiltered[_byUsernameSel]->disabledChecked;) { ++_byUsernameSel; } if (_byUsernameSel == _byUsernameFiltered.size()) _byUsernameSel = -1; } } } else { - while (_byUsernameSel >= 0 && d_byUsernameFiltered[_byUsernameSel]->inchat) { + while (_byUsernameSel >= 0 && d_byUsernameFiltered[_byUsernameSel]->disabledChecked) { --_byUsernameSel; } if (_byUsernameSel < 0) { if (!_filtered.isEmpty()) { if (_filteredSel < 0) _filteredSel = _filtered.size() - 1; - for (; _filteredSel >= 0 && contactData(_filtered[_filteredSel])->inchat;) { + for (; _filteredSel >= 0 && contactData(_filtered[_filteredSel])->disabledChecked;) { --_filteredSel; } } @@ -1259,6 +1353,10 @@ void ContactsInner::selectSkipPage(int32 h, int32 dir) { QVector ContactsInner::selected() { QVector result; + if (!usingMultiSelect()) { + return result; + } + for_const (auto row, *_contacts) { if (_checkedContacts.contains(row->history()->peer)) { contactData(row); // fill _contactsData @@ -1266,12 +1364,12 @@ QVector ContactsInner::selected() { } result.reserve(_contactsData.size()); for (ContactsData::const_iterator i = _contactsData.cbegin(), e = _contactsData.cend(); i != e; ++i) { - if (i.value()->check && i.key()->isUser()) { + if (i.value()->checkbox->checked() && i.key()->isUser()) { result.push_back(i.key()->asUser()); } } for (int32 i = 0, l = _byUsername.size(); i < l; ++i) { - if (d_byUsername[i]->check && _byUsername[i]->isUser()) { + if (d_byUsername[i]->checkbox->checked() && _byUsername[i]->isUser()) { result.push_back(_byUsername[i]->asUser()); } } @@ -1280,6 +1378,10 @@ QVector ContactsInner::selected() { QVector ContactsInner::selectedInputs() { QVector result; + if (!usingMultiSelect()) { + return result; + } + for_const (auto row, *_contacts) { if (_checkedContacts.contains(row->history()->peer)) { contactData(row); // fill _contactsData @@ -1287,46 +1389,25 @@ QVector ContactsInner::selectedInputs() { } result.reserve(_contactsData.size()); for (ContactsData::const_iterator i = _contactsData.cbegin(), e = _contactsData.cend(); i != e; ++i) { - if (i.value()->check && i.key()->isUser()) { + if (i.value()->checkbox->checked() && i.key()->isUser()) { result.push_back(i.key()->asUser()->inputUser); } } for (int32 i = 0, l = _byUsername.size(); i < l; ++i) { - if (d_byUsername[i]->check && _byUsername[i]->isUser()) { + if (d_byUsername[i]->checkbox->checked() && _byUsername[i]->isUser()) { result.push_back(_byUsername[i]->asUser()->inputUser); } } return result; } -PeerData *ContactsInner::selectedUser() { - for_const (auto row, *_contacts) { - if (_checkedContacts.contains(row->history()->peer)) { - contactData(row); // fill _contactsData - } - } - for (ContactsData::const_iterator i = _contactsData.cbegin(), e = _contactsData.cend(); i != e; ++i) { - if (i.value()->check) { - return i.key(); - } - } - for (int32 i = 0, l = _byUsername.size(); i < l; ++i) { - if (d_byUsername[i]->check) { - return _byUsername[i]; - } - } - return 0; -} - ContactsBox::ContactsBox() : ItemListBox(st::contactsScroll) , _inner(CreatingGroupNone) , _filter(this, st::boxSearchField, lang(lng_participant_filter)) , _filterCancel(this, st::boxSearchCancel) , _next(this, lang(lng_create_group_next), st::defaultBoxButton) , _cancel(this, lang(lng_cancel), st::cancelBoxButton) -, _topShadow(this) -, _bottomShadow(0) -, _saveRequestId(0) { +, _topShadow(this) { init(); } @@ -1337,8 +1418,6 @@ ContactsBox::ContactsBox(const QString &name, const QImage &photo) : ItemListBox , _next(this, lang(lng_create_group_create), st::defaultBoxButton) , _cancel(this, lang(lng_create_group_back), st::cancelBoxButton) , _topShadow(this) -, _bottomShadow(0) -, _saveRequestId(0) , _creationName(name) , _creationPhoto(photo) { init(); @@ -1350,9 +1429,7 @@ ContactsBox::ContactsBox(ChannelData *channel) : ItemListBox(st::boxScroll) , _filterCancel(this, st::boxSearchCancel) , _next(this, lang(lng_participant_invite), st::defaultBoxButton) , _cancel(this, lang(lng_create_group_skip), st::cancelBoxButton) -, _topShadow(this) -, _bottomShadow(0) -, _saveRequestId(0) { +, _topShadow(this) { init(); } @@ -1362,9 +1439,7 @@ ContactsBox::ContactsBox(ChannelData *channel, MembersFilter filter, const Membe , _filterCancel(this, st::boxSearchCancel) , _next(this, lang(lng_participant_invite), st::defaultBoxButton) , _cancel(this, lang(lng_cancel), st::cancelBoxButton) -, _topShadow(this) -, _bottomShadow(0) -, _saveRequestId(0) { +, _topShadow(this) { init(); } @@ -1374,9 +1449,7 @@ ContactsBox::ContactsBox(ChatData *chat, MembersFilter filter) : ItemListBox(st: , _filterCancel(this, st::boxSearchCancel) , _next(this, lang((filter == MembersFilterAdmins) ? lng_settings_save : lng_participant_invite), st::defaultBoxButton) , _cancel(this, lang(lng_cancel), st::cancelBoxButton) -, _topShadow(this) -, _bottomShadow(0) -, _saveRequestId(0) { +, _topShadow(this) { init(); } @@ -1386,9 +1459,7 @@ ContactsBox::ContactsBox(UserData *bot) : ItemListBox(st::contactsScroll) , _filterCancel(this, st::boxSearchCancel) , _next(this, lang(lng_create_group_next), st::defaultBoxButton) , _cancel(this, lang(lng_cancel), st::cancelBoxButton) -, _topShadow(this) -, _bottomShadow(0) -, _saveRequestId(0) { +, _topShadow(this) { init(); } diff --git a/Telegram/SourceFiles/boxes/contactsbox.h b/Telegram/SourceFiles/boxes/contactsbox.h index 646421224..6f81ccf4e 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.h +++ b/Telegram/SourceFiles/boxes/contactsbox.h @@ -22,6 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "abstractbox.h" #include "core/single_timer.h" +#include "ui/effects/round_image_checkbox.h" namespace Dialogs { class Row; @@ -51,7 +52,6 @@ public: void init(); void initList(); - void paintDialog(Painter &p, PeerData *peer, ContactData *data, bool sel); void updateFilter(QString filter = QString()); void selectSkip(int32 dir); @@ -59,7 +59,6 @@ public: QVector selected(); QVector selectedInputs(); - PeerData *selectedUser(); bool allAdmins() const { return _allAdmins.checked(); } @@ -120,13 +119,24 @@ protected: void resizeEvent(QResizeEvent *e) override; private: + void updateRowWithTop(int rowTop); + int getSelectedRowTop() const; void updateSelectedRow(); + int getRowTopWithPeer(PeerData *peer) const; + void updateRowWithPeer(PeerData *peer); void addAdminDone(const MTPUpdates &result, mtpRequestId req); bool addAdminFail(const RPCError &error, mtpRequestId req); + void paintDialog(Painter &p, PeerData *peer, ContactData *data, bool sel); + void paintDisabledCheckUserpic(Painter &p, PeerData *peer, int x, int y, int outerWidth) const; + template void addDialogsToList(FilterCallback callback); + bool usingMultiSelect() const { + return (_chat != nullptr) || (_creating != CreatingGroupNone && (!_channel || _membersFilter != MembersFilterAdmins)); + } + int32 _rowHeight; int _newItemHeight = 0; bool _newItemSel = false; @@ -161,11 +171,14 @@ private: int _selCount = 0; struct ContactData { + ContactData(); + ContactData(PeerData *peer, Ui::RoundImageCheckbox::UpdateCallback &&updateCallback); + + std_::unique_ptr checkbox; Text name; - QString online; - bool onlineColor; - bool inchat; - bool check; + QString statusText; + bool statusHasOnlineColor = false; + bool disabledChecked = false; }; typedef QMap ContactsData; ContactsData _contactsData; @@ -239,7 +252,8 @@ private: BoxButton _next, _cancel; MembersFilter _membersFilter; - ScrollableBoxShadow _topShadow, *_bottomShadow; + ScrollableBoxShadow _topShadow; + ScrollableBoxShadow *_bottomShadow = nullptr; void peopleReceived(const MTPcontacts_Found &result, mtpRequestId req); bool peopleFailed(const RPCError &error, mtpRequestId req); @@ -255,7 +269,7 @@ private: typedef QMap PeopleQueries; PeopleQueries _peopleQueries; - int32 _saveRequestId; + mtpRequestId _saveRequestId = 0; // saving admins void saveAdminsDone(const MTPUpdates &result); @@ -419,3 +433,9 @@ private: SingleTimer _loadTimer; }; + +inline Ui::RoundImageCheckbox::PaintRoundImage PaintUserpicCallback(PeerData *peer) { + return [peer](Painter &p, int x, int y, int outerWidth, int size) { + peer->paintUserpicLeft(p, size, x, y, outerWidth); + }; +} diff --git a/Telegram/SourceFiles/boxes/sharebox.cpp b/Telegram/SourceFiles/boxes/sharebox.cpp index bdecfb495..15185e72f 100644 --- a/Telegram/SourceFiles/boxes/sharebox.cpp +++ b/Telegram/SourceFiles/boxes/sharebox.cpp @@ -33,16 +33,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "apiwrap.h" #include "ui/toast/toast.h" #include "history/history_media_types.h" - -namespace { - -Ui::RoundImageCheckbox::PaintRoundImage paintUserpicCallback(PeerData *peer) { - return [peer](Painter &p, int x, int y, int outerWidth, int size) { - peer->paintUserpicLeft(p, size, x, y, outerWidth); - }; -} - -} // namespace +#include "boxes/contactsbox.h" ShareBox::ShareBox(CopyCallback &©Callback, SubmitCallback &&submitCallback, FilterCallback &&filterCallback) : ItemListBox(st::boxScroll) , _copyCallback(std_::move(copyCallback)) @@ -485,7 +476,7 @@ void ShareInner::paintChat(Painter &p, Chat *chat, int index) { ShareInner::Chat::Chat(PeerData *peer, Ui::RoundImageCheckbox::UpdateCallback &&updateCallback) : peer(peer) -, checkbox(st::sharePhotoCheckbox, std_::move(updateCallback), paintUserpicCallback(peer)) +, checkbox(st::sharePhotoCheckbox, std_::move(updateCallback), PaintUserpicCallback(peer)) , name(st::sharePhotoCheckbox.imageRadius * 2) { } @@ -604,14 +595,14 @@ void ShareInner::changeCheckState(Chat *chat) { row = _chatsIndexed->addToEnd(App::history(chat->peer)).value(0); } chat = getChat(row); - if (!chat->checkbox.selected()) { + if (!chat->checkbox.checked()) { _chatsIndexed->moveToTop(chat->peer); } emit filterCancel(); } - chat->checkbox.toggleSelected(); - if (chat->checkbox.selected()) { + chat->checkbox.setChecked(!chat->checkbox.checked()); + if (chat->checkbox.checked()) { _selected.insert(chat->peer); setActive(chatIndex(chat->peer)); } else { @@ -754,7 +745,7 @@ QVector ShareInner::selected() const { QVector result; result.reserve(_dataMap.size()); for_const (auto chat, _dataMap) { - if (chat->checkbox.selected()) { + if (chat->checkbox.checked()) { result.push_back(chat->peer); } } diff --git a/Telegram/SourceFiles/boxes/sharebox.h b/Telegram/SourceFiles/boxes/sharebox.h index 245285cdf..a463597b3 100644 --- a/Telegram/SourceFiles/boxes/sharebox.h +++ b/Telegram/SourceFiles/boxes/sharebox.h @@ -152,7 +152,6 @@ private: int displayedChatsCount() const; - static constexpr int kWideCacheScale = Ui::kWideRoundImageCheckboxScale; struct Chat { Chat(PeerData *peer, Ui::RoundImageCheckbox::UpdateCallback &&updateCallback); diff --git a/Telegram/SourceFiles/ui/effects/round_image_checkbox.cpp b/Telegram/SourceFiles/ui/effects/round_image_checkbox.cpp index 000376c63..fbe75582d 100644 --- a/Telegram/SourceFiles/ui/effects/round_image_checkbox.cpp +++ b/Telegram/SourceFiles/ui/effects/round_image_checkbox.cpp @@ -24,10 +24,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace Ui { namespace { +static constexpr int kWideScale = 4; + void prepareCheckCaches(const style::RoundImageCheckbox *st, QPixmap &checkBgCache, QPixmap &checkFullCache) { auto size = st->checkRadius * 2; - auto wideSize = size * kWideRoundImageCheckboxScale; - QImage cache(wideSize * cIntRetinaFactor(), wideSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); + auto wideSize = size * kWideScale; + auto cache = QImage(wideSize * cIntRetinaFactor(), wideSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); cache.setDevicePixelRatio(cRetinaFactor()); { Painter p(&cache); @@ -42,7 +44,7 @@ void prepareCheckCaches(const style::RoundImageCheckbox *st, QPixmap &checkBgCac auto ellipse = QRect((wideSize - size) / 2, (wideSize - size) / 2, size, size); p.drawEllipse(ellipse); } - QImage cacheIcon = cache; + auto cacheIcon = cache; { Painter p(&cacheIcon); auto ellipse = QRect((wideSize - size) / 2, (wideSize - size) / 2, size, size); @@ -80,13 +82,13 @@ RoundImageCheckbox::RoundImageCheckbox(const style::RoundImageCheckbox &st, Upda } void RoundImageCheckbox::paint(Painter &p, int x, int y, int outerWidth) { - auto selectionLevel = _selection.current(_selected ? 1. : 0.); + auto selectionLevel = _selection.current(_checked ? 1. : 0.); if (_selection.animating()) { p.setRenderHint(QPainter::SmoothPixmapTransform, true); - auto userpicRadius = qRound(kWideRoundImageCheckboxScale * (_st.imageRadius + (_st.imageSmallRadius - _st.imageRadius) * selectionLevel)); - auto userpicShift = kWideRoundImageCheckboxScale * _st.imageRadius - userpicRadius; - auto userpicLeft = x - (kWideRoundImageCheckboxScale - 1) * _st.imageRadius + userpicShift; - auto userpicTop = y - (kWideRoundImageCheckboxScale - 1) * _st.imageRadius + userpicShift; + auto userpicRadius = qRound(kWideScale * (_st.imageRadius + (_st.imageSmallRadius - _st.imageRadius) * selectionLevel)); + auto userpicShift = kWideScale * _st.imageRadius - userpicRadius; + auto userpicLeft = x - (kWideScale - 1) * _st.imageRadius + userpicShift; + auto userpicTop = y - (kWideScale - 1) * _st.imageRadius + userpicShift; auto to = QRect(userpicLeft, userpicTop, userpicRadius * 2, userpicRadius * 2); auto from = QRect(QPoint(0, 0), _wideCache.size()); p.drawPixmapLeft(to, outerWidth, _wideCache, from); @@ -95,7 +97,7 @@ void RoundImageCheckbox::paint(Painter &p, int x, int y, int outerWidth) { if (!_wideCache.isNull()) { _wideCache = QPixmap(); } - auto userpicRadius = _selected ? _st.imageSmallRadius : _st.imageRadius; + auto userpicRadius = _checked ? _st.imageSmallRadius : _st.imageRadius; auto userpicShift = _st.imageRadius - userpicRadius; auto userpicLeft = x + userpicShift; auto userpicTop = y + userpicShift; @@ -106,7 +108,7 @@ void RoundImageCheckbox::paint(Painter &p, int x, int y, int outerWidth) { p.setRenderHint(QPainter::HighQualityAntialiasing, true); p.setOpacity(snap(selectionLevel, 0., 1.)); p.setBrush(Qt::NoBrush); - QPen pen = _st.selectFg; + auto pen = _st.selectFg->p; pen.setWidth(_st.selectWidth); p.setPen(pen); p.drawEllipse(rtlrect(x, y, _st.imageRadius * 2, _st.imageRadius * 2, outerWidth)); @@ -119,10 +121,10 @@ void RoundImageCheckbox::paint(Painter &p, int x, int y, int outerWidth) { for (auto &icon : _icons) { auto fadeIn = icon.fadeIn.current(1.); auto fadeOut = icon.fadeOut.current(1.); - auto iconRadius = qRound(kWideRoundImageCheckboxScale * (_st.checkSmallRadius + fadeOut * (_st.checkRadius - _st.checkSmallRadius))); - auto iconShift = kWideRoundImageCheckboxScale * _st.checkRadius - iconRadius; - auto iconLeft = x + 2 * _st.imageRadius + _st.selectWidth - 2 * _st.checkRadius - (kWideRoundImageCheckboxScale - 1) * _st.checkRadius + iconShift; - auto iconTop = y + 2 * _st.imageRadius + _st.selectWidth - 2 * _st.checkRadius - (kWideRoundImageCheckboxScale - 1) * _st.checkRadius + iconShift; + auto iconRadius = qRound(kWideScale * (_st.checkSmallRadius + fadeOut * (_st.checkRadius - _st.checkSmallRadius))); + auto iconShift = kWideScale * _st.checkRadius - iconRadius; + auto iconLeft = x + 2 * _st.imageRadius + _st.selectWidth - 2 * _st.checkRadius - (kWideScale - 1) * _st.checkRadius + iconShift; + auto iconTop = y + 2 * _st.imageRadius + _st.selectWidth - 2 * _st.checkRadius - (kWideScale - 1) * _st.checkRadius + iconShift; auto to = QRect(iconLeft, iconTop, iconRadius * 2, iconRadius * 2); auto from = QRect(QPoint(0, 0), _wideCheckFullCache.size()); auto opacity = fadeIn * fadeOut; @@ -130,7 +132,7 @@ void RoundImageCheckbox::paint(Painter &p, int x, int y, int outerWidth) { if (fadeOut < 1.) { p.drawPixmapLeft(to, outerWidth, icon.wideCheckCache, from); } else { - auto divider = qRound((kWideRoundImageCheckboxScale - 2) * _st.checkRadius + fadeIn * 3 * _st.checkRadius); + auto divider = qRound((kWideScale - 2) * _st.checkRadius + fadeIn * 3 * _st.checkRadius); p.drawPixmapLeft(QRect(iconLeft, iconTop, divider, iconRadius * 2), outerWidth, _wideCheckFullCache, QRect(0, 0, divider * cIntRetinaFactor(), _wideCheckFullCache.height())); p.drawPixmapLeft(QRect(iconLeft + divider, iconTop, iconRadius * 2 - divider, iconRadius * 2), outerWidth, _wideCheckBgCache, QRect(divider * cIntRetinaFactor(), 0, _wideCheckBgCache.width() - divider * cIntRetinaFactor(), _wideCheckBgCache.height())); } @@ -139,25 +141,50 @@ void RoundImageCheckbox::paint(Painter &p, int x, int y, int outerWidth) { p.setOpacity(1.); } -void RoundImageCheckbox::toggleSelected() { - _selected = !_selected; - if (_selected) { +float64 RoundImageCheckbox::checkedAnimationRatio() const { + return snap(_selection.current(_checked ? 1. : 0.), 0., 1.); +} + +void RoundImageCheckbox::setChecked(bool checked, SetStyle speed) { + if (_checked == checked) { + if (speed != SetStyle::Animated) { + if (!_icons.isEmpty()) { + _icons.back().fadeIn.finish(); + _icons.back().fadeOut.finish(); + } + _selection.finish(); + } + return; + } + _checked = checked; + if (_checked) { _icons.push_back(Icon()); _icons.back().fadeIn.start(UpdateCallback(_updateCallback), 0, 1, _st.selectDuration); + if (speed != SetStyle::Animated) { + _icons.back().fadeIn.finish(); + } } else { - prepareWideCheckIconCache(&_icons.back()); _icons.back().fadeOut.start([this] { _updateCallback(); removeFadeOutedIcons(); // this call can destroy current lambda }, 1, 0, _st.selectDuration); + if (speed == SetStyle::Animated) { + prepareWideCheckIconCache(&_icons.back()); + } else { + _icons.back().fadeOut.finish(); + } + } + if (speed == SetStyle::Animated) { + prepareWideCache(); + _selection.start(UpdateCallback(_updateCallback), _checked ? 0 : 1, _checked ? 1 : 0, _st.selectDuration, anim_bumpy<125>); + } else { + _selection.finish(); } - prepareWideCache(); - _selection.start(UpdateCallback(_updateCallback), _selected ? 0 : 1, _selected ? 1 : 0, _st.selectDuration, anim_bumpy<125>); } void RoundImageCheckbox::removeFadeOutedIcons() { while (!_icons.empty() && !_icons.front().fadeIn.animating() && !_icons.front().fadeOut.animating()) { - if (_icons.size() > 1 || !_selected) { + if (_icons.size() > 1 || !_checked) { _icons.erase(_icons.begin()); } else { break; @@ -168,7 +195,7 @@ void RoundImageCheckbox::removeFadeOutedIcons() { void RoundImageCheckbox::prepareWideCache() { if (_wideCache.isNull()) { auto size = _st.imageRadius * 2; - auto wideSize = size * kWideRoundImageCheckboxScale; + auto wideSize = size * kWideScale; QImage cache(wideSize * cIntRetinaFactor(), wideSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); cache.setDevicePixelRatio(cRetinaFactor()); { @@ -190,8 +217,8 @@ void RoundImageCheckbox::prepareWideCheckIconCache(Icon *icon) { { Painter p(&wideCache); p.setCompositionMode(QPainter::CompositionMode_Source); - auto iconRadius = kWideRoundImageCheckboxScale * _st.checkRadius; - auto divider = qRound((kWideRoundImageCheckboxScale - 2) * _st.checkRadius + icon->fadeIn.current(1.) * (kWideRoundImageCheckboxScale - 1) * _st.checkRadius); + auto iconRadius = kWideScale * _st.checkRadius; + auto divider = qRound((kWideScale - 2) * _st.checkRadius + icon->fadeIn.current(1.) * (kWideScale - 1) * _st.checkRadius); p.drawPixmapLeft(QRect(0, 0, divider, iconRadius * 2), cacheWidth, _wideCheckFullCache, QRect(0, 0, divider * cIntRetinaFactor(), _wideCheckFullCache.height())); p.drawPixmapLeft(QRect(divider, 0, iconRadius * 2 - divider, iconRadius * 2), cacheWidth, _wideCheckBgCache, QRect(divider * cIntRetinaFactor(), 0, _wideCheckBgCache.width() - divider * cIntRetinaFactor(), _wideCheckBgCache.height())); } diff --git a/Telegram/SourceFiles/ui/effects/round_image_checkbox.h b/Telegram/SourceFiles/ui/effects/round_image_checkbox.h index 3d4c5955e..972143625 100644 --- a/Telegram/SourceFiles/ui/effects/round_image_checkbox.h +++ b/Telegram/SourceFiles/ui/effects/round_image_checkbox.h @@ -24,7 +24,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace Ui { -static constexpr int kWideRoundImageCheckboxScale = 4; class RoundImageCheckbox { public: using PaintRoundImage = base::lambda_unique; @@ -32,11 +31,16 @@ public: RoundImageCheckbox(const style::RoundImageCheckbox &st, UpdateCallback &&updateCallback, PaintRoundImage &&paintRoundImage); void paint(Painter &p, int x, int y, int outerWidth); + float64 checkedAnimationRatio() const; - void toggleSelected(); - bool selected() const { - return _selected; + bool checked() const { + return _checked; } + enum class SetStyle { + Animated, + Fast, + }; + void setChecked(bool checked, SetStyle speed = SetStyle::Animated); private: struct Icon { @@ -52,7 +56,7 @@ private: UpdateCallback _updateCallback; PaintRoundImage _paintRoundImage; - bool _selected = false; + bool _checked = false; QPixmap _wideCache; FloatAnimation _selection; std_::vector_of_moveable _icons;