/* 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 "info/userpic/info_userpic_emoji_builder_widget.h" #include "apiwrap.h" #include "api/api_peer_photo.h" #include "chat_helpers/emoji_list_widget.h" #include "chat_helpers/stickers_lottie.h" #include "data/data_document.h" #include "data/data_document_media.h" #include "data/data_message_reactions.h" #include "data/data_session.h" #include "data/stickers/data_custom_emoji.h" #include "editor/photo_editor_layer_widget.h" // Editor::kProfilePhotoSize. #include "history/view/media/history_view_sticker_player.h" #include "info/userpic/info_userpic_bubble_wrap.h" #include "info/userpic/info_userpic_colors_palette_chooser.h" #include "ui/empty_userpic.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "settings/settings_common.h" #include "ui/painter.h" #include "ui/rect.h" #include "ui/widgets/labels.h" #include "ui/widgets/scroll_area.h" #include "ui/wrap/padding_wrap.h" #include "ui/wrap/vertical_layout.h" #include "window/window_session_controller.h" #include "styles/style_chat_helpers.h" #include "styles/style_info_userpic_builder.h" #include "styles/style_layers.h" #include "styles/style_settings.h" namespace UserpicBuilder { namespace { class PreviewPainter final { public: PreviewPainter(int size); [[nodiscard]] not_null document() const; void setDocument( not_null document, Fn updateCallback); void paintBackground(QPainter &p, const QBrush &brush); bool paintForeground(QPainter &p); private: const int _size; const int _emojiSize; const QRect _frameRect; std::shared_ptr _media; std::unique_ptr _player; bool _paused = false; rpl::lifetime _lifetime; }; PreviewPainter::PreviewPainter(int size) : _size(size) , _emojiSize(base::SafeRound(_size / M_SQRT2)) , _frameRect(Rect(Size(_size)) - Margins((_size - _emojiSize) / 2)) { } not_null PreviewPainter::document() const { Expects(_media != nullptr); return _media->owner(); } void PreviewPainter::setDocument( not_null document, Fn updateCallback) { if (_media && (document == _media->owner())) { return; } _lifetime.destroy(); const auto sticker = document->sticker(); Assert(sticker != nullptr); _media = document->createMediaView(); _media->checkStickerLarge(); _media->goodThumbnailWanted(); rpl::single() | rpl::then( document->owner().session().downloaderTaskFinished() ) | rpl::start_with_next([=] { if (!_media->loaded()) { return; } _lifetime.destroy(); const auto emojiSize = Size(_emojiSize); if (sticker->isLottie()) { _player = std::make_unique( ChatHelpers::LottiePlayerFromDocument( _media.get(), // ChatHelpers::StickerLottieSize::EmojiInteractionReserved7, emojiSize, Lottie::Quality::High)); } else if (sticker->isWebm()) { _player = std::make_unique( _media->owner()->location(), _media->bytes(), emojiSize); } else if (sticker) { _player = std::make_unique( _media->owner()->location(), _media->bytes(), emojiSize); } if (_player) { _player->setRepaintCallback(updateCallback); } else { updateCallback(); } }, _lifetime); } void PreviewPainter::paintBackground(QPainter &p, const QBrush &brush) { PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); p.setBrush(brush); p.drawEllipse(0, 0, _size, _size); } bool PreviewPainter::paintForeground(QPainter &p) { if (_player && _player->ready()) { // resolveIsColored(); const auto frame = _player->frame( Size(_emojiSize), (/*_isColored ? st::profileVerifiedCheckBg->c : */QColor(0, 0, 0, 0)), false, crl::now(), _paused); p.drawImage(_frameRect, frame.image); if (!_paused) { _player->markFrameShown(); } return true; } return false; } class EmojiUserpic final : public Ui::RpWidget { public: EmojiUserpic(not_null parent, const QSize &size); void result(int size, Fn done); void setGradientStops(QGradientStops stops); void setDocument(not_null document); protected: void paintEvent(QPaintEvent *event) override; private: PreviewPainter _painter; QBrush _previousBrush; QBrush _brush; QGradientStops _stops; Ui::Animations::Simple _animation; }; EmojiUserpic::EmojiUserpic(not_null parent, const QSize &size) : Ui::RpWidget(parent) , _painter(size.width()) { resize(size); } void EmojiUserpic::setDocument(not_null document) { _painter.setDocument(document, [=] { update(); }); } void EmojiUserpic::result(int size, Fn done) { const auto colors = ranges::views::all( _stops ) | ranges::views::transform([](const QGradientStop &stop) { return stop.second; }) | ranges::to_vector; const auto painter = lifetime().make_state(size); // Reset to the first frame. painter->setDocument(_painter.document(), [=] { auto background = Images::GenerateLinearGradient(Size(size), colors); auto p = QPainter(&background); while (true) { if (painter->paintForeground(p)) { break; } } done(std::move(background)); }); } void EmojiUserpic::setGradientStops(QGradientStops stops) { if (_stops == stops) { return; } if (!_stops.empty()) { auto gradient = QLinearGradient(0, 0, width() / 2., height()); gradient.setStops(base::take(_stops)); _previousBrush = QBrush(std::move(gradient)); } _stops = std::move(stops); { auto gradient = QLinearGradient(0, 0, width() / 2., height()); gradient.setStops(_stops); _brush = QBrush(std::move(gradient)); } _animation.stop(); _animation.start([=] { update(); }, 0., 1., st::slideWrapDuration); } void EmojiUserpic::paintEvent(QPaintEvent *event) { auto p = QPainter(this); if (_animation.animating() && (_previousBrush != Qt::NoBrush)) { _painter.paintBackground(p, _previousBrush); p.setOpacity(_animation.value(1.)); } _painter.paintBackground(p, _brush); p.setOpacity(1.); _painter.paintForeground(p); } class EmojiSelector final : public Ui::RpWidget { public: EmojiSelector( not_null parent, not_null controller); [[nodiscard]] rpl::producer> chosen() const; private: void prepare(); const not_null _controller; base::unique_qptr _scroll; ChatHelpers::EmojiListWidget *_selector; }; EmojiSelector::EmojiSelector( not_null parent, not_null controller) : RpWidget(parent) , _controller(controller) { prepare(); } rpl::producer> EmojiSelector::chosen() const { return _selector->customChosen( ) | rpl::map([](const ChatHelpers::FileChosen &chosen) { return chosen.document; }); } void EmojiSelector::prepare() { const auto ref = Data::PossibleItemReactionsRef{ .morePremiumAvailable = true, .customAllowed = true, }; const auto manager = &_controller->session().data().customEmojiManager(); const auto tag = Data::CustomEmojiManager::SizeTag::Large; const auto &stScroll = st::reactPanelScroll; const auto session = &_controller->session(); _scroll = base::make_unique_q(this, stScroll); auto args = ChatHelpers::EmojiListDescriptor{ .session = session, .mode = ChatHelpers::EmojiListMode::FullReactions, .controller = _controller, .paused = [=, reason = Window::GifPauseReason::Layer] { return _controller->isGifPausedAtLeastFor(reason); }, .customRecentList = session->api().peerPhoto().profileEmojiList(), .customRecentFactory = [=](DocumentId id, Fn repaint) { return manager->create(id, std::move(repaint), tag); }, .st = &st::reactPanelEmojiPan, }; _selector = _scroll->setOwnedWidget( object_ptr(_scroll, std::move(args))); const auto footer = _selector->createFooter().data(); footer->setParent(this); _selector->refreshEmoji(); const auto updateVisibleTopBottom = [=] { const auto scrollTop = _scroll->scrollTop(); const auto scrollBottom = scrollTop + _scroll->height(); _selector->setVisibleTopBottom(scrollTop, scrollBottom); }; _scroll->scrollTopChanges( ) | rpl::start_with_next(updateVisibleTopBottom, _selector->lifetime()); _selector->scrollToRequests( ) | rpl::start_with_next([=](int y) { _scroll->scrollToY(y); // _shadow->update(); }, _selector->lifetime()); const auto separator = Ui::CreateChild(this); separator->paintRequest( ) | rpl::start_with_next([=](const QRect &r) { auto p = QPainter(separator); p.fillRect(r, st::shadowFg); }, separator->lifetime()); footer->show(); separator->show(); _scroll->show(); const auto scrollWidth = stScroll.width; sizeValue( ) | rpl::start_with_next([=](const QSize &s) { const auto left = st::userpicBuilderEmojiSelectorLeft; footer->setGeometry( left, st::userpicBuilderEmojiSelectorLeft, s.width() - left, footer->height()); separator->setGeometry( 0, rect::bottom(footer), s.width(), st::lineWidth); _selector->resizeToWidth(s.width() - st::boxRadius * 2); _scroll->setGeometry( st::boxRadius, rect::bottom(separator), _selector->width() + scrollWidth, s.height() - rect::bottom(separator)); }, lifetime()); } } // namespace not_null CreateUserpicBuilder( not_null parent, not_null controller, StartData data, BothWayCommunication communication) { const auto container = Ui::CreateChild(parent.get()); const auto preview = container->add( object_ptr>( container, object_ptr( container, Size(st::settingsInfoPhotoSize))), st::userpicBuilderEmojiPreviewPadding)->entity(); if (const auto id = data.documentId) { if (const auto document = controller->session().data().document(id)) { preview->setDocument(document); } } container->add( object_ptr>( container, object_ptr( container, tr::lng_userpic_builder_color_subtitle(), st::userpicBuilderEmojiSubtitle)), st::userpicBuilderEmojiSubtitlePadding); const auto paletteBg = Ui::AddBubbleWrap( container, st::userpicBuilderEmojiBubblePaletteSize, [=] { return controller->chatStyle(); }); const auto palette = Ui::CreateChild( paletteBg.get(), data.colorIndex); palette->stopsValue( ) | rpl::start_with_next([=](QGradientStops stops) { preview->setGradientStops(std::move(stops)); }, preview->lifetime()); paletteBg->innerRectValue( ) | rpl::start_with_next([=](const QRect &r) { palette->setGeometry(r - st::userpicBuilderEmojiBubblePalettePadding); }, palette->lifetime()); container->add( object_ptr>( container, object_ptr( container, tr::lng_userpic_builder_emoji_subtitle(), st::userpicBuilderEmojiSubtitle)), st::userpicBuilderEmojiSubtitlePadding); const auto selectorBg = Ui::AddBubbleWrap( container, QSize( st::userpicBuilderEmojiBubblePaletteSize.width(), st::userpicBuilderEmojiSelectorMinHeight), [=] { return controller->chatStyle(); }); const auto selector = Ui::CreateChild( selectorBg.get(), controller); selector->chosen( ) | rpl::start_with_next([=](not_null document) { preview->setDocument(document); }, preview->lifetime()); selectorBg->innerRectValue( ) | rpl::start_with_next([=](const QRect &r) { selector->setGeometry(r); }, selector->lifetime()); base::take( communication.triggers ) | rpl::start_with_next([=, done = base::take(communication.result)] { preview->result(Editor::kProfilePhotoSize, [=](QImage &&image) { done(std::move(image)); }); }, preview->lifetime()); return container; } not_null CreateEmojiUserpic( not_null parent, const QSize &size, rpl::producer> document, rpl::producer colorIndex) { const auto widget = Ui::CreateChild(parent.get(), size); std::move( document ) | rpl::start_with_next([=](not_null d) { widget->setDocument(d); }, widget->lifetime()); std::move( colorIndex ) | rpl::start_with_next([=](int index) { const auto c = Ui::EmptyUserpic::UserpicColor( Ui::EmptyUserpic::ColorIndex(index)); widget->setGradientStops({ { 0, c.color1->c }, { 1, c.color2->c } }); }, widget->lifetime()); return widget; } } // namespace UserpicBuilder