New profile photo edit by drag-n-drop. Animated photo button.

This commit is contained in:
John Preston 2016-05-27 13:57:11 +03:00
parent 329285a8a6
commit 3570a1cf91
26 changed files with 531 additions and 147 deletions

View File

@ -459,6 +459,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_profile_copy_fullname" = "Copy name";
"lng_profile_drop_area_title" = "Drop your image here";
"lng_profile_drop_area_subtitle" = "to set it as a group photo";
"lng_profile_drop_area_subtitle_channel" = "to set it as a channel photo";
"lng_channel_add_admins" = "New administrator";
"lng_channel_add_members" = "Add members";

View File

@ -2304,6 +2304,10 @@ namespace {
return result;
}
QPixmap pixmapFromImageInPlace(QImage &&image) {
return QPixmap::fromImage(std_::forward<QImage>(image), Qt::ColorOnly);
}
void regPhotoItem(PhotoData *data, HistoryItem *item) {
::photoItems[data].insert(item, NullType());
}

View File

@ -234,6 +234,7 @@ namespace App {
QImage readImage(QByteArray data, QByteArray *format = 0, bool opaque = true, bool *animated = 0);
QImage readImage(const QString &file, QByteArray *format = 0, bool opaque = true, bool *animated = 0, QByteArray *content = 0);
QPixmap pixmapFromImageInPlace(QImage &&image);
void regPhotoItem(PhotoData *data, HistoryItem *item);
void unregPhotoItem(PhotoData *data, HistoryItem *item);

View File

@ -109,10 +109,9 @@ void AboutBox::paintEvent(QPaintEvent *e) {
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
QString _getCrashReportFile(const QMimeData *m) {
if (!m || m->urls().size() != 1) return QString();
if (!m || m->urls().size() != 1 || !m->urls().at(0).isLocalFile()) return QString();
QString file(m->urls().at(0).toLocalFile());
if (file.startsWith(qsl("/.file/id="))) file = psConvertFileUrl(file);
auto file = psConvertFileUrl(m->urls().at(0));
return file.endsWith(qstr(".telegramcrash"), Qt::CaseInsensitive) ? file : QString();
}

View File

@ -5590,8 +5590,7 @@ DragState HistoryWidget::getDragState(const QMimeData *d) {
for (QList<QUrl>::const_iterator i = urls.cbegin(), en = urls.cend(); i != en; ++i) {
if (!i->isLocalFile()) return DragStateNone;
QString file(i->toLocalFile());
if (file.startsWith(qsl("/.file/id="))) file = psConvertFileUrl(file);
auto file = psConvertFileUrl(*i);
QFileInfo info(file);
if (info.isDir()) return DragStateNone;
@ -8420,8 +8419,7 @@ QStringList HistoryWidget::getMediasFromMime(const QMimeData *d) {
for (QList<QUrl>::const_iterator i = urls.cbegin(), en = urls.cend(); i != en; ++i) {
if (!i->isLocalFile()) return QStringList();
QString file(i->toLocalFile());
if (file.startsWith(qsl("/.file/id="))) file = psConvertFileUrl(file);
auto file = psConvertFileUrl(*i);
QFileInfo info(file);
uint64 s = info.size();

View File

@ -216,9 +216,8 @@ void Gif::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
if (_delete && p == _delete) {
bool wasactive = (_state & StateFlag::DeleteOver);
if (active != wasactive) {
float64 from = active ? 0 : 1, to = active ? 1 : 0;
EnsureAnimation(_a_deleteOver, from, func(this, &Gif::update));
_a_deleteOver.start(to, st::stickersRowDuration);
auto from = active ? 0. : 1., to = active ? 1. : 0.;
START_ANIMATION(_a_deleteOver, func(this, &Gif::update), from, to, st::stickersRowDuration, anim::linear);
if (active) {
_state |= StateFlag::DeleteOver;
} else {
@ -231,9 +230,8 @@ void Gif::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
if (active != wasactive) {
if (!getShownDocument()->loaded()) {
ensureAnimation();
float64 from = active ? 0 : 1, to = active ? 1 : 0;
EnsureAnimation(_animation->_a_over, from, func(this, &Gif::update));
_animation->_a_over.start(to, st::stickersRowDuration);
auto from = active ? 0. : 1., to = active ? 1. : 0.;
START_ANIMATION(_animation->_a_over, func(this, &Gif::update), from, to, st::stickersRowDuration, anim::linear);
}
if (active) {
_state |= StateFlag::Over;
@ -413,9 +411,8 @@ void Sticker::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
if (active != _active) {
_active = active;
float64 from = _active ? 0 : 1, to = _active ? 1 : 0;
EnsureAnimation(_a_over, from, func(this, &Sticker::update));
_a_over.start(to, st::stickersRowDuration);
auto from = active ? 0. : 1., to = active ? 1. : 0.;
START_ANIMATION(_a_over, func(this, &Sticker::update), from, to, st::stickersRowDuration, anim::linear);
}
}
ItemBase::clickHandlerActiveChanged(p, active);

View File

@ -36,6 +36,7 @@ profileTopBarBackPosition: point(32px, 17px);
profileMarginTop: 13px;
profilePhotoSize: 112px;
profilePhotoLeft: 35px;
profilePhotoDuration: 500;
profileNameLeft: 26px;
profileNameTop: 9px;
profileNameLabel: flatLabel(labelDefFlat) {
@ -78,13 +79,14 @@ profileSecondaryButton: BoxButton(profilePrimaryButton) {
profileDropAreaBg: profileBg;
profileDropAreaFg: #3fb0e4;
profileDropAreaPadding: margins(30px, 20px, 30px, 20px);
profileDropAreaPadding: margins(25px, 3px, 25px, 20px);
profileDropAreaTitleFont: font(24px);
profileDropAreaTitleTop: 36px;
profileDropAreaTitleTop: 30px;
profileDropAreaSubtitleFont: font(16px);
profileDropAreaSubtitleTop: 72px;
profileDropAreaSubtitleTop: 68px;
profileDropAreaBorderFg: profileDropAreaFg;
profileDropAreaBorderWidth: 3px;
profileDropAreaDuration: 200;
profileDividerFg: black;
profileDividerLeft: icon {

View File

@ -22,6 +22,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "profile/profile_cover.h"
#include "styles/style_profile.h"
#include "profile/profile_cover_drop_area.h"
#include "profile/profile_userpic_button.h"
#include "ui/buttons/round_button.h"
#include "ui/filedialog.h"
#include "observer_peer.h"
@ -71,109 +73,13 @@ const Notify::PeerUpdateFlags ButtonsUpdateFlags = Notify::PeerUpdateFlag::UserC
} // namespace
class PhotoButton final : public Button, public Notify::Observer {
public:
PhotoButton(QWidget *parent, PeerData *peer) : Button(parent), _peer(peer) {
resize(st::profilePhotoSize, st::profilePhotoSize);
processNewPeerPhoto();
Notify::registerPeerObserver(Notify::PeerUpdateFlag::PhotoChanged, this, &PhotoButton::notifyPeerUpdated);
FileDownload::registerImageLoadedObserver(this, &PhotoButton::notifyImageLoaded);
}
protected:
void paintEvent(QPaintEvent *e) {
Painter p(this);
p.drawPixmap(0, 0, _userpic);
}
private:
void notifyPeerUpdated(const Notify::PeerUpdate &update) {
if (update.peer != _peer) {
return;
}
processNewPeerPhoto();
this->update();
}
void notifyImageLoaded() {
if (_waiting && _peer->userpicLoaded()) {
_waiting = false;
_userpic = _peer->genUserpic(st::profilePhotoSize);
update();
}
}
void processNewPeerPhoto() {
bool hasPhoto = (_peer->photoId && _peer->photoId != UnknownPeerPhotoId);
setCursor(hasPhoto ? style::cur_pointer : style::cur_default);
_waiting = !_peer->userpicLoaded();
if (_waiting) {
_peer->loadUserpic(true);
} else {
_userpic = _peer->genUserpic(st::profilePhotoSize);
}
}
PeerData *_peer;
bool _waiting = false;
QPixmap _userpic;
};
class DropArea : public TWidget {
public:
DropArea(QWidget *parent) : TWidget(parent) {
}
void showAnimated() {
show();
}
protected:
void paintEvent(QPaintEvent *e) override {
Painter p(this);
p.fillRect(e->rect(), st::profileDropAreaBg);
if (width() < st::profileDropAreaPadding.left() + st::profileDropAreaPadding.right()) return;
if (height() < st::profileDropAreaPadding.top() + st::profileDropAreaPadding.bottom()) return;
auto border = st::profileDropAreaBorderWidth;
auto &borderFg = st::profileDropAreaBorderFg;
auto inner = rect().marginsRemoved(st::profileDropAreaPadding);
p.fillRect(inner.x(), inner.y(), inner.width(), border, borderFg);
p.fillRect(inner.x(), inner.y() + inner.height() - border, inner.width(), border, borderFg);
p.fillRect(inner.x(), inner.y() + border, border, inner.height() - 2 * border, borderFg);
p.fillRect(inner.x() + inner.width() - border, inner.y() + border, border, inner.height() - 2 * border, borderFg);
auto title = lang(lng_profile_drop_area_title);
int titleWidth = st::profileDropAreaTitleFont->width(title);
int titleLeft = inner.x() + (inner.width() - titleWidth) / 2;
int titleTop = inner.y() + st::profileDropAreaTitleTop + st::profileDropAreaTitleFont->ascent;
p.setFont(st::profileDropAreaTitleFont);
p.setPen(st::profileDropAreaFg);
p.drawText(titleLeft, titleTop, title);
auto subtitle = lang(lng_profile_drop_area_subtitle);
int subtitleWidth = st::profileDropAreaSubtitleFont->width(subtitle);
int subtitleLeft = inner.x() + (inner.width() - subtitleWidth) / 2;
int subtitleTop = inner.y() + st::profileDropAreaSubtitleTop + st::profileDropAreaSubtitleFont->ascent;
p.setFont(st::profileDropAreaSubtitleFont);
p.setPen(st::profileDropAreaFg);
p.drawText(subtitleLeft, subtitleTop, subtitle);
}
};
CoverWidget::CoverWidget(QWidget *parent, PeerData *peer) : TWidget(parent)
, _peer(peer)
, _peerUser(peer->asUser())
, _peerChat(peer->asChat())
, _peerChannel(peer->asChannel())
, _peerMegagroup(peer->isMegagroup() ? _peerChannel : nullptr)
, _photoButton(this, peer)
, _userpicButton(this, peer)
, _name(this, QString(), st::profileNameLabel) {
setAttribute(Qt::WA_OpaquePaintEvent);
setAcceptDrops(true);
@ -185,7 +91,7 @@ CoverWidget::CoverWidget(QWidget *parent, PeerData *peer) : TWidget(parent)
Notify::registerPeerObserver(observeEvents, this, &CoverWidget::notifyPeerUpdated);
FileDialog::registerObserver(this, &CoverWidget::notifyFileQueryUpdated);
connect(_photoButton, SIGNAL(clicked()), this, SLOT(onPhotoShow()));
connect(_userpicButton, SIGNAL(clicked()), this, SLOT(onPhotoShow()));
refreshNameText();
refreshStatusText();
@ -207,19 +113,19 @@ void CoverWidget::resizeToWidth(int newWidth) {
int newHeight = 0;
newHeight += st::profileMarginTop;
_photoButton->moveToLeft(st::profilePhotoLeft, newHeight);
_userpicButton->moveToLeft(st::profilePhotoLeft, newHeight);
int infoLeft = _photoButton->x() + _photoButton->width();
int infoLeft = _userpicButton->x() + _userpicButton->width();
int nameLeft = infoLeft + st::profileNameLeft - st::profileNameLabel.margin.left();
int nameTop = _photoButton->y() + st::profileNameTop - st::profileNameLabel.margin.top();
int nameTop = _userpicButton->y() + st::profileNameTop - st::profileNameLabel.margin.top();
_name.moveToLeft(nameLeft, nameTop);
int nameWidth = width() - infoLeft - st::profileNameLeft - st::profileButtonSkip;
nameWidth += st::profileNameLabel.margin.left() + st::profileNameLabel.margin.right();
_name.resizeToWidth(nameWidth);
_statusPosition = QPoint(infoLeft + st::profileStatusLeft, _photoButton->y() + st::profileStatusTop);
_statusPosition = QPoint(infoLeft + st::profileStatusLeft, _userpicButton->y() + st::profileStatusTop);
int buttonLeft = st::profilePhotoLeft + _photoButton->width() + st::profileButtonLeft;
int buttonLeft = st::profilePhotoLeft + _userpicButton->width() + st::profileButtonLeft;
for_const (auto button, _buttons) {
button->moveToLeft(buttonLeft, st::profileButtonTop);
buttonLeft += button->width() + st::profileButtonSkip;
@ -233,10 +139,15 @@ void CoverWidget::resizeToWidth(int newWidth) {
newHeight += st::profileBlocksTop;
resizeDropArea();
resize(newWidth, newHeight);
update();
}
void CoverWidget::showFinished() {
_userpicButton->showFinished();
}
void CoverWidget::paintEvent(QPaintEvent *e) {
Painter p(this);
@ -249,21 +160,109 @@ void CoverWidget::paintEvent(QPaintEvent *e) {
paintDivider(p);
}
void CoverWidget::resizeDropArea() {
if (_dropArea) {
_dropArea->setGeometry(0, 0, width(), _dividerTop);
}
}
void CoverWidget::dropAreaHidden(CoverDropArea *dropArea) {
if (_dropArea == dropArea) {
_dropArea->deleteLater();
_dropArea = nullptr;
}
}
bool CoverWidget::canEditPhoto() const {
if (_peerChat && _peerChat->canEdit()) {
return true;
} else if (_peerMegagroup && _peerMegagroup->canEditPhoto()) {
return true;
} else if (_peerChannel && _peerChannel->canEditPhoto()) {
return true;
}
return false;
}
bool CoverWidget::mimeDataHasImage(const QMimeData *mimeData) const {
if (!mimeData) return false;
if (mimeData->hasImage()) return true;
auto uriListFormat = qsl("text/uri-list");
if (!mimeData->hasFormat(uriListFormat)) return false;
auto &urls = mimeData->urls();
if (urls.size() != 1) return false;
auto &url = urls.at(0);
if (!url.isLocalFile()) return false;
auto file = psConvertFileUrl(url);
QFileInfo info(file);
if (info.isDir()) return false;
quint64 s = info.size();
if (s >= MaxUploadDocumentSize) return false;
for (auto &ext : cImgExtensions()) {
if (file.endsWith(ext, Qt::CaseInsensitive)) {
return true;
}
}
return false;
}
void CoverWidget::dragEnterEvent(QDragEnterEvent *e) {
_dropArea = new DropArea(this);
_dropArea->setGeometry(0, 0, width(), _dividerTop);
if (!canEditPhoto() || !mimeDataHasImage(e->mimeData())) {
e->ignore();
return;
}
if (!_dropArea) {
auto title = lang(lng_profile_drop_area_title);
QString subtitle;
if (_peerChat || _peerMegagroup) {
subtitle = lang(lng_profile_drop_area_subtitle);
} else {
subtitle = lang(lng_profile_drop_area_subtitle_channel);
}
_dropArea = new CoverDropArea(this, title, subtitle);
resizeDropArea();
}
_dropArea->showAnimated();
e->setDropAction(Qt::CopyAction);
e->accept();
}
void CoverWidget::dragLeaveEvent(QDragLeaveEvent *e) {
delete _dropArea;
_dropArea = nullptr;
if (_dropArea && !_dropArea->hiding()) {
_dropArea->hideAnimated(func(this, &CoverWidget::dropAreaHidden));
}
}
void CoverWidget::dropEvent(QDropEvent *e) {
delete _dropArea;
_dropArea = nullptr;
auto mimeData = e->mimeData();
QImage img;
if (mimeData->hasImage()) {
img = qvariant_cast<QImage>(mimeData->imageData());
} else {
auto &urls = mimeData->urls();
if (urls.size() == 1) {
auto &url = urls.at(0);
if (url.isLocalFile()) {
img = App::readImage(psConvertFileUrl(url));
}
}
}
if (!_dropArea->hiding()) {
_dropArea->hideAnimated(func(this, &CoverWidget::dropAreaHidden));
}
e->acceptProposedAction();
showSetPhotoBox(img);
}
void CoverWidget::paintDivider(Painter &p) {
@ -439,6 +438,10 @@ void CoverWidget::notifyFileQueryUpdated(const FileDialog::QueryUpdate &update)
img = App::readImage(update.filePaths.front());
}
showSetPhotoBox(img);
}
void CoverWidget::showSetPhotoBox(const QImage &img) {
if (img.isNull() || img.width() > 10 * img.height() || img.height() > 10 * img.width()) {
Ui::showLayer(new InformBox(lang(lng_bad_photo)));
return;

View File

@ -35,8 +35,8 @@ struct PeerUpdate;
namespace Profile {
class BackButton;
class PhotoButton;
class DropArea;
class UserpicButton;
class CoverDropArea;
class CoverWidget final : public TWidget, public Notify::Observer {
Q_OBJECT
@ -47,6 +47,8 @@ public:
// Count new height for width=newWidth and resize to it.
void resizeToWidth(int newWidth);
void showFinished();
private slots:
void onPhotoShow();
@ -83,14 +85,20 @@ private:
void paintDivider(Painter &p);
bool canEditPhoto() const;
void showSetPhotoBox(const QImage &img);
void resizeDropArea();
void dropAreaHidden(CoverDropArea *dropArea);
bool mimeDataHasImage(const QMimeData *mimeData) const;
PeerData *_peer;
UserData *_peerUser;
ChatData *_peerChat;
ChannelData *_peerChannel;
ChannelData *_peerMegagroup;
ChildWidget<PhotoButton> _photoButton;
ChildWidget<DropArea> _dropArea = { nullptr };
ChildWidget<UserpicButton> _userpicButton;
ChildWidget<CoverDropArea> _dropArea = { nullptr };
FlatLabel _name;

View File

@ -0,0 +1,99 @@
/*
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 "profile/profile_cover_drop_area.h"
#include "styles/style_profile.h"
namespace Profile {
CoverDropArea::CoverDropArea(QWidget *parent, const QString &title, const QString &subtitle) : TWidget(parent)
, _title(title)
, _subtitle(subtitle)
, _titleWidth(st::profileDropAreaTitleFont->width(_title))
, _subtitleWidth(st::profileDropAreaSubtitleFont->width(_subtitle)) {
}
void CoverDropArea::showAnimated() {
show();
_hiding = false;
setupAnimation();
}
void CoverDropArea::hideAnimated(HideFinishCallback &&callback) {
_hideFinishCallback = std_::move(callback);
_hiding = true;
setupAnimation();
}
void CoverDropArea::paintEvent(QPaintEvent *e) {
Painter p(this);
if (_a_appearance.animating(getms())) {
p.setOpacity(_a_appearance.current());
p.drawPixmap(0, 0, _cache);
return;
}
if (!_cache.isNull()) {
_cache = QPixmap();
if (_hiding) {
hide();
_hideFinishCallback.call(this);
return;
}
}
p.fillRect(e->rect(), st::profileDropAreaBg);
if (width() < st::profileDropAreaPadding.left() + st::profileDropAreaPadding.right()) return;
if (height() < st::profileDropAreaPadding.top() + st::profileDropAreaPadding.bottom()) return;
auto border = st::profileDropAreaBorderWidth;
auto &borderFg = st::profileDropAreaBorderFg;
auto inner = rect().marginsRemoved(st::profileDropAreaPadding);
p.fillRect(inner.x(), inner.y(), inner.width(), border, borderFg);
p.fillRect(inner.x(), inner.y() + inner.height() - border, inner.width(), border, borderFg);
p.fillRect(inner.x(), inner.y() + border, border, inner.height() - 2 * border, borderFg);
p.fillRect(inner.x() + inner.width() - border, inner.y() + border, border, inner.height() - 2 * border, borderFg);
int titleLeft = inner.x() + (inner.width() - _titleWidth) / 2;
int titleTop = inner.y() + st::profileDropAreaTitleTop + st::profileDropAreaTitleFont->ascent;
p.setFont(st::profileDropAreaTitleFont);
p.setPen(st::profileDropAreaFg);
p.drawText(titleLeft, titleTop, _title);
int subtitleLeft = inner.x() + (inner.width() - _subtitleWidth) / 2;
int subtitleTop = inner.y() + st::profileDropAreaSubtitleTop + st::profileDropAreaSubtitleFont->ascent;
p.setFont(st::profileDropAreaSubtitleFont);
p.setPen(st::profileDropAreaFg);
p.drawText(subtitleLeft, subtitleTop, _subtitle);
}
void CoverDropArea::setupAnimation() {
if (_cache.isNull()) {
_cache = myGrab(this);
}
auto from = _hiding ? 1. : 0., to = _hiding ? 0. : 1.;
START_ANIMATION(_a_appearance, func(this, &CoverDropArea::refreshCallback), from, to, st::profileDropAreaDuration, anim::linear);
}
} // namespace Profile

View File

@ -0,0 +1,57 @@
/*
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
namespace Profile {
class CoverDropArea : public TWidget {
public:
CoverDropArea(QWidget *parent, const QString &title, const QString &subtitle);
void showAnimated();
using HideFinishCallback = Function<void, CoverDropArea*>;
void hideAnimated(HideFinishCallback &&callback);
bool hiding() const {
return _hiding;
}
protected:
void paintEvent(QPaintEvent *e) override;
private:
void refreshCallback() {
update();
}
void setupAnimation();
QString _title, _subtitle;
int _titleWidth, _subtitleWidth;
QPixmap _cache;
FloatAnimation _a_appearance;
bool _hiding = false;
HideFinishCallback _hideFinishCallback;
};
} // namespace Profile

View File

@ -97,7 +97,6 @@ void FixedBar::onDeleteContact() {
}
void FixedBar::resizeToWidth(int newWidth) {
int newHeight = 0;

View File

@ -56,6 +56,10 @@ void InnerWidget::setVisibleTopBottom(int visibleTop, int visibleBottom) {
}
}
void InnerWidget::showFinished() {
_cover->showFinished();
}
void InnerWidget::decreaseAdditionalHeight(int removeHeight) {
resizeToWidth(width(), height() - removeHeight);
}

View File

@ -41,6 +41,8 @@ public:
// Updates the area that is visible inside the scroll container.
void setVisibleTopBottom(int visibleTop, int visibleBottom);
void showFinished();
signals:
void cancelled();

View File

@ -0,0 +1,117 @@
/*
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 "profile/profile_userpic_button.h"
#include "styles/style_profile.h"
#include "observer_peer.h"
#include "mtproto/file_download.h"
namespace Profile {
UserpicButton::UserpicButton(QWidget *parent, PeerData *peer) : Button(parent), _peer(peer) {
resize(st::profilePhotoSize, st::profilePhotoSize);
processPeerPhoto();
_notShownYet = _waiting;
if (!_waiting) {
_userpic = prepareUserpicPixmap();
}
Notify::registerPeerObserver(Notify::PeerUpdateFlag::PhotoChanged, this, &UserpicButton::notifyPeerUpdated);
FileDownload::registerImageLoadedObserver(this, &UserpicButton::notifyImageLoaded);
}
void UserpicButton::showFinished() {
if (_notShownYet && !_waiting) {
_notShownYet = false;
_a_appearance.finish();
START_ANIMATION(_a_appearance, func(this, &UserpicButton::refreshCallback), 0, 1, st::profilePhotoDuration, anim::linear);
}
}
void UserpicButton::paintEvent(QPaintEvent *e) {
Painter p(this);
if (_a_appearance.animating(getms())) {
p.drawPixmap(0, 0, _oldUserpic);
p.setOpacity(_a_appearance.current());
}
p.drawPixmap(0, 0, _userpic);
}
void UserpicButton::notifyPeerUpdated(const Notify::PeerUpdate &update) {
if (update.peer != _peer) {
return;
}
processNewPeerPhoto();
this->update();
}
void UserpicButton::notifyImageLoaded() {
if (_waiting && _peer->userpicLoaded()) {
_waiting = false;
startNewPhotoShowing();
}
}
void UserpicButton::processPeerPhoto() {
bool hasPhoto = (_peer->photoId && _peer->photoId != UnknownPeerPhotoId);
setCursor(hasPhoto ? style::cur_pointer : style::cur_default);
_waiting = !_peer->userpicLoaded();
if (_waiting) {
_peer->loadUserpic(true);
}
}
void UserpicButton::processNewPeerPhoto() {
processPeerPhoto();
if (!_waiting) {
startNewPhotoShowing();
}
}
void UserpicButton::startNewPhotoShowing() {
_oldUserpic = myGrab(this);
_userpic = prepareUserpicPixmap();
if (_notShownYet) {
return;
}
_a_appearance.finish();
START_ANIMATION(_a_appearance, func(this, &UserpicButton::refreshCallback), 0, 1, st::profilePhotoDuration, anim::linear);
update();
}
QPixmap UserpicButton::prepareUserpicPixmap() const {
auto retina = cIntRetinaFactor();
auto size = st::profilePhotoSize * retina;
QImage image(size, size, QImage::Format_ARGB32_Premultiplied);
{
Painter p(&image);
p.fillRect(0, 0, size, size, st::profileBg);
_peer->paintUserpic(p, st::profilePhotoSize, 0, 0);
}
return App::pixmapFromImageInPlace(std_::move(image));
}
} // namespace Profile

View File

@ -0,0 +1,64 @@
/*
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 "core/observer.h"
namespace Notify {
struct PeerUpdate;
} // namespace Notify
namespace Profile {
class UserpicButton final : public Button, public Notify::Observer {
public:
UserpicButton(QWidget *parent, PeerData *peer);
// If at the first moment the _userpic was not loaded,
// we need to show it animated after the profile is fully shown.
void showFinished();
protected:
void paintEvent(QPaintEvent *e);
private:
void notifyPeerUpdated(const Notify::PeerUpdate &update);
void notifyImageLoaded();
void refreshCallback() {
update();
}
void processPeerPhoto();
void processNewPeerPhoto();
void startNewPhotoShowing();
QPixmap prepareUserpicPixmap() const;
bool _notShownYet;
PeerData *_peer;
bool _waiting = false;
QPixmap _userpic, _oldUserpic;
FloatAnimation _a_appearance;
};
} // namespace Profile

View File

@ -130,6 +130,7 @@ void Widget::showAnimatedHook() {
void Widget::showFinishedHook() {
_fixedBar->setAnimatingMode(false);
_inner->showFinished();
}
} // namespace Profile

View File

@ -160,8 +160,8 @@ QAbstractNativeEventFilter *psNativeEventFilter();
void psNewVersion();
void psUpdateOverlayed(QWidget *widget);
inline QString psConvertFileUrl(const QString &url) {
return url;
inline QString psConvertFileUrl(const QUrl &url) {
return url.toLocalFile();
}
inline QByteArray psDownloadPathBookmark(const QString &path) {
return QByteArray();

View File

@ -898,8 +898,12 @@ void psSendToMenu(bool send, bool silent) {
void psUpdateOverlayed(QWidget *widget) {
}
QString psConvertFileUrl(const QString &url) {
return objc_convertFileUrl(url);
QString psConvertFileUrl(const QUrl &url) {
auto urlString = url.toLocalFile();
if (urlString.startsWith(qsl("/.file/id="))) {
return objc_convertFileUrl(urlString);
}
return urlString;
}
void psDownloadPathEnableAccess() {

View File

@ -187,7 +187,7 @@ QAbstractNativeEventFilter *psNativeEventFilter();
void psNewVersion();
void psUpdateOverlayed(QWidget *widget);
QString psConvertFileUrl(const QString &url);
QString psConvertFileUrl(const QUrl &url);
void psDownloadPathEnableAccess();
QByteArray psDownloadPathBookmark(const QString &path);

View File

@ -164,8 +164,8 @@ QAbstractNativeEventFilter *psNativeEventFilter();
void psNewVersion();
void psUpdateOverlayed(TWidget *widget);
inline QString psConvertFileUrl(const QString &url) {
return url;
inline QString psConvertFileUrl(const QUrl &url) {
return url.toLocalFile();
}
inline QByteArray psDownloadPathBookmark(const QString &path) {
return QByteArray();

View File

@ -161,8 +161,8 @@ QAbstractNativeEventFilter *psNativeEventFilter();
void psNewVersion();
void psUpdateOverlayed(TWidget *widget);
inline QString psConvertFileUrl(const QString &url) {
return url;
inline QString psConvertFileUrl(const QUrl &url) {
return url.toLocalFile();
}
inline QByteArray psDownloadPathBookmark(const QString &path) {
return QByteArray();

View File

@ -362,7 +362,6 @@ AnimationCallbacks animation(Param param, Type *obj, typename AnimationCallbacks
template <typename AnimType>
class SimpleAnimation {
public:
using Callback = Function<void>;
SimpleAnimation() {
@ -464,7 +463,16 @@ using FloatAnimation = SimpleAnimation<anim::fvalue>;
using IntAnimation = SimpleAnimation<anim::ivalue>;
using ColorAnimation = SimpleAnimation<anim::cvalue>;
#define EnsureAnimation(animation, from, callback) if ((animation).isNull()) { (animation).setup((from), (callback)); }
// Macro allows us to lazily create updateCallback.
#define ENSURE_ANIMATION(animation, updateCallback, from) \
if ((animation).isNull()) { \
(animation).setup((from), (updateCallback)); \
}
#define START_ANIMATION(animation, updateCallback, from, to, duration, transition) \
ENSURE_ANIMATION(animation, updateCallback, from); \
(animation).start((to), (duration), (transition))
class ClipReader;

View File

@ -335,10 +335,10 @@ void EmojiButton::paintEvent(QPaintEvent *e) {
void EmojiButton::setLoading(bool loading) {
if (_loading != loading) {
EnsureAnimation(a_loading, _loading ? 1. : 0., func(this, &EmojiButton::updateCallback));
a_loading.start(loading ? 1. : 0., st::emojiCircleDuration);
_loading = loading;
if (_loading) {
auto from = loading ? 0. : 1., to = loading ? 1. : 0.;
START_ANIMATION(a_loading, func(this, &EmojiButton::updateCallback), from, to, st::emojiCircleDuration, anim::linear);
if (loading) {
_a_loading.start();
} else {
_a_loading.stop();

View File

@ -1215,9 +1215,11 @@
<ClCompile Include="SourceFiles\profilewidget.cpp" />
<ClCompile Include="SourceFiles\profile\profile_block_widget.cpp" />
<ClCompile Include="SourceFiles\profile\profile_cover.cpp" />
<ClCompile Include="SourceFiles\profile\profile_cover_drop_area.cpp" />
<ClCompile Include="SourceFiles\profile\profile_fixed_bar.cpp" />
<ClCompile Include="SourceFiles\profile\profile_inner_widget.cpp" />
<ClCompile Include="SourceFiles\profile\profile_section_memento.cpp" />
<ClCompile Include="SourceFiles\profile\profile_userpic_button.cpp" />
<ClCompile Include="SourceFiles\profile\profile_widget.cpp" />
<ClCompile Include="SourceFiles\pspecific_linux.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
@ -1560,7 +1562,9 @@
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/profile/profile_block_widget.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include"</Command>
</CustomBuild>
<ClInclude Include="SourceFiles\profile\profile_cover_drop_area.h" />
<ClInclude Include="SourceFiles\profile\profile_section_memento.h" />
<ClInclude Include="SourceFiles\profile\profile_userpic_button.h" />
<ClInclude Include="SourceFiles\serialize\serialize_common.h" />
<ClInclude Include="SourceFiles\serialize\serialize_document.h" />
<ClInclude Include="SourceFiles\shortcuts.h" />

View File

@ -1209,6 +1209,12 @@
<ClCompile Include="SourceFiles\observer_peer.cpp">
<Filter>SourceFiles</Filter>
</ClCompile>
<ClCompile Include="SourceFiles\profile\profile_cover_drop_area.cpp">
<Filter>SourceFiles\profile</Filter>
</ClCompile>
<ClCompile Include="SourceFiles\profile\profile_userpic_button.cpp">
<Filter>SourceFiles\profile</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="SourceFiles\stdafx.h">
@ -1406,6 +1412,12 @@
<ClInclude Include="SourceFiles\observer_peer.h">
<Filter>SourceFiles</Filter>
</ClInclude>
<ClInclude Include="SourceFiles\profile\profile_cover_drop_area.h">
<Filter>SourceFiles\profile</Filter>
</ClInclude>
<ClInclude Include="SourceFiles\profile\profile_userpic_button.h">
<Filter>SourceFiles\profile</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<CustomBuild Include="SourceFiles\application.h">