From 9275b45ab8397dccb3a8f66d4f0b3ecdc3bbaff8 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 11 Mar 2026 11:38:33 +0300 Subject: [PATCH] Added initial implementation of draw-to-reply. --- Telegram/CMakeLists.txt | 2 + .../data/data_document_resolver.cpp | 5 +- .../SourceFiles/data/data_document_resolver.h | 3 +- Telegram/SourceFiles/data/data_session.cpp | 8 ++ Telegram/SourceFiles/data/data_session.h | 9 ++ .../history/history_inner_widget.cpp | 14 ++- .../SourceFiles/history/history_widget.cpp | 50 ++++++++++- Telegram/SourceFiles/history/history_widget.h | 2 + .../view/history_view_chat_section.cpp | 10 ++- .../view/history_view_draw_to_reply.cpp | 88 +++++++++++++++++++ .../history/view/history_view_draw_to_reply.h | 30 +++++++ .../view/history_view_pinned_section.cpp | 14 ++- .../view/history_view_scheduled_section.cpp | 18 +++- .../media/view/media_view_overlay_widget.cpp | 19 +++- .../window/window_session_controller.cpp | 9 +- .../window/window_session_controller.h | 1 + 16 files changed, 264 insertions(+), 18 deletions(-) create mode 100644 Telegram/SourceFiles/history/view/history_view_draw_to_reply.cpp create mode 100644 Telegram/SourceFiles/history/view/history_view_draw_to_reply.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 4005911faf..587735a60c 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -933,6 +933,8 @@ PRIVATE history/view/history_view_corner_buttons.h history/view/history_view_cursor_state.cpp history/view/history_view_cursor_state.h + history/view/history_view_draw_to_reply.cpp + history/view/history_view_draw_to_reply.h history/view/history_view_element.cpp history/view/history_view_element.h history/view/history_view_emoji_interactions.cpp diff --git a/Telegram/SourceFiles/data/data_document_resolver.cpp b/Telegram/SourceFiles/data/data_document_resolver.cpp index 47d58a1636..4bfda4ed75 100644 --- a/Telegram/SourceFiles/data/data_document_resolver.cpp +++ b/Telegram/SourceFiles/data/data_document_resolver.cpp @@ -182,7 +182,8 @@ void ResolveDocument( not_null document, HistoryItem *item, MsgId topicRootId, - PeerId monoforumPeerId) { + PeerId monoforumPeerId, + bool showDrawButton) { if (document->isNull()) { return; } @@ -193,7 +194,7 @@ void ResolveDocument( controller->openDocument( document, true, - { msgId, topicRootId, monoforumPeerId }); + { msgId, topicRootId, monoforumPeerId, showDrawButton }); } }; diff --git a/Telegram/SourceFiles/data/data_document_resolver.h b/Telegram/SourceFiles/data/data_document_resolver.h index 077a795476..57f965a863 100644 --- a/Telegram/SourceFiles/data/data_document_resolver.h +++ b/Telegram/SourceFiles/data/data_document_resolver.h @@ -30,6 +30,7 @@ void ResolveDocument( not_null document, HistoryItem *item, MsgId topicRootId, - PeerId monoforumPeerId); + PeerId monoforumPeerId, + bool showDrawButton); } // namespace Data diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 963fc89abb..f7743be32e 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -2007,6 +2007,14 @@ rpl::producer> Session::itemRepaintRequest() const return _itemRepaintRequest.events(); } +void Session::requestDrawToReply(DrawToReplyRequest request) { + _drawToReplyRequests.fire_copy(std::move(request)); +} + +rpl::producer Session::drawToReplyRequests() const { + return _drawToReplyRequests.events(); +} + void Session::requestViewRepaint(not_null view, QRect r) { _viewRepaintRequest.fire_copy({ view, r }); } diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index a46318e213..e30e903568 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -141,6 +141,12 @@ struct RecentJoinChat { PeerId joinedPeerId = 0; }; +struct DrawToReplyRequest { + FullMsgId messageId; + uint64 photoId = 0; + uint64 documentId = 0; +}; + struct RequestViewRepaint { not_null view; QRect rect; @@ -381,6 +387,8 @@ public: [[nodiscard]] rpl::producer giftAuctionGots() const; void requestItemRepaint(not_null item, QRect r = QRect()); [[nodiscard]] rpl::producer> itemRepaintRequest() const; + void requestDrawToReply(DrawToReplyRequest request); + [[nodiscard]] rpl::producer drawToReplyRequests() const; void requestViewRepaint(not_null view, QRect r = QRect()); [[nodiscard]] rpl::producer viewRepaintRequest() const; void requestItemResize(not_null item); @@ -1128,6 +1136,7 @@ private: rpl::event_stream> _itemShowHighlightRequest; rpl::event_stream> _itemViewRefreshRequest; rpl::event_stream> _itemTextRefreshRequest; + rpl::event_stream _drawToReplyRequests; rpl::event_stream> _itemDataChanges; rpl::event_stream> _itemRemoved; rpl::event_stream> _viewRemoved; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 60fe314c9c..c513b268fd 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/file_utilities.h" #include "core/click_handler_types.h" #include "core/phone_click_handler.h" +#include "data/data_chat_participant_status.h" #include "history/history_item_helpers.h" #include "history/view/controls/history_view_forward_panel.h" #include "history/view/controls/history_view_draft_options.h" @@ -4157,14 +4158,23 @@ void HistoryInner::elementShowPollResults( void HistoryInner::elementOpenPhoto( not_null photo, FullMsgId context) { - _controller->openPhoto(photo, { context }); + const auto draw = Data::CanSendAnyOf( + _history->peer, + Data::FilesSendRestrictions()); + _controller->openPhoto(photo, { .id = context, .showDrawButton = draw }); } void HistoryInner::elementOpenDocument( not_null document, FullMsgId context, bool showInMediaView) { - _controller->openDocument(document, showInMediaView, { context }); + const auto showDrawButton = Data::CanSendAnyOf( + _history->peer, + Data::FilesSendRestrictions()); + _controller->openDocument( + document, + showInMediaView, + { .id = context, .showDrawButton = showDrawButton }); } void HistoryInner::elementCancelUpload(const FullMsgId &context) { diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 62b0c43530..429828155c 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peers/edit_peer_requests_box.h" #include "core/file_utilities.h" #include "core/mime_type.h" +#include "history/view/history_view_draw_to_reply.h" #include "ui/emoji_config.h" #include "ui/chat/attach/attach_prepare.h" #include "ui/chat/choose_theme_controller.h" @@ -72,6 +73,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_todo_list.h" #include "data/data_web_page.h" #include "data/data_document.h" +#include "data/data_document_media.h" #include "data/data_photo.h" #include "data/data_photo_media.h" #include "data/data_channel.h" @@ -647,6 +649,11 @@ HistoryWidget::HistoryWidget( item->mainView()->itemDataChanged(); }, lifetime()); + session().data().drawToReplyRequests( + ) | rpl::on_next([=](Data::DrawToReplyRequest request) { + handleDrawToReplyRequest(std::move(request)); + }, lifetime()); + Core::App().settings().largeEmojiChanges( ) | rpl::on_next([=] { crl::on_main(this, [=] { @@ -1764,10 +1771,16 @@ void HistoryWidget::applyInlineBotQuery(UserData *bot, const QString &query) { InlineBots::ResultSelected result) { if (result.open) { const auto request = result.result->openRequest(); + const auto showDrawButton = canWriteMessage(); if (const auto photo = request.photo()) { - controller()->openPhoto(photo, {}); + controller()->openPhoto( + photo, + { .showDrawButton = showDrawButton }); } else if (const auto document = request.document()) { - controller()->openDocument(document, false, {}); + controller()->openDocument( + document, + false, + { .showDrawButton = showDrawButton }); } } else { sendInlineResult(result); @@ -3846,6 +3859,36 @@ void HistoryWidget::maybeMarkReactionsRead(not_null item) { session().api().markContentsRead(item); } +void HistoryWidget::handleDrawToReplyRequest( + Data::DrawToReplyRequest request) { + if (!_peer || request.messageId.peer != _peer->id) { + return; + } + auto image = HistoryView::ResolveDrawToReplyImage( + &session().data(), + request); + if (image.isNull()) { + return; + } + const auto replyTo = request.messageId; + HistoryView::OpenDrawToReplyEditor( + controller(), + std::move(image), + crl::guard(this, [=](QImage &&result) { + if (result.isNull()) { + return; + } + if (replyTo) { + replyToMessage({ .messageId = replyTo }); + } + auto list = Storage::PrepareMediaFromImage( + std::move(result), + QByteArray(), + st::sendMediaPreviewSize); + confirmSendingFiles(std::move(list)); + })); +} + void HistoryWidget::unreadCountUpdated() { if (_history->unreadMark() || (_migrated && _migrated->unreadMark())) { crl::on_main(this, [=, history = _history] { @@ -7105,10 +7148,11 @@ void HistoryWidget::updateHistoryGeometry( const auto media = item ? item->media() : nullptr; const auto document = media ? media->document() : nullptr; if (document && document->isVideoFile()) { + const auto draw = canWriteMessage(); controller()->openDocument( document, true, - { item->fullId() }, + { .id = item->fullId(), .showDrawButton = draw }, nullptr, timestamp); } diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index dc6bed1fce..6eba009091 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -32,6 +32,7 @@ class Error; namespace Data { class ForumTopic; class PhotoMedia; +struct DrawToReplyRequest; struct SendError; } // namespace Data @@ -466,6 +467,7 @@ private: void sendButtonClicked(); void newItemAdded(not_null item); void maybeMarkReactionsRead(not_null item); + void handleDrawToReplyRequest(Data::DrawToReplyRequest request); bool canSendFiles(not_null data) const; bool confirmSendingFiles( diff --git a/Telegram/SourceFiles/history/view/history_view_chat_section.cpp b/Telegram/SourceFiles/history/view/history_view_chat_section.cpp index 1d0d8ee759..aa446c58d8 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_chat_section.cpp @@ -3198,19 +3198,25 @@ void ChatWidget::listShowPremiumToast(not_null document) { void ChatWidget::listOpenPhoto( not_null photo, FullMsgId context) { + const auto showDrawButton = _topic + ? Data::CanSendAnyOf(_topic, Data::FilesSendRestrictions()) + : Data::CanSendAnyOf(_peer, Data::FilesSendRestrictions()); controller()->openPhoto( photo, - { context, _repliesRootId, _monoforumPeerId }); + { context, _repliesRootId, _monoforumPeerId, showDrawButton }); } void ChatWidget::listOpenDocument( not_null document, FullMsgId context, bool showInMediaView) { + const auto showDrawButton = _topic + ? Data::CanSendAnyOf(_topic, Data::FilesSendRestrictions()) + : Data::CanSendAnyOf(_peer, Data::FilesSendRestrictions()); controller()->openDocument( document, showInMediaView, - { context, _repliesRootId, _monoforumPeerId }); + { context, _repliesRootId, _monoforumPeerId, showDrawButton }); } void ChatWidget::listPaintEmpty( diff --git a/Telegram/SourceFiles/history/view/history_view_draw_to_reply.cpp b/Telegram/SourceFiles/history/view/history_view_draw_to_reply.cpp new file mode 100644 index 0000000000..cf5eda9532 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_draw_to_reply.cpp @@ -0,0 +1,88 @@ +/* +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 "history/view/history_view_draw_to_reply.h" + +#include "data/data_document.h" +#include "data/data_document_media.h" +#include "data/data_photo_media.h" +#include "data/data_session.h" +#include "editor/editor_layer_widget.h" +#include "editor/photo_editor.h" +#include "mainwidget.h" +#include "ui/image/image.h" +#include "window/window_session_controller.h" + +namespace HistoryView { + +QImage ResolveDrawToReplyImage( + not_null data, + const Data::DrawToReplyRequest &request) { + auto image = QImage(); + if (request.photoId) { + const auto photo = data->photo(request.photoId); + const auto media = photo->createMediaView(); + if (const auto large = media->image(Data::PhotoSize::Large)) { + image = large->original(); + } + } + if (image.isNull() && request.documentId) { + const auto document = data->document(request.documentId); + if (!document->isImage()) { + return QImage(); + } + const auto media = document->createMediaView(); + document->saveFromDataSilent(); + auto &location = document->location(true); + if (location.accessEnable()) { + image = Images::Read({ .path = location.name() }).image; + } else { + image = Images::Read({ .content = media->bytes() }).image; + } + location.accessDisable(); + } + return image; +} + +void OpenDrawToReplyEditor( + not_null controller, + QImage image, + Fn done) { + if (image.isNull()) { + return; + } + const auto parent = controller->content(); + const auto parentWidget = not_null{ parent.get() }; + auto fileImage = std::make_shared(std::move(image)); + auto editor = base::make_unique_q( + parentWidget, + &controller->window(), + fileImage, + Editor::PhotoModifications()); + const auto raw = editor.get(); + auto layer = std::make_unique( + parentWidget, + std::move(editor)); + Editor::InitEditorLayer( + layer.get(), + raw, + [fileImage, done = std::move(done)]( + Editor::PhotoModifications mods) mutable { + auto result = Editor::ImageModified( + fileImage->original(), + mods); + if (result.isNull()) { + return; + } + if (done) { + done(std::move(result)); + } + }); + controller->showLayer(std::move(layer), Ui::LayerOption::KeepOther); +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_draw_to_reply.h b/Telegram/SourceFiles/history/view/history_view_draw_to_reply.h new file mode 100644 index 0000000000..150d1f0b13 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_draw_to_reply.h @@ -0,0 +1,30 @@ +/* +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 Data { +class Session; +struct DrawToReplyRequest; +} // namespace Data + +namespace Window { +class SessionController; +} // namespace Window + +namespace HistoryView { + +[[nodiscard]] QImage ResolveDrawToReplyImage( + not_null data, + const Data::DrawToReplyRequest &request); + +void OpenDrawToReplyEditor( + not_null controller, + QImage image, + Fn done); + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp index d922068604..47e5ba184b 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_top_bar_widget.h" #include "history/view/history_view_translate_bar.h" #include "history/view/history_view_list_widget.h" +#include "data/data_chat_participant_status.h" #include "history/history.h" #include "history/history_item_components.h" #include "history/history_item.h" @@ -673,14 +674,23 @@ void PinnedWidget::listShowPremiumToast(not_null document) { void PinnedWidget::listOpenPhoto( not_null photo, FullMsgId context) { - controller()->openPhoto(photo, { context }); + const auto draw = Data::CanSendAnyOf( + _thread, + Data::FilesSendRestrictions()); + controller()->openPhoto(photo, { .id = context, .showDrawButton = draw }); } void PinnedWidget::listOpenDocument( not_null document, FullMsgId context, bool showInMediaView) { - controller()->openDocument(document, showInMediaView, { context }); + const auto draw = Data::CanSendAnyOf( + _thread, + Data::FilesSendRestrictions()); + controller()->openDocument( + document, + showInMediaView, + { .id = context, .showDrawButton = draw }); } void PinnedWidget::listPaintEmpty( diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index dfc56da4e0..ef854d42d2 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_top_bar_widget.h" #include "history/view/history_view_schedule_box.h" #include "history/view/history_view_sticker_toast.h" +#include "data/data_chat_participant_status.h" #include "history/history.h" #include "history/history_drag_area.h" #include "history/history_item_helpers.h" // GetErrorForSending. @@ -1603,14 +1604,27 @@ void ScheduledWidget::listShowPremiumToast( void ScheduledWidget::listOpenPhoto( not_null photo, FullMsgId context) { - controller()->openPhoto(photo, { context }); + const auto draw = _forumTopic + ? Data::CanSendAnyOf(_forumTopic, Data::FilesSendRestrictions()) + : Data::CanSendAnyOf( + _history->peer, + Data::FilesSendRestrictions()); + controller()->openPhoto(photo, { .id = context, .showDrawButton = draw }); } void ScheduledWidget::listOpenDocument( not_null document, FullMsgId context, bool showInMediaView) { - controller()->openDocument(document, showInMediaView, { context }); + const auto draw = _forumTopic + ? Data::CanSendAnyOf(_forumTopic, Data::FilesSendRestrictions()) + : Data::CanSendAnyOf( + _history->peer, + Data::FilesSendRestrictions()); + controller()->openDocument( + document, + showInMediaView, + { .id = context, .showDrawButton = draw }); } void ScheduledWidget::listPaintEmpty( diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 9315abd4b9..1f8c0e3a63 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -2812,7 +2812,8 @@ void OverlayWidget::handleDocumentClick() { _document, _message, _topicRootId, - _monoforumPeerId); + _monoforumPeerId, + _drawButtonEnabled); if (_document && _document->loading() && !_radial.animating()) { _radial.start(_documentMedia->progress()); } @@ -3092,6 +3093,22 @@ void OverlayWidget::recognize() { } void OverlayWidget::draw() { + if (!_session) { + return; + } + const auto photoId = _photo ? _photo->id : PhotoId(); + const auto documentId = (_document && _document->isImage()) + ? _document->id + : DocumentId(); + if (!photoId && !documentId) { + return; + } + _session->data().requestDrawToReply({ + _message ? _message->fullId() : FullMsgId(), + photoId, + documentId, + }); + close(); } void OverlayWidget::copyMedia() { diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 067a93b073..4760afc6d3 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -3247,7 +3247,8 @@ void SessionController::openPhoto( photo, item, message.topicRootId, - message.monoforumPeerId)); + message.monoforumPeerId, + message.showDrawButton)); } void SessionController::openPhoto( @@ -3307,7 +3308,8 @@ void SessionController::openDocument( message.topicRootId, message.monoforumPeerId, false, - usedTimestamp)); + usedTimestamp, + message.showDrawButton)); return; } Data::ResolveDocument( @@ -3315,7 +3317,8 @@ void SessionController::openDocument( document, item, message.topicRootId, - message.monoforumPeerId); + message.monoforumPeerId, + message.showDrawButton); } bool SessionController::openSharedStory(HistoryItem *item) { diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 8caf9b9f50..4055e92de2 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -566,6 +566,7 @@ public: FullMsgId id; MsgId topicRootId; PeerId monoforumPeerId; + bool showDrawButton = false; }; void openPhoto( not_null photo,