diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index d021af8f49..1e09b23b2e 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1751,6 +1751,8 @@ PRIVATE ui/chat/choose_theme_controller.h ui/chat/sponsored_message_bar.cpp ui/chat/sponsored_message_bar.h + ui/controls/compose_ai_button_factory.cpp + ui/controls/compose_ai_button_factory.h ui/controls/emoji_button_factory.cpp ui/controls/emoji_button_factory.h ui/controls/location_picker.cpp diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp index 92243777ac..bcaba76e66 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_drag_area.h" #include "history/history_item.h" #include "history/history.h" +#include "history/view/controls/history_view_compose_ai_button.h" #include "lang/lang_keys.h" #include "menu/menu_checked_action.h" #include "main/main_session.h" @@ -50,6 +51,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/attach/attach_item_single_media_preview.h" #include "ui/chat/attach/attach_single_file_preview.h" #include "ui/chat/attach/attach_single_media_preview.h" +#include "ui/controls/compose_ai_button_factory.h" #include "ui/controls/emoji_button.h" #include "ui/effects/scroll_content_shadow.h" #include "ui/image/image.h" @@ -615,6 +617,13 @@ void EditCaptionBox::setupField() { } Unexpected("Action in MimeData hook."); }); + + _aiButton = Ui::SetupCaptionAiButton({ + .parent = this, + .field = _field.get(), + .session = &_controller->session(), + .show = _controller->uiShow(), + }); } void EditCaptionBox::setupFieldAutocomplete() { @@ -1165,6 +1174,11 @@ void EditCaptionBox::resizeEvent(QResizeEvent *e) { _field->y() + st::boxAttachEmojiTop); _emojiToggle->update(); + if (_aiButton) { + Ui::UpdateCaptionAiButtonGeometry(_aiButton, _field.get()); + _aiButton->raise(); + } + if (!_controls->isHidden()) { _controls->resizeToWidth(width()); _controls->moveToLeft( diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.h b/Telegram/SourceFiles/boxes/edit_caption_box.h index 887d4b99c5..f08621f894 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.h +++ b/Telegram/SourceFiles/boxes/edit_caption_box.h @@ -24,6 +24,10 @@ namespace Data { class PhotoMedia; } // namespace Data +namespace HistoryView::Controls { +class ComposeAiButton; +} // namespace HistoryView::Controls + namespace Ui { class AbstractSinglePreview; class InputField; @@ -128,6 +132,7 @@ private: const base::unique_qptr _scroll; const base::unique_qptr _field; const base::unique_qptr _emojiToggle; + HistoryView::Controls::ComposeAiButton *_aiButton = nullptr; std::unique_ptr _autocomplete; diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index 767509869e..36952fdf5d 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "editor/photo_editor_layer_widget.h" #include "history/history_drag_area.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_schedule_box.h" #include "core/mime_type.h" #include "core/ui_integration.h" @@ -47,6 +48,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/grouped_layout.h" #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" +#include "ui/controls/compose_ai_button_factory.h" #include "ui/controls/emoji_button.h" #include "ui/painter.h" #include "ui/vertical_list.h" @@ -192,6 +194,21 @@ void EditFileCaptionBox( && Data::AllowEmojiWithoutPremium(captionToPeer, emoji); }, PremiumFeature::EmojiStatus); + if (controller) { + const auto aiButton = Ui::SetupCaptionAiButton({ + .parent = field->parentWidget(), + .field = field, + .session = &controller->session(), + .show = controller->uiShow(), + }); + rpl::combine( + box->sizeValue(), + field->geometryValue() + ) | rpl::on_next([=](QSize, QRect) { + Ui::UpdateCaptionAiButtonGeometry(aiButton, field); + aiButton->raise(); + }, aiButton->lifetime()); + } } field->setTextWithTags(std::move(currentCaption)); @@ -1181,6 +1198,10 @@ void SendFilesBox::updateCaptionVisibility() { if (_emojiToggle) { _emojiToggle->setVisible(can); } + if (_aiButton) { + _aiButton->setVisible(can + && Ui::HasEnoughLinesForAi(&_show->session(), _caption.data())); + } } void SendFilesBox::preparePreview() { @@ -1812,6 +1833,13 @@ void SendFilesBox::setupCaption() { checkCharsLimitation(); refreshMessagesCount(); }, _caption->lifetime()); + + _aiButton = Ui::SetupCaptionAiButton({ + .parent = this, + .field = _caption.data(), + .session = &_show->session(), + .show = _show, + }); } void SendFilesBox::setupCaptionAutocomplete() { @@ -2157,6 +2185,10 @@ void SendFilesBox::updateControlsGeometry() { _caption->y() + st::boxAttachEmojiTop); _emojiToggle->update(); } + if (_aiButton) { + Ui::UpdateCaptionAiButtonGeometry(_aiButton, _caption.data()); + _aiButton->raise(); + } } const auto pairs = std::array, 4>{ { { _hintLabel.data(), st::editMediaLabelMargins.top() }, diff --git a/Telegram/SourceFiles/boxes/send_files_box.h b/Telegram/SourceFiles/boxes/send_files_box.h index 5225cd7011..bdc266f8e9 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.h +++ b/Telegram/SourceFiles/boxes/send_files_box.h @@ -59,6 +59,7 @@ struct Action; namespace HistoryView::Controls { class CharactersLimitLabel; +class ComposeAiButton; } // namespace HistoryView::Controls enum class SendFilesAllow { @@ -304,6 +305,7 @@ private: std::unique_ptr _autocomplete; TextWithTags _prefilledCaptionText; object_ptr _emojiToggle = { nullptr }; + HistoryView::Controls::ComposeAiButton *_aiButton = nullptr; base::unique_qptr _emojiPanel; base::unique_qptr _emojiFilter; using CharactersLimitLabel = HistoryView::Controls::CharactersLimitLabel; diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index e1b52b85c4..1adb213892 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -1434,6 +1434,7 @@ historyAiComposeButtonStar1: icon{{ "chat/ai_star1-20x20", historyComposeIconFg historyAiComposeButtonStar2: icon{{ "chat/ai_star2-20x20", historyComposeIconFg }}; historyAiComposeButtonPosition: point(-8px, -4px); historyAiComposeTooltipSkip: 8px; +boxAiComposeButtonPosition: point(0px, -4px); historyRecordFrameIndex: 30; defaultComposeFilesMenu: IconButton(defaultIconButton) { diff --git a/Telegram/SourceFiles/ui/controls/compose_ai_button_factory.cpp b/Telegram/SourceFiles/ui/controls/compose_ai_button_factory.cpp new file mode 100644 index 0000000000..b975371b67 --- /dev/null +++ b/Telegram/SourceFiles/ui/controls/compose_ai_button_factory.cpp @@ -0,0 +1,114 @@ +/* +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/controls/compose_ai_button_factory.h" + +#include "boxes/compose_ai_box.h" +#include "history/view/controls/history_view_compose_ai_button.h" +#include "lang/lang_keys.h" +#include "main/main_app_config.h" +#include "main/main_session.h" +#include "ui/text/text_entity.h" +#include "ui/widgets/fields/input_field.h" + +#include "styles/style_chat_helpers.h" + +namespace Ui { + +bool HasEnoughLinesForAi( + not_null session, + not_null field) { + if (session->appConfig().aiComposeStyles().empty()) { + return false; + } + const auto &style = field->st().style; + const auto lineHeight = style.lineHeight + ? style.lineHeight + : style.font->height; + const auto margins = field->fullTextMargins(); + const auto contentHeight = field->height() + - margins.top() + - margins.bottom(); + return contentHeight >= (3 * lineHeight); +} + +void UpdateCaptionAiButtonGeometry( + not_null button, + not_null field) { + if (button->isHidden()) { + return; + } + const auto &pos = st::boxAiComposeButtonPosition; + const auto x = field->x() + + field->width() + - button->width() + + pos.x(); + const auto y = field->y() + + field->height() + - button->height() + + pos.y(); + button->moveToLeft(x, y); +} + +auto SetupCaptionAiButton(SetupCaptionAiButtonArgs &&args) +-> not_null { + const auto button = Ui::CreateChild< + HistoryView::Controls::ComposeAiButton + >(args.parent.get(), st::historyAiComposeButton); + const auto field = args.field; + const auto session = args.session; + const auto show = std::move(args.show); + + button->hide(); + button->setAccessibleName(tr::lng_ai_compose_title(tr::now)); + + button->setClickedCallback(crl::guard(field, [=] { + const auto textWithTags = field->getTextWithAppliedMarkdown(); + if (textWithTags.text.isEmpty()) { + return; + } + auto text = TextWithEntities{ + textWithTags.text, + TextUtilities::ConvertTextTagsToEntities(textWithTags.tags), + }; + HistoryView::Controls::ShowComposeAiBox(show, { + .session = session, + .text = std::move(text), + .apply = crl::guard(field, [=](TextWithEntities result) { + field->setTextWithTags( + { + result.text, + TextUtilities::ConvertEntitiesToTextTags( + result.entities), + }, + Ui::InputField::HistoryAction::NewEntry); + }), + }); + })); + + const auto updateVisibility = [=] { + const auto visible = !field->isHidden() + && HasEnoughLinesForAi(session, field); + button->setVisible(visible); + if (visible) { + UpdateCaptionAiButtonGeometry(button, field); + button->raise(); + } + }; + + rpl::merge( + field->heightChanges() | rpl::to_empty, + field->changes() | rpl::to_empty, + field->shownValue() | rpl::to_empty + ) | rpl::on_next([=] { + updateVisibility(); + }, button->lifetime()); + + return button; +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/controls/compose_ai_button_factory.h b/Telegram/SourceFiles/ui/controls/compose_ai_button_factory.h new file mode 100644 index 0000000000..e8586b3a7f --- /dev/null +++ b/Telegram/SourceFiles/ui/controls/compose_ai_button_factory.h @@ -0,0 +1,43 @@ +/* +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 + +namespace Main { +class Session; +} // namespace Main + +namespace Ui { +class InputField; +class Show; +} // namespace Ui + +namespace HistoryView::Controls { +class ComposeAiButton; +} // namespace HistoryView::Controls + +namespace Ui { + +[[nodiscard]] bool HasEnoughLinesForAi( + not_null session, + not_null field); + +struct SetupCaptionAiButtonArgs { + not_null parent; + not_null field; + not_null session; + std::shared_ptr show; +}; + +[[nodiscard]] auto SetupCaptionAiButton(SetupCaptionAiButtonArgs &&args) +-> not_null; + +void UpdateCaptionAiButtonGeometry( + not_null button, + not_null field); + +} // namespace Ui