mirror of
https://github.com/vale981/tdesktop
synced 2025-03-06 10:11:41 -05:00
Moved a cute userpic checkbox from ShareBox to a separate class.
This commit is contained in:
parent
c514c62d61
commit
763b347a8f
7 changed files with 332 additions and 207 deletions
|
@ -19,6 +19,7 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
|||
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
using "basic.style";
|
||||
using "ui/widgets/widgets.style";
|
||||
|
||||
confirmInviteTitle: flatLabel(labelDefFlat) {
|
||||
font: font(16px semibold);
|
||||
|
@ -71,22 +72,24 @@ localStorageBoxSkip: 10px;
|
|||
|
||||
shareRowsTop: 12px;
|
||||
shareRowHeight: 108px;
|
||||
sharePhotoRadius: 28px;
|
||||
sharePhotoSmallRadius: 24px;
|
||||
sharePhotoTop: 6px;
|
||||
shareSelectWidth: 2px;
|
||||
shareSelectFg: windowActiveBg;
|
||||
shareCheckBorder: windowBg;
|
||||
shareCheckBg: windowActiveBg;
|
||||
shareCheckRadius: 10px;
|
||||
shareCheckSmallRadius: 3px;
|
||||
shareCheckIcon: icon {{ "default_checkbox_check", windowBg, point(3px, 6px) }};
|
||||
sharePhotoCheckbox: RoundImageCheckbox {
|
||||
imageRadius: 28px;
|
||||
imageSmallRadius: 24px;
|
||||
selectWidth: 2px;
|
||||
selectFg: windowActiveBg;
|
||||
selectDuration: 150;
|
||||
checkBorder: windowBg;
|
||||
checkBg: windowActiveBg;
|
||||
checkRadius: 10px;
|
||||
checkSmallRadius: 3px;
|
||||
checkIcon: icon {{ "default_checkbox_check", windowBg, point(3px, 6px) }};
|
||||
}
|
||||
shareNameFont: font(11px);
|
||||
shareNameFg: windowTextFg;
|
||||
shareNameActiveFg: btnYesColor;
|
||||
shareNameTop: 6px;
|
||||
shareColumnSkip: 6px;
|
||||
shareSelectDuration: 150;
|
||||
shareActivateDuration: 150;
|
||||
shareScrollDuration: 300;
|
||||
|
||||
|
|
|
@ -34,6 +34,16 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#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
|
||||
|
||||
ShareBox::ShareBox(CopyCallback &©Callback, SubmitCallback &&submitCallback, FilterCallback &&filterCallback) : ItemListBox(st::boxScroll)
|
||||
, _copyCallback(std_::move(copyCallback))
|
||||
, _submitCallback(std_::move(submitCallback))
|
||||
|
@ -260,8 +270,6 @@ ShareInner::ShareInner(QWidget *parent, ShareBox::FilterCallback &&filterCallbac
|
|||
_filter = qsl("a");
|
||||
updateFilter();
|
||||
|
||||
prepareWideCheckIcons();
|
||||
|
||||
using UpdateFlag = Notify::PeerUpdate::Flag;
|
||||
auto observeEvents = UpdateFlag::NameChanged | UpdateFlag::PhotoChanged;
|
||||
subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(observeEvents, [this](const Notify::PeerUpdate &update) {
|
||||
|
@ -426,7 +434,8 @@ ShareInner::Chat *ShareInner::getChat(Dialogs::Row *row) {
|
|||
auto peer = row->history()->peer;
|
||||
auto i = _dataMap.constFind(peer);
|
||||
if (i == _dataMap.cend()) {
|
||||
_dataMap.insert(peer, data = new Chat(peer));
|
||||
data = new Chat(peer, [this, peer] { repaintChat(peer); });
|
||||
_dataMap.insert(peer, data);
|
||||
updateChatName(data, peer);
|
||||
} else {
|
||||
data = i.value();
|
||||
|
@ -440,8 +449,8 @@ void ShareInner::setActive(int active) {
|
|||
if (active != _active) {
|
||||
auto changeNameFg = [this](int index, style::color from, style::color to) {
|
||||
if (auto chat = getChatAtIndex(index)) {
|
||||
chat->nameFg.start([this, chat] {
|
||||
repaintChat(chat->peer);
|
||||
chat->nameFg.start([this, peer = chat->peer] {
|
||||
repaintChat(peer);
|
||||
}, from->c, to->c, st::shareActivateDuration);
|
||||
}
|
||||
};
|
||||
|
@ -457,67 +466,10 @@ void ShareInner::paintChat(Painter &p, Chat *chat, int index) {
|
|||
auto x = _rowsLeft + qFloor((index % _columnCount) * _rowWidthReal);
|
||||
auto y = _rowsTop + (index / _columnCount) * _rowHeight;
|
||||
|
||||
auto selectionLevel = chat->selection.current(chat->selected ? 1. : 0.);
|
||||
|
||||
auto w = width();
|
||||
auto photoLeft = (_rowWidth - (st::sharePhotoRadius * 2)) / 2;
|
||||
auto outerWidth = width();
|
||||
auto photoLeft = (_rowWidth - (st::sharePhotoCheckbox.imageRadius * 2)) / 2;
|
||||
auto photoTop = st::sharePhotoTop;
|
||||
if (chat->selection.animating()) {
|
||||
p.setRenderHint(QPainter::SmoothPixmapTransform, true);
|
||||
auto userpicRadius = qRound(WideCacheScale * (st::sharePhotoRadius + (st::sharePhotoSmallRadius - st::sharePhotoRadius) * selectionLevel));
|
||||
auto userpicShift = WideCacheScale * st::sharePhotoRadius - userpicRadius;
|
||||
auto userpicLeft = x + photoLeft - (WideCacheScale - 1) * st::sharePhotoRadius + userpicShift;
|
||||
auto userpicTop = y + photoTop - (WideCacheScale - 1) * st::sharePhotoRadius + userpicShift;
|
||||
auto to = QRect(userpicLeft, userpicTop, userpicRadius * 2, userpicRadius * 2);
|
||||
auto from = QRect(QPoint(0, 0), chat->wideUserpicCache.size());
|
||||
p.drawPixmapLeft(to, w, chat->wideUserpicCache, from);
|
||||
p.setRenderHint(QPainter::SmoothPixmapTransform, false);
|
||||
} else {
|
||||
if (!chat->wideUserpicCache.isNull()) {
|
||||
chat->wideUserpicCache = QPixmap();
|
||||
}
|
||||
auto userpicRadius = chat->selected ? st::sharePhotoSmallRadius : st::sharePhotoRadius;
|
||||
auto userpicShift = st::sharePhotoRadius - userpicRadius;
|
||||
auto userpicLeft = x + photoLeft + userpicShift;
|
||||
auto userpicTop = y + photoTop + userpicShift;
|
||||
chat->peer->paintUserpicLeft(p, userpicRadius * 2, userpicLeft, userpicTop, w);
|
||||
}
|
||||
|
||||
if (selectionLevel > 0) {
|
||||
p.setRenderHint(QPainter::HighQualityAntialiasing, true);
|
||||
p.setOpacity(snap(selectionLevel, 0., 1.));
|
||||
p.setBrush(Qt::NoBrush);
|
||||
QPen pen = st::shareSelectFg;
|
||||
pen.setWidth(st::shareSelectWidth);
|
||||
p.setPen(pen);
|
||||
p.drawEllipse(myrtlrect(x + photoLeft, y + photoTop, st::sharePhotoRadius * 2, st::sharePhotoRadius * 2));
|
||||
p.setOpacity(1.);
|
||||
p.setRenderHint(QPainter::HighQualityAntialiasing, false);
|
||||
}
|
||||
|
||||
removeFadeOutedIcons(chat);
|
||||
p.setRenderHint(QPainter::SmoothPixmapTransform, true);
|
||||
for (auto &icon : chat->icons) {
|
||||
auto fadeIn = icon.fadeIn.current(1.);
|
||||
auto fadeOut = icon.fadeOut.current(1.);
|
||||
auto iconRadius = qRound(WideCacheScale * (st::shareCheckSmallRadius + fadeOut * (st::shareCheckRadius - st::shareCheckSmallRadius)));
|
||||
auto iconShift = WideCacheScale * st::shareCheckRadius - iconRadius;
|
||||
auto iconLeft = x + photoLeft + 2 * st::sharePhotoRadius + st::shareSelectWidth - 2 * st::shareCheckRadius - (WideCacheScale - 1) * st::shareCheckRadius + iconShift;
|
||||
auto iconTop = y + photoTop + 2 * st::sharePhotoRadius + st::shareSelectWidth - 2 * st::shareCheckRadius - (WideCacheScale - 1) * st::shareCheckRadius + iconShift;
|
||||
auto to = QRect(iconLeft, iconTop, iconRadius * 2, iconRadius * 2);
|
||||
auto from = QRect(QPoint(0, 0), _wideCheckIconCache.size());
|
||||
auto opacity = fadeIn * fadeOut;
|
||||
p.setOpacity(opacity);
|
||||
if (fadeOut < 1.) {
|
||||
p.drawPixmapLeft(to, w, icon.wideCheckCache, from);
|
||||
} else {
|
||||
auto divider = qRound((WideCacheScale - 2) * st::shareCheckRadius + fadeIn * 3 * st::shareCheckRadius);
|
||||
p.drawPixmapLeft(QRect(iconLeft, iconTop, divider, iconRadius * 2), w, _wideCheckIconCache, QRect(0, 0, divider * cIntRetinaFactor(), _wideCheckIconCache.height()));
|
||||
p.drawPixmapLeft(QRect(iconLeft + divider, iconTop, iconRadius * 2 - divider, iconRadius * 2), w, _wideCheckCache, QRect(divider * cIntRetinaFactor(), 0, _wideCheckCache.width() - divider * cIntRetinaFactor(), _wideCheckCache.height()));
|
||||
}
|
||||
}
|
||||
p.setRenderHint(QPainter::SmoothPixmapTransform, false);
|
||||
p.setOpacity(1.);
|
||||
chat->checkbox.paint(p, x + photoLeft, y + photoTop, outerWidth);
|
||||
|
||||
if (chat->nameFg.animating()) {
|
||||
p.setPen(chat->nameFg.current());
|
||||
|
@ -527,11 +479,14 @@ void ShareInner::paintChat(Painter &p, Chat *chat, int index) {
|
|||
|
||||
auto nameWidth = (_rowWidth - st::shareColumnSkip);
|
||||
auto nameLeft = st::shareColumnSkip / 2;
|
||||
auto nameTop = photoTop + st::sharePhotoRadius * 2 + st::shareNameTop;
|
||||
chat->name.drawLeftElided(p, x + nameLeft, y + nameTop, nameWidth, w, 2, style::al_top, 0, -1, 0, true);
|
||||
auto nameTop = photoTop + st::sharePhotoCheckbox.imageRadius * 2 + st::shareNameTop;
|
||||
chat->name.drawLeftElided(p, x + nameLeft, y + nameTop, nameWidth, outerWidth, 2, style::al_top, 0, -1, 0, true);
|
||||
}
|
||||
|
||||
ShareInner::Chat::Chat(PeerData *peer) : peer(peer), name(st::sharePhotoRadius * 2) {
|
||||
ShareInner::Chat::Chat(PeerData *peer, Ui::RoundImageCheckbox::UpdateCallback &&updateCallback)
|
||||
: peer(peer)
|
||||
, checkbox(st::sharePhotoCheckbox, std_::move(updateCallback), paintUserpicCallback(peer))
|
||||
, name(st::sharePhotoCheckbox.imageRadius * 2) {
|
||||
}
|
||||
|
||||
void ShareInner::paintEvent(QPaintEvent *e) {
|
||||
|
@ -613,7 +568,7 @@ void ShareInner::updateUpon(const QPoint &pos) {
|
|||
auto left = _rowsLeft + qFloor(column * _rowWidthReal) + st::shareColumnSkip / 2;
|
||||
auto top = _rowsTop + row * _rowHeight + st::sharePhotoTop;
|
||||
auto xupon = (x >= left) && (x < left + (_rowWidth - st::shareColumnSkip));
|
||||
auto yupon = (y >= top) && (y < top + st::sharePhotoRadius * 2 + st::shareNameTop + st::shareNameFont->height * 2);
|
||||
auto yupon = (y >= top) && (y < top + st::sharePhotoCheckbox.imageRadius * 2 + st::shareNameTop + st::shareNameFont->height * 2);
|
||||
auto upon = (xupon && yupon) ? (row * _columnCount + column) : -1;
|
||||
if (upon >= displayedChatsCount()) {
|
||||
upon = -1;
|
||||
|
@ -633,28 +588,13 @@ void ShareInner::onSelectActive() {
|
|||
}
|
||||
|
||||
void ShareInner::resizeEvent(QResizeEvent *e) {
|
||||
_columnSkip = (width() - _columnCount * st::sharePhotoRadius * 2) / float64(_columnCount + 1);
|
||||
_rowWidthReal = st::sharePhotoRadius * 2 + _columnSkip;
|
||||
_columnSkip = (width() - _columnCount * st::sharePhotoCheckbox.imageRadius * 2) / float64(_columnCount + 1);
|
||||
_rowWidthReal = st::sharePhotoCheckbox.imageRadius * 2 + _columnSkip;
|
||||
_rowsLeft = qFloor(_columnSkip / 2);
|
||||
_rowWidth = qFloor(_rowWidthReal);
|
||||
update();
|
||||
}
|
||||
|
||||
struct AnimBumpy {
|
||||
AnimBumpy(float64 bump) : bump(bump)
|
||||
, dt0(bump - sqrt(bump * (bump - 1.)))
|
||||
, k(1 / (2 * dt0 - 1)) {
|
||||
}
|
||||
float64 bump;
|
||||
float64 dt0;
|
||||
float64 k;
|
||||
};
|
||||
|
||||
float64 anim_bumpy(const float64 &delta, const float64 &dt) {
|
||||
static AnimBumpy data = { 1.25 };
|
||||
return delta * (data.bump - data.k * (dt - data.dt0) * (dt - data.dt0));
|
||||
}
|
||||
|
||||
void ShareInner::changeCheckState(Chat *chat) {
|
||||
if (!chat) return;
|
||||
|
||||
|
@ -664,110 +604,22 @@ void ShareInner::changeCheckState(Chat *chat) {
|
|||
row = _chatsIndexed->addToEnd(App::history(chat->peer)).value(0);
|
||||
}
|
||||
chat = getChat(row);
|
||||
if (!chat->selected) {
|
||||
if (!chat->checkbox.selected()) {
|
||||
_chatsIndexed->moveToTop(chat->peer);
|
||||
}
|
||||
emit filterCancel();
|
||||
}
|
||||
|
||||
chat->selected = !chat->selected;
|
||||
if (chat->selected) {
|
||||
chat->checkbox.toggleSelected();
|
||||
if (chat->checkbox.selected()) {
|
||||
_selected.insert(chat->peer);
|
||||
chat->icons.push_back(Chat::Icon());
|
||||
chat->icons.back().fadeIn.start([this, chat] {
|
||||
repaintChat(chat->peer);
|
||||
}, 0, 1, st::shareSelectDuration);
|
||||
setActive(chatIndex(chat->peer));
|
||||
} else {
|
||||
_selected.remove(chat->peer);
|
||||
prepareWideCheckIconCache(&chat->icons.back());
|
||||
chat->icons.back().fadeOut.start([this, chat] {
|
||||
repaintChat(chat->peer);
|
||||
removeFadeOutedIcons(chat); // this call can destroy current lambda
|
||||
}, 1, 0, st::shareSelectDuration);
|
||||
}
|
||||
prepareWideUserpicCache(chat);
|
||||
chat->selection.start([this, chat] {
|
||||
repaintChat(chat->peer);
|
||||
}, chat->selected ? 0 : 1, chat->selected ? 1 : 0, st::shareSelectDuration, anim_bumpy);
|
||||
if (chat->selected) {
|
||||
setActive(chatIndex(chat->peer));
|
||||
}
|
||||
emit selectedChanged();
|
||||
}
|
||||
|
||||
void ShareInner::removeFadeOutedIcons(Chat *chat) {
|
||||
while (!chat->icons.empty() && !chat->icons.front().fadeIn.animating() && !chat->icons.front().fadeOut.animating()) {
|
||||
if (chat->icons.size() > 1 || !chat->selected) {
|
||||
chat->icons.erase(chat->icons.begin());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ShareInner::prepareWideUserpicCache(Chat *chat) {
|
||||
if (chat->wideUserpicCache.isNull()) {
|
||||
auto size = st::sharePhotoRadius * 2;
|
||||
auto wideSize = size * WideCacheScale;
|
||||
QImage cache(wideSize * cIntRetinaFactor(), wideSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
|
||||
cache.setDevicePixelRatio(cRetinaFactor());
|
||||
{
|
||||
Painter p(&cache);
|
||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
p.fillRect(0, 0, wideSize, wideSize, Qt::transparent);
|
||||
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||||
chat->peer->paintUserpic(p, size, (wideSize - size) / 2, (wideSize - size) / 2);
|
||||
}
|
||||
chat->wideUserpicCache = App::pixmapFromImageInPlace(std_::move(cache));
|
||||
chat->wideUserpicCache.setDevicePixelRatio(cRetinaFactor());
|
||||
}
|
||||
}
|
||||
|
||||
void ShareInner::prepareWideCheckIconCache(Chat::Icon *icon) {
|
||||
QImage wideCache(_wideCheckCache.width(), _wideCheckCache.height(), QImage::Format_ARGB32_Premultiplied);
|
||||
wideCache.setDevicePixelRatio(cRetinaFactor());
|
||||
{
|
||||
Painter p(&wideCache);
|
||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
auto iconRadius = WideCacheScale * st::shareCheckRadius;
|
||||
auto divider = qRound((WideCacheScale - 2) * st::shareCheckRadius + icon->fadeIn.current(1.) * 3 * st::shareCheckRadius);
|
||||
p.drawPixmapLeft(QRect(0, 0, divider, iconRadius * 2), width(), _wideCheckIconCache, QRect(0, 0, divider * cIntRetinaFactor(), _wideCheckIconCache.height()));
|
||||
p.drawPixmapLeft(QRect(divider, 0, iconRadius * 2 - divider, iconRadius * 2), width(), _wideCheckCache, QRect(divider * cIntRetinaFactor(), 0, _wideCheckCache.width() - divider * cIntRetinaFactor(), _wideCheckCache.height()));
|
||||
}
|
||||
icon->wideCheckCache = App::pixmapFromImageInPlace(std_::move(wideCache));
|
||||
icon->wideCheckCache.setDevicePixelRatio(cRetinaFactor());
|
||||
}
|
||||
|
||||
void ShareInner::prepareWideCheckIcons() {
|
||||
auto size = st::shareCheckRadius * 2;
|
||||
auto wideSize = size * WideCacheScale;
|
||||
QImage cache(wideSize * cIntRetinaFactor(), wideSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
|
||||
cache.setDevicePixelRatio(cRetinaFactor());
|
||||
{
|
||||
Painter p(&cache);
|
||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
p.fillRect(0, 0, wideSize, wideSize, Qt::transparent);
|
||||
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||||
p.setRenderHint(QPainter::HighQualityAntialiasing, true);
|
||||
auto pen = st::shareCheckBorder->p;
|
||||
pen.setWidth(st::shareSelectWidth);
|
||||
p.setPen(pen);
|
||||
p.setBrush(st::shareCheckBg);
|
||||
auto ellipse = QRect((wideSize - size) / 2, (wideSize - size) / 2, size, size);
|
||||
p.drawEllipse(ellipse);
|
||||
}
|
||||
QImage cacheIcon = cache;
|
||||
{
|
||||
Painter p(&cacheIcon);
|
||||
auto ellipse = QRect((wideSize - size) / 2, (wideSize - size) / 2, size, size);
|
||||
st::shareCheckIcon.paint(p, ellipse.topLeft(), wideSize);
|
||||
}
|
||||
_wideCheckCache = App::pixmapFromImageInPlace(std_::move(cache));
|
||||
_wideCheckCache.setDevicePixelRatio(cRetinaFactor());
|
||||
_wideCheckIconCache = App::pixmapFromImageInPlace(std_::move(cacheIcon));
|
||||
_wideCheckIconCache.setDevicePixelRatio(cRetinaFactor());
|
||||
}
|
||||
|
||||
bool ShareInner::hasSelected() const {
|
||||
return _selected.size();
|
||||
}
|
||||
|
@ -867,7 +719,7 @@ void ShareInner::peopleReceived(const QString &query, const QVector<MTPPeer> &pe
|
|||
auto *peer = App::peer(peerId);
|
||||
if (!peer || !_filterCallback(peer)) continue;
|
||||
|
||||
auto chat = new Chat(peer);
|
||||
auto chat = new Chat(peer, [this, peer] { repaintChat(peer); });
|
||||
updateChatName(chat, peer);
|
||||
if (auto row = _chatsIndexed->getRow(peer->id)) {
|
||||
continue;
|
||||
|
@ -902,7 +754,7 @@ QVector<PeerData*> ShareInner::selected() const {
|
|||
QVector<PeerData*> result;
|
||||
result.reserve(_dataMap.size());
|
||||
for_const (auto chat, _dataMap) {
|
||||
if (chat->selected) {
|
||||
if (chat->checkbox.selected()) {
|
||||
result.push_back(chat->peer);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "core/lambda_wrap.h"
|
||||
#include "core/observer.h"
|
||||
#include "core/vector_of_moveable.h"
|
||||
#include "ui/effects/round_image_checkbox.h"
|
||||
|
||||
namespace Dialogs {
|
||||
class Row;
|
||||
|
@ -151,30 +152,19 @@ private:
|
|||
|
||||
int displayedChatsCount() const;
|
||||
|
||||
static constexpr int WideCacheScale = 4;
|
||||
static constexpr int kWideCacheScale = Ui::kWideRoundImageCheckboxScale;
|
||||
struct Chat {
|
||||
Chat(PeerData *peer);
|
||||
Chat(PeerData *peer, Ui::RoundImageCheckbox::UpdateCallback &&updateCallback);
|
||||
|
||||
PeerData *peer;
|
||||
Ui::RoundImageCheckbox checkbox;
|
||||
Text name;
|
||||
bool selected = false;
|
||||
QPixmap wideUserpicCache;
|
||||
ColorAnimation nameFg;
|
||||
FloatAnimation selection;
|
||||
struct Icon {
|
||||
FloatAnimation fadeIn;
|
||||
FloatAnimation fadeOut;
|
||||
QPixmap wideCheckCache;
|
||||
};
|
||||
std_::vector_of_moveable<Icon> icons;
|
||||
};
|
||||
void paintChat(Painter &p, Chat *chat, int index);
|
||||
void updateChat(PeerData *peer);
|
||||
void updateChatName(Chat *chat, PeerData *peer);
|
||||
void repaintChat(PeerData *peer);
|
||||
void removeFadeOutedIcons(Chat *chat);
|
||||
void prepareWideUserpicCache(Chat *chat);
|
||||
void prepareWideCheckIconCache(Chat::Icon *icon);
|
||||
void prepareWideCheckIcons();
|
||||
int chatIndex(PeerData *peer) const;
|
||||
void repaintChatAtIndex(int index);
|
||||
Chat *getChatAtIndex(int index);
|
||||
|
@ -204,8 +194,6 @@ private:
|
|||
using FilteredDialogs = QVector<Dialogs::Row*>;
|
||||
FilteredDialogs _filtered;
|
||||
|
||||
QPixmap _wideCheckCache, _wideCheckIconCache;
|
||||
|
||||
using DataMap = QMap<PeerData*, Chat*>;
|
||||
DataMap _dataMap;
|
||||
using SelectedChats = OrderedSet<PeerData*>;
|
||||
|
|
202
Telegram/SourceFiles/ui/effects/round_image_checkbox.cpp
Normal file
202
Telegram/SourceFiles/ui/effects/round_image_checkbox.cpp
Normal file
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
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-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "stdafx.h"
|
||||
#include "ui/effects/round_image_checkbox.h"
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
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);
|
||||
cache.setDevicePixelRatio(cRetinaFactor());
|
||||
{
|
||||
Painter p(&cache);
|
||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
p.fillRect(0, 0, wideSize, wideSize, Qt::transparent);
|
||||
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||||
p.setRenderHint(QPainter::HighQualityAntialiasing, true);
|
||||
auto pen = st->checkBorder->p;
|
||||
pen.setWidth(st->selectWidth);
|
||||
p.setPen(pen);
|
||||
p.setBrush(st->checkBg);
|
||||
auto ellipse = QRect((wideSize - size) / 2, (wideSize - size) / 2, size, size);
|
||||
p.drawEllipse(ellipse);
|
||||
}
|
||||
QImage cacheIcon = cache;
|
||||
{
|
||||
Painter p(&cacheIcon);
|
||||
auto ellipse = QRect((wideSize - size) / 2, (wideSize - size) / 2, size, size);
|
||||
st->checkIcon.paint(p, ellipse.topLeft(), wideSize);
|
||||
}
|
||||
checkBgCache = App::pixmapFromImageInPlace(std_::move(cache));
|
||||
checkBgCache.setDevicePixelRatio(cRetinaFactor());
|
||||
checkFullCache = App::pixmapFromImageInPlace(std_::move(cacheIcon));
|
||||
checkFullCache.setDevicePixelRatio(cRetinaFactor());
|
||||
}
|
||||
|
||||
struct AnimBumpy {
|
||||
AnimBumpy(float64 bump) : bump(bump)
|
||||
, dt0(bump - sqrt(bump * (bump - 1.)))
|
||||
, k(1 / (2 * dt0 - 1)) {
|
||||
}
|
||||
float64 bump;
|
||||
float64 dt0;
|
||||
float64 k;
|
||||
};
|
||||
|
||||
template <int BumpRatioPercent>
|
||||
float64 anim_bumpy(const float64 &delta, const float64 &dt) {
|
||||
static AnimBumpy data = { BumpRatioPercent / 100. };
|
||||
return delta * (data.bump - data.k * (dt - data.dt0) * (dt - data.dt0));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RoundImageCheckbox::RoundImageCheckbox(const style::RoundImageCheckbox &st, UpdateCallback &&updateCallback, PaintRoundImage &&paintRoundImage)
|
||||
: _st(st)
|
||||
, _updateCallback(std_::move(updateCallback))
|
||||
, _paintRoundImage(std_::move(paintRoundImage)) {
|
||||
prepareCheckCaches(&_st, _wideCheckBgCache, _wideCheckFullCache);
|
||||
}
|
||||
|
||||
void RoundImageCheckbox::paint(Painter &p, int x, int y, int outerWidth) {
|
||||
auto selectionLevel = _selection.current(_selected ? 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 to = QRect(userpicLeft, userpicTop, userpicRadius * 2, userpicRadius * 2);
|
||||
auto from = QRect(QPoint(0, 0), _wideCache.size());
|
||||
p.drawPixmapLeft(to, outerWidth, _wideCache, from);
|
||||
p.setRenderHint(QPainter::SmoothPixmapTransform, false);
|
||||
} else {
|
||||
if (!_wideCache.isNull()) {
|
||||
_wideCache = QPixmap();
|
||||
}
|
||||
auto userpicRadius = _selected ? _st.imageSmallRadius : _st.imageRadius;
|
||||
auto userpicShift = _st.imageRadius - userpicRadius;
|
||||
auto userpicLeft = x + userpicShift;
|
||||
auto userpicTop = y + userpicShift;
|
||||
_paintRoundImage(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
|
||||
}
|
||||
|
||||
if (selectionLevel > 0) {
|
||||
p.setRenderHint(QPainter::HighQualityAntialiasing, true);
|
||||
p.setOpacity(snap(selectionLevel, 0., 1.));
|
||||
p.setBrush(Qt::NoBrush);
|
||||
QPen pen = _st.selectFg;
|
||||
pen.setWidth(_st.selectWidth);
|
||||
p.setPen(pen);
|
||||
p.drawEllipse(rtlrect(x, y, _st.imageRadius * 2, _st.imageRadius * 2, outerWidth));
|
||||
p.setOpacity(1.);
|
||||
p.setRenderHint(QPainter::HighQualityAntialiasing, false);
|
||||
}
|
||||
|
||||
removeFadeOutedIcons();
|
||||
p.setRenderHint(QPainter::SmoothPixmapTransform, true);
|
||||
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 to = QRect(iconLeft, iconTop, iconRadius * 2, iconRadius * 2);
|
||||
auto from = QRect(QPoint(0, 0), _wideCheckFullCache.size());
|
||||
auto opacity = fadeIn * fadeOut;
|
||||
p.setOpacity(opacity);
|
||||
if (fadeOut < 1.) {
|
||||
p.drawPixmapLeft(to, outerWidth, icon.wideCheckCache, from);
|
||||
} else {
|
||||
auto divider = qRound((kWideRoundImageCheckboxScale - 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()));
|
||||
}
|
||||
}
|
||||
p.setRenderHint(QPainter::SmoothPixmapTransform, false);
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
|
||||
void RoundImageCheckbox::toggleSelected() {
|
||||
_selected = !_selected;
|
||||
if (_selected) {
|
||||
_icons.push_back(Icon());
|
||||
_icons.back().fadeIn.start(UpdateCallback(_updateCallback), 0, 1, _st.selectDuration);
|
||||
} else {
|
||||
prepareWideCheckIconCache(&_icons.back());
|
||||
_icons.back().fadeOut.start([this] {
|
||||
_updateCallback();
|
||||
removeFadeOutedIcons(); // this call can destroy current lambda
|
||||
}, 1, 0, _st.selectDuration);
|
||||
}
|
||||
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) {
|
||||
_icons.erase(_icons.begin());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RoundImageCheckbox::prepareWideCache() {
|
||||
if (_wideCache.isNull()) {
|
||||
auto size = _st.imageRadius * 2;
|
||||
auto wideSize = size * kWideRoundImageCheckboxScale;
|
||||
QImage cache(wideSize * cIntRetinaFactor(), wideSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
|
||||
cache.setDevicePixelRatio(cRetinaFactor());
|
||||
{
|
||||
Painter p(&cache);
|
||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
p.fillRect(0, 0, wideSize, wideSize, Qt::transparent);
|
||||
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||||
_paintRoundImage(p, (wideSize - size) / 2, (wideSize - size) / 2, wideSize, size);
|
||||
}
|
||||
_wideCache = App::pixmapFromImageInPlace(std_::move(cache));
|
||||
}
|
||||
}
|
||||
|
||||
void RoundImageCheckbox::prepareWideCheckIconCache(Icon *icon) {
|
||||
auto cacheWidth = _wideCheckBgCache.width() / _wideCheckBgCache.devicePixelRatio();
|
||||
auto cacheHeight = _wideCheckBgCache.height() / _wideCheckBgCache.devicePixelRatio();
|
||||
auto wideCache = QImage(cacheWidth * cIntRetinaFactor(), cacheHeight * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
|
||||
wideCache.setDevicePixelRatio(cRetinaFactor());
|
||||
{
|
||||
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);
|
||||
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()));
|
||||
}
|
||||
icon->wideCheckCache = App::pixmapFromImageInPlace(std_::move(wideCache));
|
||||
icon->wideCheckCache.setDevicePixelRatio(cRetinaFactor());
|
||||
}
|
||||
|
||||
} // namespace Ui
|
65
Telegram/SourceFiles/ui/effects/round_image_checkbox.h
Normal file
65
Telegram/SourceFiles/ui/effects/round_image_checkbox.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
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-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
static constexpr int kWideRoundImageCheckboxScale = 4;
|
||||
class RoundImageCheckbox {
|
||||
public:
|
||||
using PaintRoundImage = base::lambda_unique<void(Painter &p, int x, int y, int outerWidth, int size)>;
|
||||
using UpdateCallback = base::lambda_wrap<void()>;
|
||||
RoundImageCheckbox(const style::RoundImageCheckbox &st, UpdateCallback &&updateCallback, PaintRoundImage &&paintRoundImage);
|
||||
|
||||
void paint(Painter &p, int x, int y, int outerWidth);
|
||||
|
||||
void toggleSelected();
|
||||
bool selected() const {
|
||||
return _selected;
|
||||
}
|
||||
|
||||
private:
|
||||
struct Icon {
|
||||
FloatAnimation fadeIn;
|
||||
FloatAnimation fadeOut;
|
||||
QPixmap wideCheckCache;
|
||||
};
|
||||
void removeFadeOutedIcons();
|
||||
void prepareWideCache();
|
||||
void prepareWideCheckIconCache(Icon *icon);
|
||||
|
||||
const style::RoundImageCheckbox &_st;
|
||||
UpdateCallback _updateCallback;
|
||||
PaintRoundImage _paintRoundImage;
|
||||
|
||||
bool _selected = false;
|
||||
QPixmap _wideCache;
|
||||
FloatAnimation _selection;
|
||||
std_::vector_of_moveable<Icon> _icons;
|
||||
|
||||
// Those pixmaps are shared among all checkboxes that have the same style.
|
||||
QPixmap _wideCheckBgCache, _wideCheckFullCache;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui
|
|
@ -54,6 +54,19 @@ FilledSlider {
|
|||
duration: int;
|
||||
}
|
||||
|
||||
RoundImageCheckbox {
|
||||
imageRadius: pixels;
|
||||
imageSmallRadius: pixels;
|
||||
selectWidth: pixels;
|
||||
selectFg: color;
|
||||
selectDuration: int;
|
||||
checkBorder: color;
|
||||
checkBg: color;
|
||||
checkRadius: pixels;
|
||||
checkSmallRadius: pixels;
|
||||
checkIcon: icon;
|
||||
}
|
||||
|
||||
widgetSlideDuration: 200;
|
||||
|
||||
discreteSliderHeight: 39px;
|
||||
|
|
|
@ -455,6 +455,8 @@
|
|||
'<(src_loc)/ui/effects/radial_animation.h',
|
||||
'<(src_loc)/ui/effects/rect_shadow.cpp',
|
||||
'<(src_loc)/ui/effects/rect_shadow.h',
|
||||
'<(src_loc)/ui/effects/round_image_checkbox.cpp',
|
||||
'<(src_loc)/ui/effects/round_image_checkbox.h',
|
||||
'<(src_loc)/ui/style/style_core.cpp',
|
||||
'<(src_loc)/ui/style/style_core.h',
|
||||
'<(src_loc)/ui/style/style_core_color.cpp',
|
||||
|
|
Loading…
Add table
Reference in a new issue