/* 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 "api/api_peer_photo.h" #include "apiwrap.h" #include "chat_helpers/emoji_list_widget.h" #include "chat_helpers/stickers_list_widget.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 "info/userpic/info_userpic_bubble_wrap.h" #include "info/userpic/info_userpic_colors_palette_chooser.h" #include "info/userpic/info_userpic_emoji_builder_common.h" #include "info/userpic/info_userpic_emoji_builder_preview.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "settings/settings_common.h" #include "ui/controls/emoji_button.h" #include "ui/empty_userpic.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.h" #include "styles/style_chat_helpers.h" #include "styles/style_info_userpic_builder.h" #include "styles/style_layers.h" #include "styles/style_settings.h" #include "styles/style_menu_icons.h" namespace UserpicBuilder { namespace { class EmojiSelector final : public Ui::RpWidget { public: EmojiSelector( not_null parent, not_null controller); [[nodiscard]] rpl::producer> chosen() const; private: using Footer = ChatHelpers::TabbedSelector::InnerFooter; using List = ChatHelpers::TabbedSelector::Inner; using Type = ChatHelpers::SelectorTab; void createSelector(Type type); struct Selector { not_null list; not_null footer; }; [[nodiscard]] Selector createEmojiList() const; [[nodiscard]] Selector createStickersList() const; const not_null _controller; base::unique_qptr _container; base::unique_qptr _scroll; rpl::event_stream> _chosen; }; EmojiSelector::EmojiSelector( not_null parent, not_null controller) : RpWidget(parent) , _controller(controller) { createSelector(Type::Emoji); } rpl::producer> EmojiSelector::chosen() const { return _chosen.events(); } EmojiSelector::Selector EmojiSelector::createEmojiList() const { const auto session = &_controller->session(); const auto manager = &session->data().customEmojiManager(); const auto tag = Data::CustomEmojiManager::SizeTag::Large; auto args = ChatHelpers::EmojiListDescriptor{ .session = session, .mode = ChatHelpers::EmojiListMode::FullReactions, .controller = _controller, .paused = [=] { return true; }, .customRecentList = session->api().peerPhoto().profileEmojiList(), .customRecentFactory = [=](DocumentId id, Fn repaint) { return manager->create(id, std::move(repaint), tag); }, .st = &st::reactPanelEmojiPan, }; const auto list = _scroll->setOwnedWidget( object_ptr(_scroll, std::move(args))); const auto footer = list->createFooter().data(); list->refreshEmoji(); list->customChosen( ) | rpl::start_with_next([=](const ChatHelpers::FileChosen &chosen) { _chosen.fire_copy(chosen.document); }, list->lifetime()); return { list, footer }; } EmojiSelector::Selector EmojiSelector::createStickersList() const { const auto list = _scroll->setOwnedWidget( object_ptr( _scroll, _controller, Window::GifPauseReason::Any)); const auto footer = list->createFooter().data(); list->refreshRecent(); list->chosen( ) | rpl::start_with_next([=](const ChatHelpers::FileChosen &chosen) { _chosen.fire_copy(chosen.document); }, list->lifetime()); return { list, footer }; } void EmojiSelector::createSelector(Type type) { Expects((type == Type::Emoji) || (type == Type::Stickers)); const auto isEmoji = (type == Type::Emoji); const auto &stScroll = st::reactPanelScroll; _container = base::make_unique_q(this); const auto container = _container.get(); container->show(); sizeValue( ) | rpl::start_with_next([=](const QSize &s) { container->setGeometry(Rect(s)); }, container->lifetime()); _scroll = base::make_unique_q(container, stScroll); const auto selector = isEmoji ? createEmojiList() : createStickersList(); selector.footer->setParent(container); const auto toggleButton = Ui::CreateChild(container); const auto &togglePos = st::userpicBuilderEmojiSelectorTogglePosition; { const auto &pos = togglePos; toggleButton->resize(st::menuIconStickers.size() // Trying to overlap the settings button under. + QSize(pos.x() * 2, pos.y() * 2)); toggleButton->show(); toggleButton->paintRequest( ) | rpl::start_with_next([=] { auto p = QPainter(toggleButton); const auto r = toggleButton->rect() - QMargins(pos.x(), pos.y(), pos.x(), pos.y()); p.fillRect(r, st::boxBg); const auto &icon = st::userpicBuilderEmojiToggleStickersIcon; if (isEmoji) { icon.paintInCenter(p, r); } else { st::userpicBuilderEmojiToggleEmojiIcon.paintInCenter(p, r); const auto line = style::ConvertScaleExact( st::historyEmojiCircleLine); p.setPen(QPen( st::emojiIconFg, line, Qt::SolidLine, Qt::RoundCap)); p.setBrush(Qt::NoBrush); PainterHighQualityEnabler hq(p); const auto diff = (icon.width() - st::userpicBuilderEmojiToggleEmojiSize) / 2; p.drawEllipse(r - Margins(diff)); } }, toggleButton->lifetime()); } toggleButton->show(); toggleButton->setClickedCallback([=] { createSelector(isEmoji ? Type::Stickers : Type::Emoji); }); _scroll->scrollTopChanges( ) | rpl::start_with_next([=] { const auto scrollTop = _scroll->scrollTop(); const auto scrollBottom = scrollTop + _scroll->height(); selector.list->setVisibleTopBottom(scrollTop, scrollBottom); }, selector.list->lifetime()); selector.list->scrollToRequests( ) | rpl::start_with_next([=](int y) { _scroll->scrollToY(y); // _shadow->update(); }, selector.list->lifetime()); const auto separator = Ui::CreateChild(container); separator->paintRequest( ) | rpl::start_with_next([=](const QRect &r) { auto p = QPainter(separator); p.fillRect(r, st::shadowFg); }, separator->lifetime()); selector.footer->show(); separator->show(); _scroll->show(); const auto scrollWidth = stScroll.width; sizeValue( ) | rpl::start_with_next([=](const QSize &s) { const auto left = st::userpicBuilderEmojiSelectorLeft; const auto mostTop = st::userpicBuilderEmojiSelectorLeft; toggleButton->move(QPoint(left, mostTop)); selector.footer->setGeometry( (isEmoji ? (rect::right(toggleButton) - togglePos.x()) : left), mostTop, s.width() - left, selector.footer->height()); separator->setGeometry( 0, rect::bottom(selector.footer), s.width(), st::lineWidth); selector.list->resizeToWidth(s.width() - st::boxRadius * 2); _scroll->setGeometry( st::boxRadius, rect::bottom(separator), selector.list->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.builderColorIndex); 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