diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index ac17eb8a56..e1766f5e63 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1417,6 +1417,8 @@ PRIVATE ui/widgets/level_meter.h ui/countryinput.cpp ui/countryinput.h + ui/dynamic_thumbnails.cpp + ui/dynamic_thumbnails.h ui/filter_icons.cpp ui/filter_icons.h ui/filter_icon_panel.cpp diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp index 0e40b77929..1e225afc5c 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp @@ -19,14 +19,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_peer.h" #include "data/data_message_reactions.h" #include "data/stickers/data_stickers.h" +#include "dialogs/ui/dialogs_stories_content.h" +#include "dialogs/ui/dialogs_stories_content.h" #include "lottie/lottie_common.h" #include "lottie/lottie_frame_generator.h" #include "ffmpeg/ffmpeg_frame_generator.h" #include "chat_helpers/stickers_lottie.h" #include "storage/file_download.h" // kMaxFileInMemory #include "ui/widgets/fields/input_field.h" +#include "ui/text/custom_emoji_instance.h" #include "ui/text/text_custom_emoji.h" #include "ui/text/text_utilities.h" +#include "ui/dynamic_thumbnails.h" #include "ui/ui_utility.h" #include "apiwrap.h" #include "styles/style_chat.h" @@ -94,6 +98,10 @@ private: return u"internal:"_q; } +[[nodiscard]] QString UserpicEmojiPrefix() { + return u"userpic:"_q; +} + [[nodiscard]] QString InternalPadding(QMargins value) { return value.isNull() ? QString() : QString(",%1,%2,%3,%4" ).arg(value.left() @@ -528,6 +536,10 @@ std::unique_ptr CustomEmojiManager::create( int sizeOverride) { if (data.startsWith(InternalPrefix())) { return internal(data); + } else if (data.startsWith(UserpicEmojiPrefix())) { + const auto ratio = style::DevicePixelRatio(); + const auto size = FrameSizeFromTag(tag, sizeOverride) / ratio; + return userpic(data, std::move(update), size); } const auto parsed = ParseCustomEmojiData(data); return parsed @@ -575,6 +587,26 @@ std::unique_ptr CustomEmojiManager::internal( info.textColor); } +std::unique_ptr CustomEmojiManager::userpic( + QStringView data, + Fn update, + int size) { + const auto v = data.mid(UserpicEmojiPrefix().size()).split(','); + if (v.size() != 5 && v.size() != 1) { + return nullptr; + } + const auto id = PeerId(v[0].toULongLong()); + const auto padding = (v.size() == 5) + ? QMargins(v[1].toInt(), v[2].toInt(), v[3].toInt(), v[4].toInt()) + : QMargins(); + return std::make_unique( + data.toString(), + Ui::MakeUserpicThumbnail(_owner->peer(id)), + std::move(update), + padding, + size); +} + void CustomEmojiManager::resolve( QStringView data, not_null listener) { @@ -955,6 +987,14 @@ QString CustomEmojiManager::registerInternalEmoji( return result + InternalPadding(padding); } +[[nodiscard]] QString CustomEmojiManager::peerUserpicEmojiData( + not_null peer, + QMargins padding) { + return UserpicEmojiPrefix() + + QString::number(peer->id.value) + + InternalPadding(padding); +} + int FrameSizeFromTag(SizeTag tag) { const auto emoji = EmojiSizeFromTag(tag); const auto factor = style::DevicePixelRatio(); diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.h b/Telegram/SourceFiles/data/stickers/data_custom_emoji.h index 49799e6395..1cbbe0de98 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.h +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.h @@ -92,6 +92,10 @@ public: QMargins padding = {}, bool textColor = true); + [[nodiscard]] QString peerUserpicEmojiData( + not_null peer, + QMargins padding = {}); + [[nodiscard]] uint64 coloredSetId() const; private: @@ -146,6 +150,10 @@ private: LoaderFactory factory); [[nodiscard]] std::unique_ptr internal( QStringView data); + [[nodiscard]] std::unique_ptr userpic( + QStringView data, + Fn update, + int size); [[nodiscard]] static int SizeIndex(SizeTag tag); const not_null _owner; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 3047c27e26..dab3e303ad 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/dialogs_three_state_icon.h" #include "dialogs/ui/dialogs_layout.h" #include "dialogs/ui/dialogs_stories_content.h" -#include "dialogs/ui/dialogs_stories_list.h" #include "dialogs/ui/dialogs_video_userpic.h" #include "dialogs/dialogs_indexed_list.h" #include "dialogs/dialogs_widget.h" diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp index 25f9966209..bf60a24889 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp @@ -22,6 +22,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/info_memento.h" #include "main/main_session.h" #include "lang/lang_keys.h" +#include "ui/dynamic_image.h" +#include "ui/dynamic_thumbnails.h" #include "ui/painter.h" #include "window/window_session_controller.h" #include "styles/style_menu_icons.h" @@ -31,99 +33,6 @@ namespace { constexpr auto kShownLastCount = 3; -class PeerUserpic final : public Thumbnail { -public: - explicit PeerUserpic(not_null peer); - - QImage image(int size) override; - void subscribeToUpdates(Fn callback) override; - -private: - struct Subscribed { - explicit Subscribed(Fn callback) - : callback(std::move(callback)) { - } - - Ui::PeerUserpicView view; - Fn callback; - InMemoryKey key; - rpl::lifetime photoLifetime; - rpl::lifetime downloadLifetime; - }; - - [[nodiscard]] bool waitingUserpicLoad() const; - void processNewPhoto(); - - const not_null _peer; - QImage _frame; - std::unique_ptr _subscribed; - -}; - -class StoryThumbnail : public Thumbnail { -public: - explicit StoryThumbnail(FullStoryId id); - virtual ~StoryThumbnail() = default; - - QImage image(int size) override; - void subscribeToUpdates(Fn callback) override; - -protected: - struct Thumb { - Image *image = nullptr; - bool blurred = false; - }; - [[nodiscard]] virtual Main::Session &session() = 0; - [[nodiscard]] virtual Thumb loaded(FullStoryId id) = 0; - virtual void clear() = 0; - -private: - const FullStoryId _id; - QImage _full; - rpl::lifetime _subscription; - QImage _prepared; - bool _blurred = false; - -}; - -class PhotoThumbnail final : public StoryThumbnail { -public: - PhotoThumbnail(not_null photo, FullStoryId id); - -private: - Main::Session &session() override; - Thumb loaded(FullStoryId id) override; - void clear() override; - - const not_null _photo; - std::shared_ptr _media; - -}; - -class VideoThumbnail final : public StoryThumbnail { -public: - VideoThumbnail(not_null video, FullStoryId id); - -private: - Main::Session &session() override; - Thumb loaded(FullStoryId id) override; - void clear() override; - - const not_null _video; - std::shared_ptr _media; - -}; - -class EmptyThumbnail final : public Thumbnail { -public: - QImage image(int size) override; - void subscribeToUpdates(Fn callback) override; - -private: - QImage _cached; - -}; - class State final { public: State(not_null data, Data::StorySourcesList list); @@ -135,193 +44,10 @@ private: const Data::StorySourcesList _list; base::flat_map< not_null, - std::shared_ptr> _userpics; + std::shared_ptr> _userpics; }; -PeerUserpic::PeerUserpic(not_null peer) -: _peer(peer) { -} - -QImage PeerUserpic::image(int size) { - Expects(_subscribed != nullptr); - - const auto good = (_frame.width() == size * _frame.devicePixelRatio()); - const auto key = _peer->userpicUniqueKey(_subscribed->view); - if (!good || (_subscribed->key != key && !waitingUserpicLoad())) { - const auto ratio = style::DevicePixelRatio(); - _subscribed->key = key; - _frame = QImage( - QSize(size, size) * ratio, - QImage::Format_ARGB32_Premultiplied); - _frame.setDevicePixelRatio(ratio); - _frame.fill(Qt::transparent); - - auto p = Painter(&_frame); - _peer->paintUserpic(p, _subscribed->view, 0, 0, size); - } - return _frame; -} - -bool PeerUserpic::waitingUserpicLoad() const { - return _peer->hasUserpic() && _peer->useEmptyUserpic(_subscribed->view); -} - -void PeerUserpic::subscribeToUpdates(Fn callback) { - if (!callback) { - _subscribed = nullptr; - return; - } - _subscribed = std::make_unique(std::move(callback)); - - _peer->session().changes().peerUpdates( - _peer, - Data::PeerUpdate::Flag::Photo - ) | rpl::start_with_next([=] { - _subscribed->callback(); - processNewPhoto(); - }, _subscribed->photoLifetime); - - processNewPhoto(); -} - -void PeerUserpic::processNewPhoto() { - Expects(_subscribed != nullptr); - - if (!waitingUserpicLoad()) { - _subscribed->downloadLifetime.destroy(); - return; - } - _peer->session().downloaderTaskFinished( - ) | rpl::filter([=] { - return !waitingUserpicLoad(); - }) | rpl::start_with_next([=] { - _subscribed->callback(); - _subscribed->downloadLifetime.destroy(); - }, _subscribed->downloadLifetime); -} - -StoryThumbnail::StoryThumbnail(FullStoryId id) -: _id(id) { -} - -QImage StoryThumbnail::image(int size) { - const auto ratio = style::DevicePixelRatio(); - if (_prepared.width() != size * ratio) { - if (_full.isNull()) { - _prepared = QImage( - QSize(size, size) * ratio, - QImage::Format_ARGB32_Premultiplied); - _prepared.fill(Qt::black); - } else { - const auto width = _full.width(); - const auto skip = std::max((_full.height() - width) / 2, 0); - _prepared = _full.copy(0, skip, width, width).scaled( - QSize(size, size) * ratio, - Qt::IgnoreAspectRatio, - Qt::SmoothTransformation); - } - _prepared = Images::Circle(std::move(_prepared)); - _prepared.setDevicePixelRatio(ratio); - } - return _prepared; -} - -void StoryThumbnail::subscribeToUpdates(Fn callback) { - _subscription.destroy(); - if (!callback) { - clear(); - return; - } else if (!_full.isNull() && !_blurred) { - return; - } - const auto thumbnail = loaded(_id); - if (const auto image = thumbnail.image) { - _full = image->original(); - } - _blurred = thumbnail.blurred; - if (!_blurred) { - _prepared = QImage(); - } else { - _subscription = session().downloaderTaskFinished( - ) | rpl::filter([=] { - const auto thumbnail = loaded(_id); - if (!thumbnail.blurred) { - _full = thumbnail.image->original(); - _prepared = QImage(); - _blurred = false; - return true; - } - return false; - }) | rpl::take(1) | rpl::start_with_next(callback); - } -} - -PhotoThumbnail::PhotoThumbnail(not_null photo, FullStoryId id) -: StoryThumbnail(id) -, _photo(photo) { -} - -Main::Session &PhotoThumbnail::session() { - return _photo->session(); -} - -StoryThumbnail::Thumb PhotoThumbnail::loaded(FullStoryId id) { - if (!_media) { - _media = _photo->createMediaView(); - _media->wanted(Data::PhotoSize::Small, id); - } - if (const auto small = _media->image(Data::PhotoSize::Small)) { - return { .image = small }; - } - return { .image = _media->thumbnailInline(), .blurred = true }; -} - -void PhotoThumbnail::clear() { - _media = nullptr; -} - -VideoThumbnail::VideoThumbnail( - not_null video, - FullStoryId id) -: StoryThumbnail(id) -, _video(video) { -} - -Main::Session &VideoThumbnail::session() { - return _video->session(); -} - -StoryThumbnail::Thumb VideoThumbnail::loaded(FullStoryId id) { - if (!_media) { - _media = _video->createMediaView(); - _media->thumbnailWanted(id); - } - if (const auto small = _media->thumbnail()) { - return { .image = small }; - } - return { .image = _media->thumbnailInline(), .blurred = true }; -} - -void VideoThumbnail::clear() { - _media = nullptr; -} - -QImage EmptyThumbnail::image(int size) { - const auto ratio = style::DevicePixelRatio(); - if (_cached.width() != size * ratio) { - _cached = QImage( - QSize(size, size) * ratio, - QImage::Format_ARGB32_Premultiplied); - _cached.fill(Qt::black); - _cached.setDevicePixelRatio(ratio); - } - return _cached; -} - -void EmptyThumbnail::subscribeToUpdates(Fn callback) { -} - State::State(not_null data, Data::StorySourcesList list) : _data(data) , _list(list) { @@ -335,12 +61,12 @@ Content State::next() { const auto source = _data->source(info.id); Assert(source != nullptr); - auto userpic = std::shared_ptr(); + auto userpic = std::shared_ptr(); const auto peer = source->peer; if (const auto i = _userpics.find(peer); i != end(_userpics)) { userpic = i->second; } else { - userpic = MakeUserpicThumbnail(peer); + userpic = Ui::MakeUserpicThumbnail(peer); _userpics.emplace(peer, userpic); } result.elements.push_back({ @@ -430,7 +156,7 @@ rpl::producer LastForPeer(not_null peer) { result.elements.reserve(ids.size()); result.elements.push_back({ .id = uint64(id), - .thumbnail = MakeStoryThumbnail(*maybe), + .thumbnail = Ui::MakeStoryThumbnail(*maybe), .count = 1U, .unreadCount = unread ? 1U : 0U, }); @@ -479,23 +205,6 @@ rpl::producer LastForPeer(not_null peer) { }) | rpl::flatten_latest(); } -std::shared_ptr MakeUserpicThumbnail(not_null peer) { - return std::make_shared(peer); -} - -std::shared_ptr MakeStoryThumbnail( - not_null story) { - using Result = std::shared_ptr; - const auto id = story->fullId(); - return v::match(story->media().data, [](v::null_t) -> Result { - return std::make_shared(); - }, [&](not_null photo) -> Result { - return std::make_shared(photo, id); - }, [&](not_null video) -> Result { - return std::make_shared(video, id); - }); -} - void FillSourceMenu( not_null controller, const ShowMenuRequest &request) { diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.h b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.h index b42715d541..c38854f811 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.h @@ -23,7 +23,6 @@ class SessionController; namespace Dialogs::Stories { struct Content; -class Thumbnail; struct ShowMenuRequest; [[nodiscard]] rpl::producer ContentForSession( @@ -32,11 +31,6 @@ struct ShowMenuRequest; [[nodiscard]] rpl::producer LastForPeer(not_null peer); -[[nodiscard]] std::shared_ptr MakeUserpicThumbnail( - not_null peer); -[[nodiscard]] std::shared_ptr MakeStoryThumbnail( - not_null story); - void FillSourceMenu( not_null controller, const ShowMenuRequest &request); diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp index e6a93af3af..f615f4fb39 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/labels.h" #include "ui/widgets/popup_menu.h" #include "ui/widgets/tooltip.h" +#include "ui/dynamic_image.h" #include "ui/painter.h" #include "styles/style_dialogs.h" diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h index 71d4aca42a..8697677452 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h @@ -10,9 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/qt/qt_compare.h" #include "base/timer.h" #include "base/weak_ptr.h" +#include "ui/effects/animations.h" +#include "ui/text/text_custom_emoji.h" #include "ui/widgets/menu/menu_add_action_callback.h" #include "ui/rp_widget.h" -#include "ui/effects/animations.h" class QPainter; @@ -23,22 +24,17 @@ struct DialogsStoriesList; namespace Ui { class PopupMenu; +class DynamicImage; struct OutlineSegment; class ImportantTooltip; } // namespace Ui namespace Dialogs::Stories { -class Thumbnail { -public: - [[nodiscard]] virtual QImage image(int size) = 0; - virtual void subscribeToUpdates(Fn callback) = 0; -}; - struct Element { uint64 id = 0; QString name; - std::shared_ptr thumbnail; + std::shared_ptr thumbnail; uint32 count : 15 = 0; uint32 unreadCount : 15 = 0; uint32 skipSmall : 1 = 0; diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp index 159a0a4de0..859ab201ea 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp @@ -15,8 +15,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_document.h" #include "data/data_media_types.h" -#include "dialogs/ui/dialogs_stories_content.h" -#include "dialogs/ui/dialogs_stories_list.h" #include "history/history.h" #include "history/history_item.h" #include "history/history_item_components.h" @@ -30,6 +28,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/ripple_animation.h" #include "ui/text/text_utilities.h" #include "ui/widgets/tooltip.h" +#include "ui/dynamic_image.h" +#include "ui/dynamic_thumbnails.h" #include "ui/painter.h" #include "ui/round_rect.h" #include "styles/style_chat.h" @@ -446,7 +446,7 @@ PeerBubbleListPart::PeerBubbleListPart( peer->name(), kDefaultTextOptions, st::msgMinWidth), - .thumbnail = Dialogs::Stories::MakeUserpicThumbnail(peer), + .thumbnail = Ui::MakeUserpicThumbnail(peer), .link = peer->openLink(), .colorIndex = peer->colorIndex(), }); diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.h b/Telegram/SourceFiles/history/view/media/history_view_giveaway.h index 534886fc77..1aae9340de 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.h +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.h @@ -15,11 +15,8 @@ struct GiveawayStart; struct GiveawayResults; } // namespace Data -namespace Dialogs::Stories { -class Thumbnail; -} // namespace Dialogs::Stories - namespace Ui { +class DynamicImage; class RippleAnimation; } // namespace Ui @@ -208,10 +205,9 @@ public: private: int layout(int x, int y, int available); - using Thumbnail = Dialogs::Stories::Thumbnail; struct Peer { Ui::Text::String name; - std::shared_ptr thumbnail; + std::shared_ptr thumbnail; QRect geometry; ClickHandlerPtr link; mutable std::unique_ptr ripple; diff --git a/Telegram/SourceFiles/history/view/media/history_view_similar_channels.cpp b/Telegram/SourceFiles/history/view/media/history_view_similar_channels.cpp index 919a76cb09..05a03df4dc 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_similar_channels.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_similar_channels.cpp @@ -14,8 +14,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_premium_limits.h" #include "data/data_session.h" -#include "dialogs/ui/dialogs_stories_content.h" -#include "dialogs/ui/dialogs_stories_list.h" #include "history/view/history_view_element.h" #include "history/view/history_view_cursor_state.h" #include "history/history.h" @@ -30,6 +28,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/chat_theme.h" #include "ui/effects/ripple_animation.h" #include "ui/text/text_utilities.h" +#include "ui/dynamic_image.h" +#include "ui/dynamic_thumbnails.h" #include "ui/painter.h" #include "window/window_session_controller.h" #include "styles/style_chat.h" @@ -373,7 +373,7 @@ void SimilarChannels::fillMoreThumbnails() const { if (similar.list.size() <= _channels.size() + i) { break; } - _moreThumbnails[i] = Dialogs::Stories::MakeUserpicThumbnail( + _moreThumbnails[i] = Ui::MakeUserpicThumbnail( similar.list[_channels.size() + i]); } } @@ -556,7 +556,7 @@ QSize SimilarChannels::countOptimalSize() { : channel->name()), kDefaultTextOptions, st::chatSimilarChannelPhoto), - .thumbnail = Dialogs::Stories::MakeUserpicThumbnail(channel), + .thumbnail = Ui::MakeUserpicThumbnail(channel), .more = uint32(moreCounter), .moreLocked = uint32((moreCounter && !premium) ? 1 : 0), }); diff --git a/Telegram/SourceFiles/history/view/media/history_view_similar_channels.h b/Telegram/SourceFiles/history/view/media/history_view_similar_channels.h index a7f30b2a70..138373914e 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_similar_channels.h +++ b/Telegram/SourceFiles/history/view/media/history_view_similar_channels.h @@ -9,11 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_media.h" -namespace Dialogs::Stories { -class Thumbnail; -} // namespace Dialogs::Stories - namespace Ui { +class DynamicImage; class RippleAnimation; } // namespace Ui @@ -58,11 +55,10 @@ public: bool consumeHorizontalScroll(QPoint position, int delta) override; private: - using Thumbnail = Dialogs::Stories::Thumbnail; struct Channel { QRect geometry; Ui::Text::String name; - std::shared_ptr thumbnail; + std::shared_ptr thumbnail; ClickHandlerPtr link; QString counter; mutable QRect counterRect; @@ -99,7 +95,7 @@ private: mutable uint32 _hasHeavyPart : 1 = 0; std::vector _channels; - mutable std::array, 2> _moreThumbnails; + mutable std::array, 2> _moreThumbnails; mutable ClickHandlerPtr _viewAllLink; mutable ClickHandlerPtr _toggleLink; diff --git a/Telegram/SourceFiles/history/view/media/history_view_story_mention.cpp b/Telegram/SourceFiles/history/view/media/history_view_story_mention.cpp index 2d9089f378..a2b2d65ba0 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_story_mention.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_story_mention.cpp @@ -15,8 +15,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_file_click_handler.h" #include "data/data_session.h" #include "data/data_stories.h" -#include "dialogs/ui/dialogs_stories_content.h" -#include "dialogs/ui/dialogs_stories_list.h" #include "editor/photo_editor_common.h" #include "editor/photo_editor_layer_widget.h" #include "history/history.h" @@ -31,6 +29,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/outline_segments.h" #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" +#include "ui/dynamic_image.h" +#include "ui/dynamic_thumbnails.h" #include "ui/painter.h" #include "mainwidget.h" #include "apiwrap.h" @@ -102,12 +102,11 @@ void StoryMention::draw( const QRect &geometry) { const auto showStory = _story->forbidsForward() ? 0 : 1; if (!_thumbnail || _thumbnailFromStory != showStory) { - using namespace Dialogs::Stories; const auto item = _parent->data(); const auto history = item->history(); _thumbnail = showStory - ? MakeStoryThumbnail(_story) - : MakeUserpicThumbnail(item->out() + ? Ui::MakeStoryThumbnail(_story) + : Ui::MakeUserpicThumbnail(item->out() ? history->session().user() : history->peer); _thumbnailFromStory = showStory; diff --git a/Telegram/SourceFiles/history/view/media/history_view_story_mention.h b/Telegram/SourceFiles/history/view/media/history_view_story_mention.h index 376926f3bf..6018234602 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_story_mention.h +++ b/Telegram/SourceFiles/history/view/media/history_view_story_mention.h @@ -15,9 +15,9 @@ namespace Data { class Story; } // namespace Data -namespace Dialogs::Stories { -class Thumbnail; -} // namespace Dialogs::Stories +namespace Ui { +class DynamicImage; +} // namespace Ui namespace HistoryView { @@ -53,13 +53,11 @@ public: void unloadHeavyPart() override; private: - using Thumbnail = Dialogs::Stories::Thumbnail; - bool changeSubscribedTo(uint32 value); const not_null _parent; const not_null _story; - std::shared_ptr _thumbnail; + std::shared_ptr _thumbnail; QBrush _unreadBrush; uint32 _paletteVersion : 29 = 0; uint32 _thumbnailFromStory : 1 = 0; diff --git a/Telegram/SourceFiles/info/info_top_bar.cpp b/Telegram/SourceFiles/info/info_top_bar.cpp index 6c91d38ca2..d9a422adb1 100644 --- a/Telegram/SourceFiles/info/info_top_bar.cpp +++ b/Telegram/SourceFiles/info/info_top_bar.cpp @@ -7,9 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/info_top_bar.h" -#include -#include -#include "dialogs/ui/dialogs_stories_content.h" #include "dialogs/ui/dialogs_stories_list.h" #include "lang/lang_keys.h" #include "lang/lang_numbers_animation.h" diff --git a/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp b/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp new file mode 100644 index 0000000000..3cd8ba36a7 --- /dev/null +++ b/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp @@ -0,0 +1,322 @@ +/* +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 "ui/dynamic_thumbnails.h" + +#include "data/data_changes.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_story.h" +#include "main/main_session.h" +#include "ui/dynamic_image.h" +#include "ui/painter.h" +#include "ui/userpic_view.h" + +namespace Ui { +namespace { + +class PeerUserpic final : public DynamicImage { +public: + explicit PeerUserpic(not_null peer); + + QImage image(int size) override; + void subscribeToUpdates(Fn callback) override; + +private: + struct Subscribed { + explicit Subscribed(Fn callback) + : callback(std::move(callback)) { + } + + Ui::PeerUserpicView view; + Fn callback; + InMemoryKey key; + rpl::lifetime photoLifetime; + rpl::lifetime downloadLifetime; + }; + + [[nodiscard]] bool waitingUserpicLoad() const; + void processNewPhoto(); + + const not_null _peer; + QImage _frame; + std::unique_ptr _subscribed; + +}; + +class StoryThumbnail : public DynamicImage { +public: + explicit StoryThumbnail(FullStoryId id); + virtual ~StoryThumbnail() = default; + + QImage image(int size) override; + void subscribeToUpdates(Fn callback) override; + +protected: + struct Thumb { + Image *image = nullptr; + bool blurred = false; + }; + [[nodiscard]] virtual Main::Session &session() = 0; + [[nodiscard]] virtual Thumb loaded(FullStoryId id) = 0; + virtual void clear() = 0; + +private: + const FullStoryId _id; + QImage _full; + rpl::lifetime _subscription; + QImage _prepared; + bool _blurred = false; + +}; + +class PhotoThumbnail final : public StoryThumbnail { +public: + PhotoThumbnail(not_null photo, FullStoryId id); + +private: + Main::Session &session() override; + Thumb loaded(FullStoryId id) override; + void clear() override; + + const not_null _photo; + std::shared_ptr _media; + +}; + +class VideoThumbnail final : public StoryThumbnail { +public: + VideoThumbnail(not_null video, FullStoryId id); + +private: + Main::Session &session() override; + Thumb loaded(FullStoryId id) override; + void clear() override; + + const not_null _video; + std::shared_ptr _media; + +}; + +class EmptyThumbnail final : public DynamicImage { +public: + QImage image(int size) override; + void subscribeToUpdates(Fn callback) override; + +private: + QImage _cached; + +}; + +PeerUserpic::PeerUserpic(not_null peer) +: _peer(peer) { +} + +QImage PeerUserpic::image(int size) { + Expects(_subscribed != nullptr); + + const auto good = (_frame.width() == size * _frame.devicePixelRatio()); + const auto key = _peer->userpicUniqueKey(_subscribed->view); + if (!good || (_subscribed->key != key && !waitingUserpicLoad())) { + const auto ratio = style::DevicePixelRatio(); + _subscribed->key = key; + _frame = QImage( + QSize(size, size) * ratio, + QImage::Format_ARGB32_Premultiplied); + _frame.setDevicePixelRatio(ratio); + _frame.fill(Qt::transparent); + + auto p = Painter(&_frame); + _peer->paintUserpic(p, _subscribed->view, 0, 0, size); + } + return _frame; +} + +bool PeerUserpic::waitingUserpicLoad() const { + return _peer->hasUserpic() && _peer->useEmptyUserpic(_subscribed->view); +} + +void PeerUserpic::subscribeToUpdates(Fn callback) { + if (!callback) { + _subscribed = nullptr; + return; + } + _subscribed = std::make_unique(std::move(callback)); + + _peer->session().changes().peerUpdates( + _peer, + Data::PeerUpdate::Flag::Photo + ) | rpl::start_with_next([=] { + _subscribed->callback(); + processNewPhoto(); + }, _subscribed->photoLifetime); + + processNewPhoto(); +} + +void PeerUserpic::processNewPhoto() { + Expects(_subscribed != nullptr); + + if (!waitingUserpicLoad()) { + _subscribed->downloadLifetime.destroy(); + return; + } + _peer->session().downloaderTaskFinished( + ) | rpl::filter([=] { + return !waitingUserpicLoad(); + }) | rpl::start_with_next([=] { + _subscribed->callback(); + _subscribed->downloadLifetime.destroy(); + }, _subscribed->downloadLifetime); +} + +StoryThumbnail::StoryThumbnail(FullStoryId id) +: _id(id) { +} + +QImage StoryThumbnail::image(int size) { + const auto ratio = style::DevicePixelRatio(); + if (_prepared.width() != size * ratio) { + if (_full.isNull()) { + _prepared = QImage( + QSize(size, size) * ratio, + QImage::Format_ARGB32_Premultiplied); + _prepared.fill(Qt::black); + } else { + const auto width = _full.width(); + const auto skip = std::max((_full.height() - width) / 2, 0); + _prepared = _full.copy(0, skip, width, width).scaled( + QSize(size, size) * ratio, + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); + } + _prepared = Images::Circle(std::move(_prepared)); + _prepared.setDevicePixelRatio(ratio); + } + return _prepared; +} + +void StoryThumbnail::subscribeToUpdates(Fn callback) { + _subscription.destroy(); + if (!callback) { + clear(); + return; + } else if (!_full.isNull() && !_blurred) { + return; + } + const auto thumbnail = loaded(_id); + if (const auto image = thumbnail.image) { + _full = image->original(); + } + _blurred = thumbnail.blurred; + if (!_blurred) { + _prepared = QImage(); + } else { + _subscription = session().downloaderTaskFinished( + ) | rpl::filter([=] { + const auto thumbnail = loaded(_id); + if (!thumbnail.blurred) { + _full = thumbnail.image->original(); + _prepared = QImage(); + _blurred = false; + return true; + } + return false; + }) | rpl::take(1) | rpl::start_with_next(callback); + } +} + +PhotoThumbnail::PhotoThumbnail(not_null photo, FullStoryId id) +: StoryThumbnail(id) +, _photo(photo) { +} + +Main::Session &PhotoThumbnail::session() { + return _photo->session(); +} + +StoryThumbnail::Thumb PhotoThumbnail::loaded(FullStoryId id) { + if (!_media) { + _media = _photo->createMediaView(); + _media->wanted(Data::PhotoSize::Small, id); + } + if (const auto small = _media->image(Data::PhotoSize::Small)) { + return { .image = small }; + } + return { .image = _media->thumbnailInline(), .blurred = true }; +} + +void PhotoThumbnail::clear() { + _media = nullptr; +} + +VideoThumbnail::VideoThumbnail( + not_null video, + FullStoryId id) +: StoryThumbnail(id) +, _video(video) { +} + +Main::Session &VideoThumbnail::session() { + return _video->session(); +} + +StoryThumbnail::Thumb VideoThumbnail::loaded(FullStoryId id) { + if (!_media) { + _media = _video->createMediaView(); + _media->thumbnailWanted(id); + } + if (const auto small = _media->thumbnail()) { + return { .image = small }; + } + return { .image = _media->thumbnailInline(), .blurred = true }; +} + +void VideoThumbnail::clear() { + _media = nullptr; +} + +QImage EmptyThumbnail::image(int size) { + const auto ratio = style::DevicePixelRatio(); + if (_cached.width() != size * ratio) { + _cached = QImage( + QSize(size, size) * ratio, + QImage::Format_ARGB32_Premultiplied); + _cached.fill(Qt::black); + _cached.setDevicePixelRatio(ratio); + } + return _cached; +} + +void EmptyThumbnail::subscribeToUpdates(Fn callback) { +} + +} // namespace + +std::shared_ptr MakeUserpicThumbnail( + not_null peer) { + return std::make_shared(peer); +} + +std::shared_ptr MakeStoryThumbnail( + not_null story) { + using Result = std::shared_ptr; + const auto id = story->fullId(); + return v::match(story->media().data, [](v::null_t) -> Result { + return std::make_shared(); + }, [&](not_null photo) -> Result { + return std::make_shared(photo, id); + }, [&](not_null video) -> Result { + return std::make_shared(video, id); + }); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/dynamic_thumbnails.h b/Telegram/SourceFiles/ui/dynamic_thumbnails.h new file mode 100644 index 0000000000..a3a95111f5 --- /dev/null +++ b/Telegram/SourceFiles/ui/dynamic_thumbnails.h @@ -0,0 +1,25 @@ +/* +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 +*/ +#pragma once + +class PeerData; + +namespace Data { +class Story; +} // namespace Data + +namespace Ui { + +class DynamicImage; + +[[nodiscard]] std::shared_ptr MakeUserpicThumbnail( + not_null peer); +[[nodiscard]] std::shared_ptr MakeStoryThumbnail( + not_null story); + +} // namespace Ui diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 7328e27862..d424751135 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 7328e2786248c673e3599695a56989d9c1062303 +Subproject commit d4247511355a666903e9a57d821b1eb58884aade