mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-05-04 15:03:59 +02:00
426 lines
11 KiB
C++
426 lines
11 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/stories/media_stories_sibling.h"
|
|
|
|
#include "base/weak_ptr.h"
|
|
#include "data/data_document.h"
|
|
#include "data/data_document_media.h"
|
|
#include "data/data_file_origin.h"
|
|
#include "data/data_peer.h"
|
|
#include "data/data_photo.h"
|
|
#include "data/data_photo_media.h"
|
|
#include "data/data_session.h"
|
|
#include "lang/lang_keys.h"
|
|
#include "main/main_session.h"
|
|
#include "media/stories/media_stories_controller.h"
|
|
#include "media/stories/media_stories_view.h"
|
|
#include "media/streaming/media_streaming_instance.h"
|
|
#include "media/streaming/media_streaming_player.h"
|
|
#include "ui/painter.h"
|
|
#include "styles/style_media_view.h"
|
|
|
|
namespace Media::Stories {
|
|
namespace {
|
|
|
|
constexpr auto kGoodFadeDuration = crl::time(200);
|
|
constexpr auto kSiblingFade = 0.5;
|
|
constexpr auto kSiblingFadeOver = 0.4;
|
|
constexpr auto kSiblingNameOpacity = 0.8;
|
|
constexpr auto kSiblingNameOpacityOver = 1.;
|
|
constexpr auto kSiblingScaleOver = 0.05;
|
|
|
|
[[nodiscard]] StoryId LookupShownId(
|
|
const Data::StoriesSource &source,
|
|
StoryId suggestedId) {
|
|
const auto i = suggestedId
|
|
? source.ids.lower_bound(Data::StoryIdDates{ suggestedId })
|
|
: end(source.ids);
|
|
return (i != end(source.ids) && i->id == suggestedId)
|
|
? suggestedId
|
|
: source.toOpen().id;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
class Sibling::Loader {
|
|
public:
|
|
virtual ~Loader() = default;
|
|
|
|
virtual QImage blurred() = 0;
|
|
virtual QImage good() = 0;
|
|
};
|
|
|
|
class Sibling::LoaderPhoto final : public Sibling::Loader {
|
|
public:
|
|
LoaderPhoto(
|
|
not_null<PhotoData*> photo,
|
|
Data::FileOrigin origin,
|
|
Fn<void()> update);
|
|
|
|
QImage blurred() override;
|
|
QImage good() override;
|
|
|
|
private:
|
|
const not_null<PhotoData*> _photo;
|
|
const Fn<void()> _update;
|
|
std::shared_ptr<Data::PhotoMedia> _media;
|
|
rpl::lifetime _waitingLoading;
|
|
|
|
};
|
|
|
|
class Sibling::LoaderVideo final
|
|
: public Sibling::Loader
|
|
, public base::has_weak_ptr {
|
|
public:
|
|
LoaderVideo(
|
|
not_null<DocumentData*> video,
|
|
Data::FileOrigin origin,
|
|
Fn<void()> update);
|
|
|
|
QImage blurred() override;
|
|
QImage good() override;
|
|
|
|
private:
|
|
void waitForGoodThumbnail();
|
|
bool updateAfterGoodCheck();
|
|
void createStreamedPlayer();
|
|
void streamedFailed();
|
|
|
|
const not_null<DocumentData*> _video;
|
|
const Data::FileOrigin _origin;
|
|
const Fn<void()> _update;
|
|
std::shared_ptr<Data::DocumentMedia> _media;
|
|
std::unique_ptr<Streaming::Instance> _streamed;
|
|
rpl::lifetime _waitingGoodGeneration;
|
|
bool _checkingGoodInCache = false;
|
|
bool _failed = false;
|
|
|
|
};
|
|
|
|
Sibling::LoaderPhoto::LoaderPhoto(
|
|
not_null<PhotoData*> photo,
|
|
Data::FileOrigin origin,
|
|
Fn<void()> update)
|
|
: _photo(photo)
|
|
, _update(std::move(update))
|
|
, _media(_photo->createMediaView()) {
|
|
_photo->load(origin, LoadFromCloudOrLocal, true);
|
|
}
|
|
|
|
QImage Sibling::LoaderPhoto::blurred() {
|
|
if (const auto image = _media->thumbnailInline()) {
|
|
return image->original();
|
|
}
|
|
const auto ratio = style::DevicePixelRatio();
|
|
auto result = QImage(ratio, ratio, QImage::Format_ARGB32_Premultiplied);
|
|
result.fill(Qt::black);
|
|
result.setDevicePixelRatio(ratio);
|
|
return result;
|
|
}
|
|
|
|
QImage Sibling::LoaderPhoto::good() {
|
|
if (const auto image = _media->image(Data::PhotoSize::Large)) {
|
|
return image->original();
|
|
} else if (!_waitingLoading) {
|
|
_photo->session().downloaderTaskFinished(
|
|
) | rpl::start_with_next([=] {
|
|
if (_media->loaded()) {
|
|
_update();
|
|
}
|
|
}, _waitingLoading);
|
|
}
|
|
return QImage();
|
|
}
|
|
|
|
Sibling::LoaderVideo::LoaderVideo(
|
|
not_null<DocumentData*> video,
|
|
Data::FileOrigin origin,
|
|
Fn<void()> update)
|
|
: _video(video)
|
|
, _origin(origin)
|
|
, _update(std::move(update))
|
|
, _media(_video->createMediaView()) {
|
|
_media->goodThumbnailWanted();
|
|
}
|
|
|
|
QImage Sibling::LoaderVideo::blurred() {
|
|
if (const auto image = _media->thumbnailInline()) {
|
|
return image->original();
|
|
}
|
|
const auto ratio = style::DevicePixelRatio();
|
|
auto result = QImage(ratio, ratio, QImage::Format_ARGB32_Premultiplied);
|
|
result.fill(Qt::black);
|
|
result.setDevicePixelRatio(ratio);
|
|
return result;
|
|
}
|
|
|
|
QImage Sibling::LoaderVideo::good() {
|
|
if (const auto image = _media->goodThumbnail()) {
|
|
return image->original();
|
|
} else if (!_video->goodThumbnailChecked()
|
|
&& !_video->goodThumbnailNoData()) {
|
|
if (!_checkingGoodInCache) {
|
|
waitForGoodThumbnail();
|
|
}
|
|
} else if (_failed) {
|
|
return QImage();
|
|
} else if (!_streamed) {
|
|
createStreamedPlayer();
|
|
} else if (_streamed->ready()) {
|
|
return _streamed->info().video.cover;
|
|
}
|
|
return QImage();
|
|
}
|
|
|
|
void Sibling::LoaderVideo::createStreamedPlayer() {
|
|
_streamed = std::make_unique<Streaming::Instance>(
|
|
_video,
|
|
_origin,
|
|
[] {}); // waitingCallback
|
|
_streamed->lockPlayer();
|
|
_streamed->player().updates(
|
|
) | rpl::start_with_next_error([=](Streaming::Update &&update) {
|
|
v::match(update.data, [&](Streaming::Information &update) {
|
|
_update();
|
|
}, [](const auto &update) {
|
|
});
|
|
}, [=](Streaming::Error &&error) {
|
|
streamedFailed();
|
|
}, _streamed->lifetime());
|
|
if (_streamed->ready()) {
|
|
_update();
|
|
} else if (!_streamed->valid()) {
|
|
streamedFailed();
|
|
} else if (!_streamed->player().active()
|
|
&& !_streamed->player().finished()) {
|
|
_streamed->play({
|
|
.mode = Streaming::Mode::Video,
|
|
});
|
|
_streamed->pause();
|
|
}
|
|
}
|
|
|
|
void Sibling::LoaderVideo::streamedFailed() {
|
|
_failed = true;
|
|
_streamed = nullptr;
|
|
_update();
|
|
}
|
|
|
|
void Sibling::LoaderVideo::waitForGoodThumbnail() {
|
|
_checkingGoodInCache = true;
|
|
const auto weak = make_weak(this);
|
|
_video->owner().cache().get({}, [=](const auto &) {
|
|
crl::on_main([=] {
|
|
if (const auto strong = weak.get()) {
|
|
if (!strong->updateAfterGoodCheck()) {
|
|
strong->_video->session().downloaderTaskFinished(
|
|
) | rpl::start_with_next([=] {
|
|
strong->updateAfterGoodCheck();
|
|
}, strong->_waitingGoodGeneration);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
bool Sibling::LoaderVideo::updateAfterGoodCheck() {
|
|
if (!_video->goodThumbnailChecked()
|
|
&& !_video->goodThumbnailNoData()) {
|
|
return false;
|
|
}
|
|
_checkingGoodInCache = false;
|
|
_waitingGoodGeneration.destroy();
|
|
_update();
|
|
return true;
|
|
}
|
|
|
|
Sibling::Sibling(
|
|
not_null<Controller*> controller,
|
|
const Data::StoriesSource &source,
|
|
StoryId suggestedId)
|
|
: _controller(controller)
|
|
, _id{ source.peer->id, LookupShownId(source, suggestedId) }
|
|
, _peer(source.peer) {
|
|
checkStory();
|
|
_goodShown.stop();
|
|
}
|
|
|
|
Sibling::~Sibling() = default;
|
|
|
|
void Sibling::checkStory() {
|
|
const auto maybeStory = _peer->owner().stories().lookup(_id);
|
|
if (!maybeStory) {
|
|
if (_blurred.isNull()) {
|
|
setBlackThumbnail();
|
|
if (maybeStory.error() == Data::NoStory::Unknown) {
|
|
_peer->owner().stories().resolve(_id, crl::guard(this, [=] {
|
|
checkStory();
|
|
}));
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
const auto story = *maybeStory;
|
|
const auto origin = Data::FileOrigin();
|
|
v::match(story->media().data, [&](not_null<PhotoData*> photo) {
|
|
_loader = std::make_unique<LoaderPhoto>(photo, origin, [=] {
|
|
check();
|
|
});
|
|
}, [&](not_null<DocumentData*> document) {
|
|
_loader = std::make_unique<LoaderVideo>(document, origin, [=] {
|
|
check();
|
|
});
|
|
}, [&](v::null_t) {
|
|
_loader = nullptr;
|
|
});
|
|
if (!_loader) {
|
|
setBlackThumbnail();
|
|
return;
|
|
}
|
|
_blurred = _loader->blurred();
|
|
check();
|
|
}
|
|
|
|
void Sibling::setBlackThumbnail() {
|
|
_blurred = QImage(
|
|
st::storiesMaxSize,
|
|
QImage::Format_ARGB32_Premultiplied);
|
|
_blurred.fill(Qt::black);
|
|
}
|
|
|
|
FullStoryId Sibling::shownId() const {
|
|
return _id;
|
|
}
|
|
|
|
not_null<PeerData*> Sibling::peer() const {
|
|
return _peer;
|
|
}
|
|
|
|
bool Sibling::shows(
|
|
const Data::StoriesSource &source,
|
|
StoryId suggestedId) const {
|
|
const auto fullId = FullStoryId{
|
|
source.peer->id,
|
|
LookupShownId(source, suggestedId),
|
|
};
|
|
return (_id == fullId);
|
|
}
|
|
|
|
SiblingView Sibling::view(const SiblingLayout &layout, float64 over) {
|
|
const auto name = nameImage(layout);
|
|
return {
|
|
.image = _good.isNull() ? _blurred : _good,
|
|
.layout = {
|
|
.geometry = layout.geometry,
|
|
.fade = kSiblingFade * (1 - over) + kSiblingFadeOver * over,
|
|
.radius = st::storiesRadius,
|
|
},
|
|
.userpic = userpicImage(layout),
|
|
.userpicPosition = layout.userpic.topLeft(),
|
|
.name = name,
|
|
.namePosition = namePosition(layout, name),
|
|
.nameOpacity = (kSiblingNameOpacity * (1 - over)
|
|
+ kSiblingNameOpacityOver * over),
|
|
.scale = 1. + (over * kSiblingScaleOver),
|
|
};
|
|
}
|
|
|
|
QImage Sibling::userpicImage(const SiblingLayout &layout) {
|
|
const auto ratio = style::DevicePixelRatio();
|
|
const auto size = layout.userpic.width() * ratio;
|
|
const auto key = _peer->userpicUniqueKey(_userpicView);
|
|
if (_userpicImage.width() != size || _userpicKey != key) {
|
|
_userpicKey = key;
|
|
_userpicImage = _peer->generateUserpicImage(_userpicView, size);
|
|
_userpicImage.setDevicePixelRatio(ratio);
|
|
}
|
|
return _userpicImage;
|
|
}
|
|
|
|
QImage Sibling::nameImage(const SiblingLayout &layout) {
|
|
if (_nameFontSize != layout.nameFontSize) {
|
|
_nameFontSize = layout.nameFontSize;
|
|
|
|
const auto family = 0; // Default font family.
|
|
const auto font = style::font(
|
|
_nameFontSize,
|
|
style::internal::FontSemibold,
|
|
family);
|
|
_name.reset();
|
|
_nameStyle = std::make_unique<style::TextStyle>(style::TextStyle{
|
|
.font = font,
|
|
});
|
|
};
|
|
const auto text = _peer->isSelf()
|
|
? tr::lng_stories_my_name(tr::now)
|
|
: _peer->shortName();
|
|
if (_nameText != text) {
|
|
_name.reset();
|
|
_nameText = text;
|
|
}
|
|
if (!_name) {
|
|
_nameAvailableWidth = 0;
|
|
_name.emplace(*_nameStyle, _nameText);
|
|
}
|
|
const auto available = layout.nameBoundingRect.width();
|
|
const auto wasCut = (_nameAvailableWidth < _name->maxWidth());
|
|
const auto nowCut = (available < _name->maxWidth());
|
|
if (_nameImage.isNull()
|
|
|| _nameAvailableWidth != layout.nameBoundingRect.width()) {
|
|
_nameAvailableWidth = layout.nameBoundingRect.width();
|
|
if (_nameImage.isNull() || nowCut || wasCut) {
|
|
const auto w = std::min(_nameAvailableWidth, _name->maxWidth());
|
|
const auto h = _nameStyle->font->height;
|
|
const auto ratio = style::DevicePixelRatio();
|
|
_nameImage = QImage(
|
|
QSize(w, h) * ratio,
|
|
QImage::Format_ARGB32_Premultiplied);
|
|
_nameImage.setDevicePixelRatio(ratio);
|
|
_nameImage.fill(Qt::transparent);
|
|
auto p = Painter(&_nameImage);
|
|
auto hq = PainterHighQualityEnabler(p);
|
|
p.setFont(_nameStyle->font);
|
|
p.setPen(Qt::white);
|
|
_name->drawLeftElided(p, 0, 0, w, w);
|
|
}
|
|
}
|
|
return _nameImage;
|
|
}
|
|
|
|
QPoint Sibling::namePosition(
|
|
const SiblingLayout &layout,
|
|
const QImage &image) const {
|
|
const auto size = image.size() / image.devicePixelRatio();
|
|
const auto width = size.width();
|
|
const auto bounding = layout.nameBoundingRect;
|
|
const auto left = layout.geometry.x()
|
|
+ (layout.geometry.width() - width) / 2;
|
|
const auto top = bounding.y() + bounding.height() - size.height();
|
|
if (left < bounding.x()) {
|
|
return { bounding.x(), top };
|
|
} else if (left + width > bounding.x() + bounding.width()) {
|
|
return { bounding.x() + bounding.width() - width, top };
|
|
}
|
|
return { left, top };
|
|
}
|
|
|
|
void Sibling::check() {
|
|
Expects(_loader != nullptr);
|
|
|
|
auto good = _loader->good();
|
|
if (good.isNull()) {
|
|
return;
|
|
}
|
|
_loader = nullptr;
|
|
_good = std::move(good);
|
|
_goodShown.start([=] {
|
|
_controller->repaintSibling(this);
|
|
}, 0., 1., kGoodFadeDuration, anim::linear);
|
|
}
|
|
|
|
} // namespace Media::Stories
|