894 lines
23 KiB
C++
894 lines
23 KiB
C++
/*
|
|
This file is part of Telegram Desktop,
|
|
the official desktop application for the Telegram messaging service.
|
|
|
|
For license and copyright information please follow this link:
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|
*/
|
|
#include "media/view/media_view_group_thumbs.h"
|
|
|
|
#include "data/data_shared_media.h"
|
|
#include "data/data_user_photos.h"
|
|
#include "data/data_photo.h"
|
|
#include "data/data_photo_media.h"
|
|
#include "data/data_document.h"
|
|
#include "data/data_document_media.h"
|
|
#include "data/data_media_types.h"
|
|
#include "data/data_session.h"
|
|
#include "data/data_web_page.h"
|
|
#include "data/data_file_origin.h"
|
|
#include "history/history.h"
|
|
#include "history/view/media/history_view_media.h"
|
|
#include "ui/image/image.h"
|
|
#include "ui/ui_utility.h"
|
|
#include "main/main_session.h"
|
|
#include "core/crash_reports.h"
|
|
#include "styles/style_media_view.h"
|
|
|
|
namespace Media {
|
|
namespace View {
|
|
namespace {
|
|
|
|
constexpr auto kThumbDuration = crl::time(150);
|
|
|
|
int Round(float64 value) {
|
|
return int(base::SafeRound(value));
|
|
}
|
|
|
|
using Context = GroupThumbs::Context;
|
|
using Key = GroupThumbs::Key;
|
|
|
|
#if 0
|
|
[[nodiscard]] QString DebugSerializeMsgId(FullMsgId itemId) {
|
|
return QString("msg%1_%2").arg(itemId.channel.bare).arg(itemId.msg);
|
|
}
|
|
|
|
[[nodiscard]] QString DebugSerializePeer(PeerId peerId) {
|
|
return peerIsUser(peerId)
|
|
? QString("user%1").arg(peerToUser(peerId).bare)
|
|
: peerIsChat(peerId)
|
|
? QString("chat%1").arg(peerToChat(peerId).bare)
|
|
: QString("channel%1").arg(peerToChannel(peerId).bare);
|
|
}
|
|
|
|
[[nodiscard]] QString DebugSerializeKey(const Key &key) {
|
|
return v::match(key, [&](PhotoId photoId) {
|
|
return QString("photo%1").arg(photoId);
|
|
}, [](FullMsgId itemId) {
|
|
return DebugSerializeMsgId(itemId);
|
|
}, [&](GroupThumbs::CollageKey key) {
|
|
return QString("collage%1").arg(key.index);
|
|
});
|
|
}
|
|
|
|
[[nodiscard]] QString DebugSerializeContext(const Context &context) {
|
|
return v::match(context, [](PeerId peerId) {
|
|
return DebugSerializePeer(peerId);
|
|
}, [](MessageGroupId groupId) {
|
|
return QString("group_%1_%2"
|
|
).arg(DebugSerializePeer(groupId.peer)
|
|
).arg(groupId.value);
|
|
}, [](FullMsgId item) {
|
|
return DebugSerializeMsgId(item);
|
|
}, [](v::null_t) -> QString {
|
|
return "null";
|
|
});
|
|
}
|
|
#endif
|
|
|
|
Data::FileOrigin ComputeFileOrigin(const Key &key, const Context &context) {
|
|
return v::match(key, [&](PhotoId photoId) {
|
|
return v::match(context, [&](PeerId peerId) {
|
|
return peerIsUser(peerId)
|
|
? Data::FileOriginUserPhoto(peerToUser(peerId), photoId)
|
|
: Data::FileOrigin(Data::FileOriginPeerPhoto(peerId));
|
|
}, [](auto&&) {
|
|
return Data::FileOrigin();
|
|
});
|
|
}, [](FullMsgId itemId) {
|
|
return Data::FileOrigin(itemId);
|
|
}, [&](GroupThumbs::CollageKey) {
|
|
return v::match(context, [](const GroupThumbs::CollageSlice &slice) {
|
|
return Data::FileOrigin(slice.context);
|
|
}, [](auto&&) {
|
|
return Data::FileOrigin();
|
|
});
|
|
});
|
|
}
|
|
|
|
Context ComputeContext(
|
|
not_null<Main::Session*> session,
|
|
const SharedMediaWithLastSlice &slice,
|
|
int index) {
|
|
Expects(index >= 0 && index < slice.size());
|
|
|
|
const auto value = slice[index];
|
|
if (const auto photo = std::get_if<not_null<PhotoData*>>(&value)) {
|
|
if (const auto peer = (*photo)->peer) {
|
|
return peer->id;
|
|
}
|
|
return v::null;
|
|
} else if (const auto msgId = std::get_if<FullMsgId>(&value)) {
|
|
if (const auto item = session->data().message(*msgId)) {
|
|
if (item->isService()) {
|
|
return item->history()->peer->id;
|
|
} else if (const auto groupId = item->groupId()) {
|
|
return groupId;
|
|
}
|
|
}
|
|
return v::null;
|
|
}
|
|
Unexpected("Variant in ComputeContext(SharedMediaWithLastSlice::Value)");
|
|
}
|
|
|
|
Context ComputeContext(
|
|
not_null<Main::Session*> session,
|
|
const UserPhotosSlice &slice,
|
|
int index) {
|
|
return peerFromUser(slice.key().userId);
|
|
}
|
|
|
|
Context ComputeContext(
|
|
not_null<Main::Session*> session,
|
|
const GroupThumbs::CollageSlice &slice,
|
|
int index) {
|
|
return slice.context;
|
|
}
|
|
|
|
Key ComputeKey(const SharedMediaWithLastSlice &slice, int index) {
|
|
Expects(index >= 0 && index < slice.size());
|
|
|
|
const auto value = slice[index];
|
|
if (const auto photo = std::get_if<not_null<PhotoData*>>(&value)) {
|
|
return (*photo)->id;
|
|
} else if (const auto msgId = std::get_if<FullMsgId>(&value)) {
|
|
return *msgId;
|
|
}
|
|
Unexpected("Variant in ComputeKey(SharedMediaWithLastSlice::Value)");
|
|
}
|
|
|
|
Key ComputeKey(const UserPhotosSlice &slice, int index) {
|
|
return slice[index];
|
|
}
|
|
|
|
Key ComputeKey(const GroupThumbs::CollageSlice &slice, int index) {
|
|
return GroupThumbs::CollageKey{ index };
|
|
}
|
|
|
|
int ComputeThumbsLimit(int availableWidth) {
|
|
const auto singleWidth = st::mediaviewGroupWidth
|
|
+ 2 * st::mediaviewGroupSkip;
|
|
const auto currentWidth = st::mediaviewGroupWidthMax
|
|
+ 2 * st::mediaviewGroupSkipCurrent;
|
|
const auto skipForAnimation = 2 * singleWidth;
|
|
const auto leftWidth = availableWidth
|
|
- currentWidth
|
|
- skipForAnimation;
|
|
return std::max(leftWidth / (2 * singleWidth), 1);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
class GroupThumbs::Thumb {
|
|
public:
|
|
enum class State {
|
|
Unknown,
|
|
Current,
|
|
Alive,
|
|
Dying,
|
|
};
|
|
|
|
Thumb(Key key, Fn<void()> handler);
|
|
Thumb(
|
|
Key key,
|
|
not_null<PhotoData*> photo,
|
|
Data::FileOrigin origin,
|
|
Fn<void()> handler);
|
|
Thumb(
|
|
Key key,
|
|
not_null<DocumentData*> document,
|
|
Data::FileOrigin origin,
|
|
Fn<void()> handler);
|
|
|
|
[[nodiscard]] int leftToUpdate() const;
|
|
[[nodiscard]] int rightToUpdate() const;
|
|
|
|
void animateToLeft(not_null<Thumb*> next);
|
|
void animateToRight(not_null<Thumb*> prev);
|
|
|
|
void setState(State state);
|
|
[[nodiscard]] State state() const;
|
|
[[nodiscard]] bool inited() const;
|
|
[[nodiscard]] bool removed() const;
|
|
|
|
void paint(QPainter &p, int x, int y, int outerWidth, float64 progress);
|
|
[[nodiscard]] ClickHandlerPtr getState(QPoint point) const;
|
|
|
|
private:
|
|
QSize wantedPixSize() const;
|
|
void validateImage();
|
|
int currentLeft() const;
|
|
int currentWidth() const;
|
|
int finalLeft() const;
|
|
int finalWidth() const;
|
|
void animateTo(int left, int width);
|
|
|
|
ClickHandlerPtr _link;
|
|
const Key _key;
|
|
std::shared_ptr<Data::DocumentMedia> _documentMedia;
|
|
std::shared_ptr<Data::PhotoMedia> _photoMedia;
|
|
Image *_image = nullptr;
|
|
Data::FileOrigin _origin;
|
|
State _state = State::Alive;
|
|
QPixmap _full;
|
|
int _fullWidth = 0;
|
|
bool _hiding = false;
|
|
|
|
anim::value _left = { 0. };
|
|
anim::value _width = { 0. };
|
|
anim::value _opacity = { 0., 1. };
|
|
|
|
};
|
|
|
|
GroupThumbs::Thumb::Thumb(Key key, Fn<void()> handler)
|
|
: _key(key) {
|
|
_link = std::make_shared<LambdaClickHandler>(std::move(handler));
|
|
validateImage();
|
|
}
|
|
|
|
GroupThumbs::Thumb::Thumb(
|
|
Key key,
|
|
not_null<PhotoData*> photo,
|
|
Data::FileOrigin origin,
|
|
Fn<void()> handler)
|
|
: _key(key)
|
|
, _photoMedia(photo->createMediaView())
|
|
, _origin(origin) {
|
|
_link = std::make_shared<LambdaClickHandler>(std::move(handler));
|
|
_photoMedia->wanted(Data::PhotoSize::Thumbnail, origin);
|
|
validateImage();
|
|
}
|
|
|
|
GroupThumbs::Thumb::Thumb(
|
|
Key key,
|
|
not_null<DocumentData*> document,
|
|
Data::FileOrigin origin,
|
|
Fn<void()> handler)
|
|
: _key(key)
|
|
, _documentMedia(document->createMediaView())
|
|
, _origin(origin) {
|
|
_link = std::make_shared<LambdaClickHandler>(std::move(handler));
|
|
_documentMedia->thumbnailWanted(origin);
|
|
validateImage();
|
|
}
|
|
|
|
QSize GroupThumbs::Thumb::wantedPixSize() const {
|
|
const auto originalWidth = _image ? std::max(_image->width(), 1) : 1;
|
|
const auto originalHeight = _image ? std::max(_image->height(), 1) : 1;
|
|
const auto pixHeight = st::mediaviewGroupHeight;
|
|
const auto pixWidth = originalWidth * pixHeight / originalHeight;
|
|
return { pixWidth, pixHeight };
|
|
}
|
|
|
|
void GroupThumbs::Thumb::validateImage() {
|
|
if (!_image) {
|
|
if (_photoMedia) {
|
|
_image = _photoMedia->image(Data::PhotoSize::Thumbnail);
|
|
} else if (_documentMedia) {
|
|
_image = _documentMedia->thumbnail();
|
|
}
|
|
}
|
|
if (!_full.isNull() || !_image) {
|
|
return;
|
|
}
|
|
|
|
const auto pixSize = wantedPixSize();
|
|
if (pixSize.width() > st::mediaviewGroupWidthMax) {
|
|
const auto originalWidth = _image->width();
|
|
const auto originalHeight = _image->height();
|
|
const auto takeWidth = originalWidth * st::mediaviewGroupWidthMax
|
|
/ pixSize.width();
|
|
auto original = _image->original();
|
|
original.setDevicePixelRatio(cRetinaFactor());
|
|
_full = Ui::PixmapFromImage(original.copy(
|
|
(originalWidth - takeWidth) / 2,
|
|
0,
|
|
takeWidth,
|
|
originalHeight
|
|
).scaled(
|
|
st::mediaviewGroupWidthMax * cIntRetinaFactor(),
|
|
pixSize.height() * cIntRetinaFactor(),
|
|
Qt::IgnoreAspectRatio,
|
|
Qt::SmoothTransformation));
|
|
} else {
|
|
_full = _image->pixNoCache(pixSize * style::DevicePixelRatio());
|
|
}
|
|
_fullWidth = std::min(
|
|
wantedPixSize().width(),
|
|
st::mediaviewGroupWidthMax);
|
|
}
|
|
|
|
int GroupThumbs::Thumb::leftToUpdate() const {
|
|
return Round(std::min(_left.from(), _left.to()));
|
|
}
|
|
|
|
int GroupThumbs::Thumb::rightToUpdate() const {
|
|
return Round(std::max(
|
|
_left.from() + _width.from(),
|
|
_left.to() + _width.to()));
|
|
}
|
|
|
|
int GroupThumbs::Thumb::currentLeft() const {
|
|
return Round(_left.current());
|
|
}
|
|
|
|
int GroupThumbs::Thumb::currentWidth() const {
|
|
return Round(_width.current());
|
|
}
|
|
|
|
int GroupThumbs::Thumb::finalLeft() const {
|
|
return Round(_left.to());
|
|
}
|
|
|
|
int GroupThumbs::Thumb::finalWidth() const {
|
|
return Round(_width.to());
|
|
}
|
|
|
|
void GroupThumbs::Thumb::setState(State state) {
|
|
const auto isNewThumb = (_state == State::Alive);
|
|
_state = state;
|
|
if (_state == State::Current) {
|
|
if (isNewThumb) {
|
|
_opacity = anim::value(1.);
|
|
_left = anim::value(-_fullWidth / 2);
|
|
_width = anim::value(_fullWidth);
|
|
} else {
|
|
_opacity.start(1.);
|
|
}
|
|
_hiding = false;
|
|
animateTo(-_fullWidth / 2, _fullWidth);
|
|
} else if (_state == State::Alive) {
|
|
_opacity.start(0.7);
|
|
_hiding = false;
|
|
} else if (_state == State::Dying) {
|
|
_opacity.start(0.);
|
|
_hiding = true;
|
|
_left.restart();
|
|
_width.restart();
|
|
}
|
|
}
|
|
|
|
void GroupThumbs::Thumb::animateTo(int left, int width) {
|
|
_left.start(left);
|
|
_width.start(width);
|
|
}
|
|
|
|
void GroupThumbs::Thumb::animateToLeft(not_null<Thumb*> next) {
|
|
const auto width = st::mediaviewGroupWidth;
|
|
if (_state == State::Alive) {
|
|
// New item animation, start exactly from the next, move only.
|
|
_left = anim::value(next->currentLeft() - width);
|
|
_width = anim::value(width);
|
|
} else if (_state == State::Unknown) {
|
|
// Existing item animation.
|
|
setState(State::Alive);
|
|
}
|
|
const auto skip1 = st::mediaviewGroupSkip;
|
|
const auto skip2 = (next->state() == State::Current)
|
|
? st::mediaviewGroupSkipCurrent
|
|
: st::mediaviewGroupSkip;
|
|
animateTo(next->finalLeft() - width - skip1 - skip2, width);
|
|
}
|
|
|
|
void GroupThumbs::Thumb::animateToRight(not_null<Thumb*> prev) {
|
|
const auto width = st::mediaviewGroupWidth;
|
|
if (_state == State::Alive) {
|
|
// New item animation, start exactly from the next, move only.
|
|
_left = anim::value(prev->currentLeft() + prev->currentWidth());
|
|
_width = anim::value(width);
|
|
} else if (_state == State::Unknown) {
|
|
// Existing item animation.
|
|
setState(State::Alive);
|
|
}
|
|
const auto skip1 = st::mediaviewGroupSkip;
|
|
const auto skip2 = (prev->state() == State::Current)
|
|
? st::mediaviewGroupSkipCurrent
|
|
: st::mediaviewGroupSkip;
|
|
animateTo(prev->finalLeft() + prev->finalWidth() + skip1 + skip2, width);
|
|
}
|
|
|
|
auto GroupThumbs::Thumb::state() const -> State {
|
|
return _state;
|
|
}
|
|
|
|
bool GroupThumbs::Thumb::inited() const {
|
|
return _fullWidth != 0;
|
|
}
|
|
|
|
bool GroupThumbs::Thumb::removed() const {
|
|
return (_state == State::Dying) && _hiding && !_opacity.current();
|
|
}
|
|
|
|
void GroupThumbs::Thumb::paint(
|
|
QPainter &p,
|
|
int x,
|
|
int y,
|
|
int outerWidth,
|
|
float64 progress) {
|
|
validateImage();
|
|
|
|
_opacity.update(progress, anim::linear);
|
|
_left.update(progress, anim::linear);
|
|
_width.update(progress, anim::linear);
|
|
|
|
const auto left = x + currentLeft();
|
|
const auto width = currentWidth();
|
|
const auto opacity = p.opacity();
|
|
p.setOpacity(_opacity.current() * opacity);
|
|
if (width == _fullWidth) {
|
|
p.drawPixmap(left, y, _full);
|
|
} else {
|
|
const auto takeWidth = width * cIntRetinaFactor();
|
|
const auto from = QRect(
|
|
(_full.width() - takeWidth) / 2,
|
|
0,
|
|
takeWidth,
|
|
_full.height());
|
|
const auto to = QRect(left, y, width, st::mediaviewGroupHeight);
|
|
p.drawPixmap(to, _full, from);
|
|
}
|
|
p.setOpacity(opacity);
|
|
}
|
|
|
|
ClickHandlerPtr GroupThumbs::Thumb::getState(QPoint point) const {
|
|
if (_state != State::Alive) {
|
|
return nullptr;
|
|
}
|
|
const auto left = finalLeft();
|
|
const auto width = finalWidth();
|
|
return QRect(left, 0, width, st::mediaviewGroupHeight).contains(point)
|
|
? _link
|
|
: nullptr;
|
|
}
|
|
|
|
int GroupThumbs::CollageSlice::size() const {
|
|
return data->items.size();
|
|
}
|
|
|
|
GroupThumbs::GroupThumbs(not_null<Main::Session*> session, Context context)
|
|
: _session(session)
|
|
, _context(context) {
|
|
}
|
|
|
|
void GroupThumbs::updateContext(Context context) {
|
|
if (_context != context) {
|
|
clear();
|
|
_context = context;
|
|
}
|
|
}
|
|
|
|
template <typename Slice>
|
|
void GroupThumbs::RefreshFromSlice(
|
|
not_null<Main::Session*> session,
|
|
std::unique_ptr<GroupThumbs> &instance,
|
|
const Slice &slice,
|
|
int index,
|
|
int availableWidth) {
|
|
const auto context = ComputeContext(session, slice, index);
|
|
if (instance) {
|
|
instance->updateContext(context);
|
|
}
|
|
if (v::is_null(context)) {
|
|
if (instance) {
|
|
instance->resizeToWidth(availableWidth);
|
|
}
|
|
return;
|
|
}
|
|
const auto limit = ComputeThumbsLimit(availableWidth);
|
|
const auto from = [&] {
|
|
const auto edge = std::max(index - limit, 0);
|
|
for (auto result = index; result != edge; --result) {
|
|
if (ComputeContext(session, slice, result - 1) != context) {
|
|
return result;
|
|
}
|
|
}
|
|
return edge;
|
|
}();
|
|
const auto till = [&] {
|
|
const auto edge = std::min(index + limit + 1, slice.size());
|
|
for (auto result = index + 1; result != edge; ++result) {
|
|
if (ComputeContext(session, slice, result) != context) {
|
|
return result;
|
|
}
|
|
}
|
|
return edge;
|
|
}();
|
|
if (from + 1 < till) {
|
|
if (!instance) {
|
|
instance = std::make_unique<GroupThumbs>(session, context);
|
|
}
|
|
instance->fillItems(slice, from, index, till);
|
|
instance->resizeToWidth(availableWidth);
|
|
} else if (instance) {
|
|
instance->clear();
|
|
instance->resizeToWidth(availableWidth);
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
template <typename Slice>
|
|
void ValidateSlice(
|
|
const Slice &slice,
|
|
const Context &context,
|
|
int from,
|
|
int index,
|
|
int till) {
|
|
auto keys = base::flat_set<Key>();
|
|
for (auto i = from; i != till; ++i) {
|
|
const auto key = ComputeKey(slice, i);
|
|
if (keys.contains(key)) {
|
|
// All items should be unique!
|
|
auto strings = QStringList();
|
|
strings.reserve(till - from);
|
|
for (auto i = from; i != till; ++i) {
|
|
strings.push_back(DebugSerializeKey(ComputeKey(slice, i)));
|
|
}
|
|
CrashReports::SetAnnotation(
|
|
"keys",
|
|
QString("%1:%2-(%3)-%4:"
|
|
).arg(DebugSerializeContext(context)
|
|
).arg(from
|
|
).arg(index
|
|
).arg(till) + strings.join(","));
|
|
if (Logs::DebugEnabled()) {
|
|
Unexpected("Bad slice in GroupThumbs.");
|
|
}
|
|
break;
|
|
} else {
|
|
keys.emplace(key);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
template <typename Slice>
|
|
void GroupThumbs::fillItems(
|
|
const Slice &slice,
|
|
int from,
|
|
int index,
|
|
int till) {
|
|
Expects(from <= index);
|
|
Expects(index < till);
|
|
Expects(from + 1 < till);
|
|
|
|
const auto current = (index - from);
|
|
const auto old = base::take(_items);
|
|
|
|
//ValidateSlice(slice, _context, from, index, till);
|
|
|
|
markCacheStale();
|
|
_items.reserve(till - from);
|
|
for (auto i = from; i != till; ++i) {
|
|
_items.push_back(validateCacheEntry(ComputeKey(slice, i)));
|
|
}
|
|
animateAliveItems(current);
|
|
fillDyingItems(old);
|
|
startDelayedAnimation();
|
|
}
|
|
|
|
void GroupThumbs::animateAliveItems(int current) {
|
|
Expects(current >= 0 && current < _items.size());
|
|
|
|
_items[current]->setState(Thumb::State::Current);
|
|
for (auto i = current; i != 0;) {
|
|
const auto prev = _items[i];
|
|
const auto item = _items[--i];
|
|
item->animateToLeft(prev);
|
|
}
|
|
for (auto i = current + 1; i != _items.size(); ++i) {
|
|
const auto prev = _items[i - 1];
|
|
const auto item = _items[i];
|
|
item->animateToRight(prev);
|
|
}
|
|
}
|
|
|
|
void GroupThumbs::fillDyingItems(const std::vector<not_null<Thumb*>> &old) {
|
|
//Expects(_cache.size() >= _items.size());
|
|
|
|
if (_cache.size() >= _items.size()) {
|
|
_dying.reserve(_cache.size() - _items.size());
|
|
}
|
|
animatePreviouslyAlive(old);
|
|
markRestAsDying();
|
|
}
|
|
|
|
void GroupThumbs::markRestAsDying() {
|
|
//Expects(_cache.size() >= _items.size());
|
|
|
|
if (_cache.size() >= _items.size()) {
|
|
_dying.reserve(_cache.size() - _items.size());
|
|
}
|
|
for (const auto &cacheItem : _cache) {
|
|
const auto &thumb = cacheItem.second;
|
|
const auto state = thumb->state();
|
|
if (state == Thumb::State::Unknown) {
|
|
markAsDying(thumb.get());
|
|
}
|
|
}
|
|
}
|
|
|
|
void GroupThumbs::markAsDying(not_null<Thumb*> thumb) {
|
|
thumb->setState(Thumb::State::Dying);
|
|
_dying.push_back(thumb.get());
|
|
}
|
|
|
|
void GroupThumbs::animatePreviouslyAlive(
|
|
const std::vector<not_null<Thumb*>> &old) {
|
|
auto toRight = false;
|
|
for (auto i = 0; i != old.size(); ++i) {
|
|
const auto item = old[i];
|
|
if (item->state() == Thumb::State::Unknown) {
|
|
if (toRight) {
|
|
markAsDying(item);
|
|
item->animateToRight(old[i - 1]);
|
|
}
|
|
} else if (!toRight) {
|
|
for (auto j = i; j != 0;) {
|
|
const auto next = old[j];
|
|
const auto prev = old[--j];
|
|
markAsDying(prev);
|
|
prev->animateToLeft(next);
|
|
}
|
|
toRight = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
auto GroupThumbs::createThumb(Key key)
|
|
-> std::unique_ptr<Thumb> {
|
|
if (const auto photoId = std::get_if<PhotoId>(&key)) {
|
|
const auto photo = _session->data().photo(*photoId);
|
|
return createThumb(key, photo);
|
|
} else if (const auto msgId = std::get_if<FullMsgId>(&key)) {
|
|
if (const auto item = _session->data().message(*msgId)) {
|
|
if (const auto media = item->media()) {
|
|
if (const auto photo = media->photo()) {
|
|
return createThumb(key, photo);
|
|
} else if (const auto document = media->document()) {
|
|
return createThumb(key, document);
|
|
}
|
|
}
|
|
}
|
|
return createThumb(key, nullptr);
|
|
} else if (const auto collageKey = std::get_if<CollageKey>(&key)) {
|
|
if (const auto itemId = std::get_if<FullMsgId>(&_context)) {
|
|
if (const auto item = _session->data().message(*itemId)) {
|
|
if (const auto media = item->media()) {
|
|
if (const auto page = media->webpage()) {
|
|
return createThumb(
|
|
key,
|
|
page->collage,
|
|
collageKey->index);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return createThumb(key, nullptr);
|
|
}
|
|
Unexpected("Value of Key in GroupThumbs::createThumb()");
|
|
}
|
|
|
|
auto GroupThumbs::createThumb(
|
|
Key key,
|
|
const WebPageCollage &collage,
|
|
int index)
|
|
-> std::unique_ptr<Thumb> {
|
|
if (index < 0 || index >= collage.items.size()) {
|
|
return createThumb(key, nullptr);
|
|
}
|
|
const auto &item = collage.items[index];
|
|
if (const auto photo = std::get_if<PhotoData*>(&item)) {
|
|
return createThumb(key, (*photo));
|
|
} else if (const auto document = std::get_if<DocumentData*>(&item)) {
|
|
return createThumb(key, (*document));
|
|
}
|
|
return createThumb(key, nullptr);
|
|
}
|
|
|
|
auto GroupThumbs::createThumb(Key key, std::nullptr_t)
|
|
-> std::unique_ptr<Thumb> {
|
|
const auto weak = base::make_weak(this);
|
|
const auto origin = ComputeFileOrigin(key, _context);
|
|
return std::make_unique<Thumb>(key, [=] {
|
|
if (const auto strong = weak.get()) {
|
|
strong->_activateStream.fire_copy(key);
|
|
}
|
|
});
|
|
}
|
|
|
|
auto GroupThumbs::createThumb(Key key, not_null<PhotoData*> photo)
|
|
-> std::unique_ptr<Thumb> {
|
|
const auto weak = base::make_weak(this);
|
|
const auto origin = ComputeFileOrigin(key, _context);
|
|
return std::make_unique<Thumb>(key, photo, origin, [=] {
|
|
if (const auto strong = weak.get()) {
|
|
strong->_activateStream.fire_copy(key);
|
|
}
|
|
});
|
|
}
|
|
|
|
auto GroupThumbs::createThumb(Key key, not_null<DocumentData*> document)
|
|
-> std::unique_ptr<Thumb> {
|
|
const auto weak = base::make_weak(this);
|
|
const auto origin = ComputeFileOrigin(key, _context);
|
|
return std::make_unique<Thumb>(key, document, origin, [=] {
|
|
if (const auto strong = weak.get()) {
|
|
strong->_activateStream.fire_copy(key);
|
|
}
|
|
});
|
|
}
|
|
|
|
auto GroupThumbs::validateCacheEntry(Key key) -> not_null<Thumb*> {
|
|
const auto i = _cache.find(key);
|
|
return (i != _cache.end())
|
|
? i->second.get()
|
|
: _cache.emplace(key, createThumb(key)).first->second.get();
|
|
}
|
|
|
|
void GroupThumbs::markCacheStale() {
|
|
_dying.clear();
|
|
for (const auto &cacheItem : _cache) {
|
|
const auto &thumb = cacheItem.second;
|
|
thumb->setState(Thumb::State::Unknown);
|
|
}
|
|
}
|
|
|
|
void GroupThumbs::Refresh(
|
|
not_null<Main::Session*> session,
|
|
std::unique_ptr<GroupThumbs> &instance,
|
|
const SharedMediaWithLastSlice &slice,
|
|
int index,
|
|
int availableWidth) {
|
|
RefreshFromSlice(session, instance, slice, index, availableWidth);
|
|
}
|
|
|
|
void GroupThumbs::Refresh(
|
|
not_null<Main::Session*> session,
|
|
std::unique_ptr<GroupThumbs> &instance,
|
|
const UserPhotosSlice &slice,
|
|
int index,
|
|
int availableWidth) {
|
|
RefreshFromSlice(session, instance, slice, index, availableWidth);
|
|
}
|
|
|
|
void GroupThumbs::Refresh(
|
|
not_null<Main::Session*> session,
|
|
std::unique_ptr<GroupThumbs> &instance,
|
|
const CollageSlice &slice,
|
|
int index,
|
|
int availableWidth) {
|
|
RefreshFromSlice(session, instance, slice, index, availableWidth);
|
|
}
|
|
|
|
void GroupThumbs::clear() {
|
|
if (_items.empty()) {
|
|
return;
|
|
}
|
|
base::take(_items);
|
|
markCacheStale();
|
|
markRestAsDying();
|
|
startDelayedAnimation();
|
|
}
|
|
|
|
void GroupThumbs::startDelayedAnimation() {
|
|
_animation.stop();
|
|
_waitingForAnimationStart = true;
|
|
countUpdatedRect();
|
|
}
|
|
|
|
void GroupThumbs::resizeToWidth(int newWidth) {
|
|
_width = newWidth;
|
|
}
|
|
|
|
int GroupThumbs::height() const {
|
|
return st::mediaviewGroupPadding.top()
|
|
+ st::mediaviewGroupHeight
|
|
+ st::mediaviewGroupPadding.bottom();
|
|
}
|
|
|
|
bool GroupThumbs::hiding() const {
|
|
return _items.empty();
|
|
}
|
|
|
|
bool GroupThumbs::hidden() const {
|
|
return hiding() && !_waitingForAnimationStart && !_animation.animating();
|
|
}
|
|
|
|
void GroupThumbs::checkForAnimationStart() {
|
|
if (_waitingForAnimationStart) {
|
|
_waitingForAnimationStart = false;
|
|
_animation.start([=] { update(); }, 0., 1., kThumbDuration);
|
|
}
|
|
}
|
|
|
|
void GroupThumbs::update() {
|
|
if (_cache.empty()) {
|
|
return;
|
|
}
|
|
_updateRequests.fire_copy(_updatedRect);
|
|
}
|
|
|
|
void GroupThumbs::paint(QPainter &p, int x, int y, int outerWidth) {
|
|
const auto progress = _waitingForAnimationStart
|
|
? 0.
|
|
: _animation.value(1.);
|
|
x += (_width / 2);
|
|
y += st::mediaviewGroupPadding.top();
|
|
auto initedCurrentIndex = int(_items.size());
|
|
for (auto i = _cache.begin(); i != _cache.end();) {
|
|
const auto thumb = not_null{ i->second.get() };
|
|
const auto inited = thumb->inited();
|
|
thumb->paint(p, x, y, outerWidth, progress);
|
|
if (thumb->removed()) {
|
|
_dying.erase(ranges::remove(_dying, thumb), _dying.end());
|
|
i = _cache.erase(i);
|
|
} else {
|
|
if (!inited
|
|
&& thumb->inited()
|
|
&& thumb->state() == Thumb::State::Current) {
|
|
initedCurrentIndex = ranges::find(_items, thumb)
|
|
- begin(_items);
|
|
}
|
|
++i;
|
|
}
|
|
}
|
|
if (initedCurrentIndex < _items.size()) {
|
|
animateAliveItems(initedCurrentIndex);
|
|
}
|
|
}
|
|
|
|
ClickHandlerPtr GroupThumbs::getState(QPoint point) const {
|
|
point -= QPoint((_width / 2), st::mediaviewGroupPadding.top());
|
|
for (const auto &cacheItem : _cache) {
|
|
const auto &thumb = cacheItem.second;
|
|
if (auto link = thumb->getState(point)) {
|
|
return link;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void GroupThumbs::countUpdatedRect() {
|
|
if (_cache.empty()) {
|
|
return;
|
|
}
|
|
auto min = _width;
|
|
auto max = 0;
|
|
const auto left = [](const auto &cacheItem) {
|
|
const auto &[key, thumb] = cacheItem;
|
|
return thumb->leftToUpdate();
|
|
};
|
|
const auto right = [](const auto &cacheItem) {
|
|
const auto &[key, thumb] = cacheItem;
|
|
return thumb->rightToUpdate();
|
|
};
|
|
accumulate_min(min, left(*ranges::max_element(
|
|
_cache,
|
|
std::greater<>(),
|
|
left)));
|
|
accumulate_max(max, right(*ranges::max_element(
|
|
_cache,
|
|
std::less<>(),
|
|
right)));
|
|
_updatedRect = QRect(
|
|
min,
|
|
st::mediaviewGroupPadding.top(),
|
|
max - min,
|
|
st::mediaviewGroupHeight);
|
|
}
|
|
|
|
GroupThumbs::~GroupThumbs() = default;
|
|
|
|
} // namespace View
|
|
} // namespace Media
|