diff --git a/Telegram/SourceFiles/boxes/compose_ai_box.cpp b/Telegram/SourceFiles/boxes/compose_ai_box.cpp index 73384983cf..33b9af4ae7 100644 --- a/Telegram/SourceFiles/boxes/compose_ai_box.cpp +++ b/Telegram/SourceFiles/boxes/compose_ai_box.cpp @@ -13,9 +13,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/compose/compose_show.h" #include "chat_helpers/stickers_lottie.h" #include "core/application.h" +#include "core/click_handler_types.h" #include "core/core_settings.h" #include "core/ui_integration.h" #include "data/data_document.h" +#include "data/data_user.h" #include "data/stickers/data_custom_emoji.h" #include "data/data_session.h" #include "lang/lang_keys.h" @@ -25,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/sections/settings_premium.h" #include "spellcheck/platform/platform_language.h" #include "ui/boxes/choose_language_box.h" +#include "ui/chat/chat_style.h" #include "ui/controls/labeled_emoji_tabs.h" #include "ui/controls/send_button.h" #include "ui/effects/ripple_animation.h" @@ -34,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/ui_utility.h" #include "ui/text/custom_emoji_helper.h" #include "ui/text/custom_emoji_text_badge.h" +#include "ui/text/text_extended_data.h" #include "ui/text/text_utilities.h" #include "ui/vertical_list.h" #include "ui/wrap/slide_wrap.h" @@ -299,7 +303,8 @@ public: ComposeAiPreviewCard( QWidget *parent, not_null session, - TextWithEntities original); + TextWithEntities original, + std::shared_ptr chatStyle); void setResizeCallback(Fn callback); void setChooseCallback(Fn callback); @@ -312,6 +317,7 @@ public: void setEmojifyChecked(bool checked); void setState(CardState state); void setResultText(TextWithEntities text); + void setShow(std::shared_ptr show); protected: int resizeGetHeight(int newWidth) override; @@ -565,7 +571,8 @@ void ComposeAiModeTabs::paintEvent(QPaintEvent *e) { ComposeAiPreviewCard::ComposeAiPreviewCard( QWidget *parent, not_null session, - TextWithEntities original) + TextWithEntities original, + std::shared_ptr chatStyle) : RpWidget(parent) , _context(Core::TextContext({ .session = session })) , _original(std::move(original)) @@ -574,7 +581,7 @@ ComposeAiPreviewCard::ComposeAiPreviewCard( st::aiComposeCardTitle)) , _originalBody(Ui::CreateChild( this, - st::aboutLabel)) + st::aiComposeBodyLabel)) , _originalToggle(Ui::CreateChild( this, st::aiComposeExpandButton)) @@ -583,7 +590,7 @@ ComposeAiPreviewCard::ComposeAiPreviewCard( st::aiComposeCardTitle)) , _resultBody(Ui::CreateChild( this, - st::aboutLabel)) + st::aiComposeBodyLabel)) , _copy(Ui::CreateChild( this, st::aiComposeCopyButton)) @@ -603,6 +610,16 @@ ComposeAiPreviewCard::ComposeAiPreviewCard( return false; }); _resultBody->setSelectable(true); + const auto watchHeight = [=](not_null label) { + label->heightValue( + ) | rpl::skip(1) | rpl::on_next([=] { + if (_resized) { + _resized(); + } + }, lifetime()); + }; + watchHeight(_originalBody); + watchHeight(_resultBody); _diffColors[0] = { &st::boxTextFgGood->p, &st::boxTextFgGood->p }; _diffColors[1] = { &st::attentionButtonFg->p, &st::attentionButtonFg->p }; _resultBody->setColors(_diffColors); @@ -629,6 +646,22 @@ ComposeAiPreviewCard::ComposeAiPreviewCard( _resultBody->setMarkedText(_original, _context); _copy->setVisible(false); updateOriginalToggleIcon(); + if (chatStyle) { + const auto style = chatStyle; + const auto s = session.get(); + const auto setupCaches = [=](not_null label) { + label->setPreCache([=] { + return style->messageStyle(false, false).preCache.get(); + }); + label->setBlockquoteCache([=] { + return style->coloredQuoteCache( + false, + s->user()->colorIndex()); + }); + }; + setupCaches(_originalBody); + setupCaches(_resultBody); + } } void ComposeAiPreviewCard::setResizeCallback(Fn callback) { @@ -714,6 +747,27 @@ void ComposeAiPreviewCard::setResultText(TextWithEntities text) { refreshGeometry(); } +void ComposeAiPreviewCard::setShow(std::shared_ptr show) { + const auto setupFilter = [&](not_null label) { + label->setClickHandlerFilter([=]( + const ClickHandlerPtr &handler, + Qt::MouseButton button) { + if (dynamic_cast(handler.get())) { + ActivateClickHandler(label, handler, ClickContext{ + .button = button, + .other = QVariant::fromValue(ClickHandlerContext{ + .show = show, + }) + }); + return false; + } + return true; + }); + }; + setupFilter(_originalBody); + setupFilter(_resultBody); +} + int ComposeAiPreviewCard::resizeGetHeight(int newWidth) { const auto padding = st::aiComposeCardPadding; const auto contentWidth = newWidth - padding.left() - padding.right(); @@ -875,7 +929,12 @@ ComposeAiContent::ComposeAiContent( , _stylesData(ResolveStyleDescriptors( _session->appConfig().aiComposeStyles())) , _translateStylesData(ResolveTranslateStyleDescriptors(_session, _stylesData)) -, _preview(Ui::CreateChild(this, _session, _original)) { +, _preview( + Ui::CreateChild( + this, + _session, + _original, + args.chatStyle)) { _preview->setResizeCallback([=] { refreshLayout(); }); _preview->setChooseCallback([=] { chooseLanguage(); }); _preview->setCopyCallback([=] { copyResult(); }); @@ -885,6 +944,7 @@ ComposeAiContent::ComposeAiContent( request(); } }); + _preview->setShow(_box->uiShow()); } ComposeAiContent::~ComposeAiContent() { diff --git a/Telegram/SourceFiles/boxes/compose_ai_box.h b/Telegram/SourceFiles/boxes/compose_ai_box.h index 3fcf984adb..f7e23ac0b6 100644 --- a/Telegram/SourceFiles/boxes/compose_ai_box.h +++ b/Telegram/SourceFiles/boxes/compose_ai_box.h @@ -16,6 +16,7 @@ class Session; } // namespace Main namespace Ui { +class ChatStyle; class GenericBox; class RpWidget; class Show; @@ -26,6 +27,7 @@ namespace HistoryView::Controls { struct ComposeAiBoxArgs { not_null session; TextWithEntities text; + std::shared_ptr chatStyle; Fn apply; Fn)> send; Fn, Fn)> setupMenu; diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp index bcaba76e66..c71374f467 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -576,11 +576,16 @@ void EditCaptionBox::setupField() { const auto allow = [=](not_null emoji) { return Data::AllowEmojiWithoutPremium(peer, emoji); }; - InitMessageFieldHandlers( - _controller, - _field.get(), - Window::GifPauseReason::Layer, - allow); + const auto chatStyle = InitMessageFieldHandlers({ + .session = &_controller->session(), + .show = _controller->uiShow(), + .field = _field.get(), + .customEmojiPaused = [=] { + return _controller->isGifPausedAtLeastFor( + Window::GifPauseReason::Layer); + }, + .allowPremiumEmoji = allow, + }); setupFieldAutocomplete(); Ui::Emoji::SuggestionsController::Init( getDelegate()->outerContainer(), @@ -623,6 +628,7 @@ void EditCaptionBox::setupField() { .field = _field.get(), .session = &_controller->session(), .show = _controller->uiShow(), + .chatStyle = chatStyle, }); } diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index 36952fdf5d..c603a29ee0 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -184,22 +184,35 @@ void EditFileCaptionBox( Ui::ResizeFitChild(wrap, field); if (const auto window = Core::App().findWindow(box)) { const auto controller = window->sessionController(); + const auto allow = [=](not_null emoji) { + return captionToPeer + && Data::AllowEmojiWithoutPremium(captionToPeer, emoji); + }; Ui::SetupCaptionFieldInBox( box, controller, field, captionToPeer, - [=](not_null emoji) { - return captionToPeer - && Data::AllowEmojiWithoutPremium(captionToPeer, emoji); - }, + allow, PremiumFeature::EmojiStatus); if (controller) { + const auto chatStyle = InitMessageFieldHandlers({ + .session = &controller->session(), + .show = controller->uiShow(), + .field = field, + .customEmojiPaused = [=] { + return controller->isGifPausedAtLeastFor( + Window::GifPauseReason::Layer); + }, + .allowPremiumEmoji = allow, + .fieldStyle = &st.files.caption, + }); const auto aiButton = Ui::SetupCaptionAiButton({ .parent = field->parentWidget(), .field = field, .session = &controller->session(), .show = controller->uiShow(), + .chatStyle = chatStyle, }); rpl::combine( box->sizeValue(), @@ -1763,7 +1776,7 @@ void SendFilesBox::setupCaption() { return Data::AllowEmojiWithoutPremium(_toPeer, emoji); }; const auto show = _show; - InitMessageFieldHandlers({ + const auto chatStyle = InitMessageFieldHandlers({ .session = &show->session(), .show = show, .field = _caption.data(), @@ -1839,6 +1852,7 @@ void SendFilesBox::setupCaption() { .field = _caption.data(), .session = &_show->session(), .show = _show, + .chatStyle = chatStyle, }); } diff --git a/Telegram/SourceFiles/boxes/send_gif_with_caption_box.cpp b/Telegram/SourceFiles/boxes/send_gif_with_caption_box.cpp index a0492408ef..2fe28e6ca6 100644 --- a/Telegram/SourceFiles/boxes/send_gif_with_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/send_gif_with_caption_box.cpp @@ -29,12 +29,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_item.h" #include "history/view/controls/history_view_characters_limit.h" +#include "history/view/controls/history_view_compose_ai_button.h" #include "history/view/history_view_message.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "media/clip/media_clip_reader.h" #include "menu/menu_checked_action.h" #include "menu/menu_send.h" +#include "ui/controls/compose_ai_button_factory.h" #include "ui/controls/emoji_button.h" #include "ui/controls/emoji_button_factory.h" #include "ui/effects/spoiler_mess.h" @@ -333,9 +335,25 @@ void CaptionBox( input->setTextWithTags(std::move(initialText)); input->setSubmitSettings(Core::App().settings().sendSubmitWay()); - InitMessageField(controller, input, [=](not_null) { - return true; + const auto chatStyle = InitMessageField( + controller, + input, + [=](not_null) { return true; }); + + const auto aiButton = Ui::SetupCaptionAiButton({ + .parent = input->parentWidget(), + .field = input, + .session = &controller->session(), + .show = controller->uiShow(), + .chatStyle = chatStyle, }); + rpl::combine( + box->sizeValue(), + input->geometryValue() + ) | rpl::on_next([=](QSize, QRect) { + Ui::UpdateCaptionAiButtonGeometry(aiButton, input); + aiButton->raise(); + }, aiButton->lifetime()); const auto sendMenuDetails = [=] { return details; }; struct Autocomplete { diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 9f83636345..4333d18452 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -1807,3 +1807,27 @@ aiComposeSendButton: SendButton(historySend) { } sendIconFg: windowFgActive; } + +aiComposeBodyLabel: FlatLabel(defaultFlatLabel) { + minWidth: 300px; + align: align(topleft); + style: TextStyle(defaultTextStyle) { + lineHeight: 22px; + blockquote: QuoteStyle(historyQuoteStyle) { + padding: margins(10px, 2px, 20px, 2px); + icon: icon{{ "chat/mini_quote", windowFg }}; + iconPosition: point(4px, 4px); + expand: icon{{ "intro_country_dropdown", windowFg }}; + expandPosition: point(6px, 4px); + collapse: icon{{ "intro_country_dropdown-flip_vertical", windowFg }}; + collapsePosition: point(6px, 4px); + } + pre: QuoteStyle(historyQuoteStyle) { + header: 20px; + headerPosition: point(10px, 2px); + scrollable: true; + icon: icon{{ "chat/mini_copy", windowFg }}; + iconPosition: point(4px, 2px); + } + } +} diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index 5a603a0c87..8e7db8f70a 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -498,7 +498,8 @@ Fn save)> DefaultEditLanguageCallback( }; } -void InitMessageFieldHandlers(MessageFieldHandlersArgs &&args) { +auto InitMessageFieldHandlers(MessageFieldHandlersArgs &&args) +-> std::shared_ptr { const auto paused = [passed = args.customEmojiPaused] { return passed && passed(); }; @@ -526,7 +527,7 @@ void InitMessageFieldHandlers(MessageFieldHandlersArgs &&args) { field->setEditLanguageCallback(DefaultEditLanguageCallback(show)); InitSpellchecker(show, field, args.fieldStyle != nullptr); } - const auto style = field->lifetime().make_state( + const auto style = std::make_shared( session->colorIndicesValue()); field->setPreCache([=] { return style->messageStyle(false, false).preCache.get(); @@ -535,6 +536,7 @@ void InitMessageFieldHandlers(MessageFieldHandlersArgs &&args) { const auto colorIndex = session->user()->colorIndex(); return style->coloredQuoteCache(false, colorIndex).get(); }); + return style; } [[nodiscard]] bool IsGoodFactcheckUrl(QStringView url) { @@ -646,11 +648,11 @@ void InitMessageFieldGeometry(not_null field) { field->setAdditionalMargin(style::ConvertScale(4) - 4); } -void InitMessageField( +std::shared_ptr InitMessageField( std::shared_ptr show, not_null field, Fn)> allowPremiumEmoji) { - InitMessageFieldHandlers({ + const auto style = InitMessageFieldHandlers({ .session = &show->session(), .show = show, .field = field, @@ -660,9 +662,10 @@ void InitMessageField( .allowPremiumEmoji = std::move(allowPremiumEmoji), }); InitMessageFieldGeometry(field); + return style; } -void InitMessageField( +std::shared_ptr InitMessageField( not_null controller, not_null field, Fn)> allowPremiumEmoji) { diff --git a/Telegram/SourceFiles/chat_helpers/message_field.h b/Telegram/SourceFiles/chat_helpers/message_field.h index 5f9512b4e4..a24fa100b3 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.h +++ b/Telegram/SourceFiles/chat_helpers/message_field.h @@ -42,6 +42,7 @@ struct WriteRestriction; } // namespace HistoryView::Controls namespace Ui { +class ChatStyle; class GenericBox; class PopupMenu; class Show; @@ -73,18 +74,19 @@ struct MessageFieldHandlersArgs { const style::InputField *fieldStyle = nullptr; base::flat_set allowMarkdownTags; }; -void InitMessageFieldHandlers(MessageFieldHandlersArgs &&args); +auto InitMessageFieldHandlers(MessageFieldHandlersArgs &&args) +-> std::shared_ptr; void InitMessageFieldHandlers( not_null controller, not_null field, ChatHelpers::PauseReason pauseReasonLevel, Fn)> allowPremiumEmoji = nullptr); -void InitMessageField( +std::shared_ptr InitMessageField( std::shared_ptr show, not_null field, Fn)> allowPremiumEmoji); -void InitMessageField( +std::shared_ptr InitMessageField( not_null controller, not_null field, Fn)> allowPremiumEmoji); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index bd4b4f59f3..aa64b1282e 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -483,7 +483,7 @@ HistoryWidget::HistoryWidget( updateControlsGeometry(); }, lifetime()); - InitMessageField(controller, _field, [=]( + _fieldChatStyle = InitMessageField(controller, _field, [=]( not_null document) { if (_peer && Data::AllowEmojiWithoutPremium(_peer, document)) { return true; @@ -4677,6 +4677,7 @@ void HistoryWidget::showAiComposeBox() { HistoryView::Controls::ShowComposeAiBox(controller()->uiShow(), { .session = &session(), .text = text, + .chatStyle = _fieldChatStyle, .apply = crl::guard(this, [=](const TextWithEntities &result) { const auto action = Ui::InputField::HistoryAction::NewEntry; setFieldText({ diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 309004d58c..755ed32866 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -82,6 +82,7 @@ class SpoilerAnimation; class ChooseThemeController; class ContinuousScroll; struct ChatPaintHighlight; +class ChatStyle; template class SlideWrap; } // namespace Ui @@ -866,6 +867,7 @@ private: rpl::lifetime _subsectionTabsLifetime; rpl::lifetime _subsectionCheckLifetime; base::unique_qptr _aiTooltip; + std::shared_ptr _fieldChatStyle; bool _aiTooltipShown = false; bool _cmdStartShown = false; object_ptr _field; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 492aaffb67..8be266b88b 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -2362,7 +2362,7 @@ void ComposeControls::initField() { _field->setEnabled(shown); }, _field->lifetime()); #endif // Q_OS_MAC - InitMessageField(_show, _field, [=](not_null emoji) { + _chatStyle = InitMessageField(_show, _field, [=](not_null emoji) { if (_history && Data::AllowEmojiWithoutPremium(_history->peer, emoji)) { return true; @@ -3659,6 +3659,7 @@ void ComposeControls::showAiComposeBox() { Controls::ShowComposeAiBox(_show, { .session = _session, .text = text, + .chatStyle = _chatStyle, .apply = crl::guard(_wrap.get(), [=](TextWithEntities result) { const auto action = Ui::InputField::HistoryAction::NewEntry; setFieldText({ diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index 483df684a5..484981f918 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -72,6 +72,7 @@ class ImportantTooltip; struct PreparedList; struct SendStarButtonState; class ReactionFlyAnimation; +class ChatStyle; } // namespace Ui namespace Ui::Emoji { @@ -477,6 +478,7 @@ private: const std::unique_ptr _voiceRecordBar; base::unique_qptr _aiTooltip; bool _aiTooltipShown = false; + std::shared_ptr _chatStyle; const Fn _sendMenuDetails; const Fn)> _unavailableEmojiPasted; diff --git a/Telegram/SourceFiles/ui/controls/compose_ai_button_factory.cpp b/Telegram/SourceFiles/ui/controls/compose_ai_button_factory.cpp index b975371b67..12df0832a0 100644 --- a/Telegram/SourceFiles/ui/controls/compose_ai_button_factory.cpp +++ b/Telegram/SourceFiles/ui/controls/compose_ai_button_factory.cpp @@ -62,6 +62,7 @@ auto SetupCaptionAiButton(SetupCaptionAiButtonArgs &&args) const auto field = args.field; const auto session = args.session; const auto show = std::move(args.show); + const auto chatStyle = std::move(args.chatStyle); button->hide(); button->setAccessibleName(tr::lng_ai_compose_title(tr::now)); @@ -78,6 +79,7 @@ auto SetupCaptionAiButton(SetupCaptionAiButtonArgs &&args) HistoryView::Controls::ShowComposeAiBox(show, { .session = session, .text = std::move(text), + .chatStyle = chatStyle, .apply = crl::guard(field, [=](TextWithEntities result) { field->setTextWithTags( { diff --git a/Telegram/SourceFiles/ui/controls/compose_ai_button_factory.h b/Telegram/SourceFiles/ui/controls/compose_ai_button_factory.h index e8586b3a7f..5acb15b297 100644 --- a/Telegram/SourceFiles/ui/controls/compose_ai_button_factory.h +++ b/Telegram/SourceFiles/ui/controls/compose_ai_button_factory.h @@ -12,6 +12,7 @@ class Session; } // namespace Main namespace Ui { +class ChatStyle; class InputField; class Show; } // namespace Ui @@ -31,6 +32,7 @@ struct SetupCaptionAiButtonArgs { not_null field; not_null session; std::shared_ptr show; + std::shared_ptr chatStyle; }; [[nodiscard]] auto SetupCaptionAiButton(SetupCaptionAiButtonArgs &&args) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 07964a7b49..f20fd259d6 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 07964a7b496d8da2f7f10ebe731f70f9ae2b6b05 +Subproject commit f20fd259d6a15766b74123a0a53b0ef7503ebd27