mirror of https://github.com/procxx/kepka.git
				
				
				
			
		
			
				
	
	
		
			732 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			732 lines
		
	
	
		
			18 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_document.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 "main/main_session.h"
 | 
						|
#include "app.h"
 | 
						|
#include "styles/style_mediaview.h"
 | 
						|
 | 
						|
namespace Media {
 | 
						|
namespace View {
 | 
						|
namespace {
 | 
						|
 | 
						|
constexpr auto kThumbDuration = crl::time(150);
 | 
						|
 | 
						|
int Round(float64 value) {
 | 
						|
	return int(std::round(value));
 | 
						|
}
 | 
						|
 | 
						|
using Context = GroupThumbs::Context;
 | 
						|
using Key = GroupThumbs::Key;
 | 
						|
 | 
						|
Data::FileOrigin ComputeFileOrigin(const Key &key, const Context &context) {
 | 
						|
	return key.match([&](PhotoId photoId) {
 | 
						|
		return context.match([&](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 context.match([](const GroupThumbs::CollageSlice &slice) {
 | 
						|
			return Data::FileOrigin(slice.context);
 | 
						|
		}, [](auto&&) {
 | 
						|
			return Data::FileOrigin();
 | 
						|
		});
 | 
						|
	});
 | 
						|
}
 | 
						|
 | 
						|
Context ComputeContext(const SharedMediaWithLastSlice &slice, int index) {
 | 
						|
	Expects(index >= 0 && index < slice.size());
 | 
						|
 | 
						|
	const auto value = slice[index];
 | 
						|
	if (const auto photo = base::get_if<not_null<PhotoData*>>(&value)) {
 | 
						|
		if (const auto peer = (*photo)->peer) {
 | 
						|
			return peer->id;
 | 
						|
		}
 | 
						|
		return std::nullopt;
 | 
						|
	} else if (const auto msgId = base::get_if<FullMsgId>(&value)) {
 | 
						|
		if (const auto item = Auth().data().message(*msgId)) {
 | 
						|
			if (!item->toHistoryMessage()) {
 | 
						|
				return item->history()->peer->id;
 | 
						|
			} else if (const auto groupId = item->groupId()) {
 | 
						|
				return groupId;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return std::nullopt;
 | 
						|
	}
 | 
						|
	Unexpected("Variant in ComputeContext(SharedMediaWithLastSlice::Value)");
 | 
						|
}
 | 
						|
 | 
						|
Context ComputeContext(const UserPhotosSlice &slice, int index) {
 | 
						|
	return peerFromUser(slice.key().userId);
 | 
						|
}
 | 
						|
 | 
						|
Context ComputeContext(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 = base::get_if<not_null<PhotoData*>>(&value)) {
 | 
						|
		return (*photo)->id;
 | 
						|
	} else if (const auto msgId = base::get_if<FullMsgId>(&value)) {
 | 
						|
		return *msgId;
 | 
						|
	}
 | 
						|
	Unexpected("Variant in ComputeContext(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,
 | 
						|
		Image *image,
 | 
						|
		Data::FileOrigin origin,
 | 
						|
		Fn<void()> handler);
 | 
						|
 | 
						|
	int leftToUpdate() const;
 | 
						|
	int rightToUpdate() const;
 | 
						|
 | 
						|
	void animateToLeft(not_null<Thumb*> next);
 | 
						|
	void animateToRight(not_null<Thumb*> prev);
 | 
						|
 | 
						|
	void setState(State state);
 | 
						|
	State state() const;
 | 
						|
	bool removed() const;
 | 
						|
 | 
						|
	void paint(Painter &p, int x, int y, int outerWidth, float64 progress);
 | 
						|
	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;
 | 
						|
	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,
 | 
						|
	Image *image,
 | 
						|
	Data::FileOrigin origin,
 | 
						|
	Fn<void()> handler)
 | 
						|
: _key(key)
 | 
						|
, _image(image)
 | 
						|
, _origin(origin) {
 | 
						|
	_link = std::make_shared<LambdaClickHandler>(std::move(handler));
 | 
						|
	_fullWidth = std::min(
 | 
						|
		wantedPixSize().width(),
 | 
						|
		st::mediaviewGroupWidthMax);
 | 
						|
	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 (!_full.isNull() || !_image) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	_image->load(_origin);
 | 
						|
	if (!_image->loaded()) {
 | 
						|
		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();
 | 
						|
		const auto original = _image->pixNoCache(_origin).toImage();
 | 
						|
		_full = App::pixmapFromImageInPlace(original.copy(
 | 
						|
			(originalWidth - takeWidth) / 2,
 | 
						|
			0,
 | 
						|
			takeWidth,
 | 
						|
			originalHeight
 | 
						|
		).scaled(
 | 
						|
			st::mediaviewGroupWidthMax * cIntRetinaFactor(),
 | 
						|
			pixSize.height() * cIntRetinaFactor(),
 | 
						|
			Qt::IgnoreAspectRatio,
 | 
						|
			Qt::SmoothTransformation));
 | 
						|
	} else {
 | 
						|
		_full = _image->pixNoCache(
 | 
						|
			_origin,
 | 
						|
			pixSize.width() * cIntRetinaFactor(),
 | 
						|
			pixSize.height() * cIntRetinaFactor(),
 | 
						|
			Images::Option::Smooth);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
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::removed() const {
 | 
						|
	return (_state == State::Dying) && _hiding && !_opacity.current();
 | 
						|
}
 | 
						|
 | 
						|
void GroupThumbs::Thumb::paint(
 | 
						|
		Painter &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(Context context)
 | 
						|
: _context(context) {
 | 
						|
}
 | 
						|
 | 
						|
void GroupThumbs::updateContext(Context context) {
 | 
						|
	if (_context != context) {
 | 
						|
		clear();
 | 
						|
		_context = context;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
template <typename Slice>
 | 
						|
void GroupThumbs::RefreshFromSlice(
 | 
						|
		std::unique_ptr<GroupThumbs> &instance,
 | 
						|
		const Slice &slice,
 | 
						|
		int index,
 | 
						|
		int availableWidth) {
 | 
						|
	const auto context = ComputeContext(slice, index);
 | 
						|
	if (instance) {
 | 
						|
		instance->updateContext(context);
 | 
						|
	}
 | 
						|
	if (!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(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(slice, result) != context) {
 | 
						|
				return result;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return edge;
 | 
						|
	}();
 | 
						|
	if (from + 1 < till) {
 | 
						|
		if (!instance) {
 | 
						|
			instance = std::make_unique<GroupThumbs>(context);
 | 
						|
		}
 | 
						|
		instance->fillItems(slice, from, index, till);
 | 
						|
		instance->resizeToWidth(availableWidth);
 | 
						|
	} else if (instance) {
 | 
						|
		instance->clear();
 | 
						|
		instance->resizeToWidth(availableWidth);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
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);
 | 
						|
 | 
						|
	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) {
 | 
						|
	_dying.reserve(_cache.size() - _items.size());
 | 
						|
	animatePreviouslyAlive(old);
 | 
						|
	markRestAsDying();
 | 
						|
}
 | 
						|
 | 
						|
void GroupThumbs::markRestAsDying() {
 | 
						|
	_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 = base::get_if<PhotoId>(&key)) {
 | 
						|
		const auto photo = Auth().data().photo(*photoId);
 | 
						|
		return createThumb(key, photo->thumbnail());
 | 
						|
	} else if (const auto msgId = base::get_if<FullMsgId>(&key)) {
 | 
						|
		if (const auto item = Auth().data().message(*msgId)) {
 | 
						|
			if (const auto media = item->media()) {
 | 
						|
				if (const auto photo = media->photo()) {
 | 
						|
					return createThumb(key, photo->thumbnail());
 | 
						|
				} else if (const auto document = media->document()) {
 | 
						|
					return createThumb(key, document->thumbnail());
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return createThumb(key, nullptr);
 | 
						|
	} else if (const auto collageKey = base::get_if<CollageKey>(&key)) {
 | 
						|
		if (const auto itemId = base::get_if<FullMsgId>(&_context)) {
 | 
						|
			if (const auto item = Auth().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 = base::get_if<PhotoData*>(&item)) {
 | 
						|
		return createThumb(key, (*photo)->thumbnail());
 | 
						|
	} else if (const auto document = base::get_if<DocumentData*>(&item)) {
 | 
						|
		return createThumb(key, (*document)->thumbnail());
 | 
						|
	}
 | 
						|
	return createThumb(key, nullptr);
 | 
						|
}
 | 
						|
 | 
						|
auto GroupThumbs::createThumb(Key key, Image *image)
 | 
						|
-> std::unique_ptr<Thumb> {
 | 
						|
	const auto weak = base::make_weak(this);
 | 
						|
	const auto origin = ComputeFileOrigin(key, _context);
 | 
						|
	return std::make_unique<Thumb>(key, image, 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(
 | 
						|
		std::unique_ptr<GroupThumbs> &instance,
 | 
						|
		const SharedMediaWithLastSlice &slice,
 | 
						|
		int index,
 | 
						|
		int availableWidth) {
 | 
						|
	RefreshFromSlice(instance, slice, index, availableWidth);
 | 
						|
}
 | 
						|
 | 
						|
void GroupThumbs::Refresh(
 | 
						|
		std::unique_ptr<GroupThumbs> &instance,
 | 
						|
		const UserPhotosSlice &slice,
 | 
						|
		int index,
 | 
						|
		int availableWidth) {
 | 
						|
	RefreshFromSlice(instance, slice, index, availableWidth);
 | 
						|
}
 | 
						|
 | 
						|
void GroupThumbs::Refresh(
 | 
						|
		std::unique_ptr<GroupThumbs> &instance,
 | 
						|
		const CollageSlice &slice,
 | 
						|
		int index,
 | 
						|
		int availableWidth) {
 | 
						|
	RefreshFromSlice(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(Painter &p, int x, int y, int outerWidth) {
 | 
						|
	const auto progress = _waitingForAnimationStart
 | 
						|
		? 0.
 | 
						|
		: _animation.value(1.);
 | 
						|
	x += (_width / 2);
 | 
						|
	y += st::mediaviewGroupPadding.top();
 | 
						|
	for (auto i = _cache.begin(); i != _cache.end();) {
 | 
						|
		const auto &thumb = i->second;
 | 
						|
		thumb->paint(p, x, y, outerWidth, progress);
 | 
						|
		if (thumb->removed()) {
 | 
						|
			_dying.erase(
 | 
						|
				ranges::remove(
 | 
						|
					_dying,
 | 
						|
					thumb.get(),
 | 
						|
					[](not_null<Thumb*> thumb) { return thumb.get(); }),
 | 
						|
				_dying.end());
 | 
						|
			i = _cache.erase(i);
 | 
						|
		} else {
 | 
						|
			++i;
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
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
 |