diff --git a/Telegram/Resources/icons/chat/input_draw.png b/Telegram/Resources/icons/chat/input_draw.png new file mode 100644 index 000000000..37f5c854c Binary files /dev/null and b/Telegram/Resources/icons/chat/input_draw.png differ diff --git a/Telegram/Resources/icons/chat/input_draw@2x.png b/Telegram/Resources/icons/chat/input_draw@2x.png new file mode 100644 index 000000000..0087392e5 Binary files /dev/null and b/Telegram/Resources/icons/chat/input_draw@2x.png differ diff --git a/Telegram/Resources/icons/chat/input_draw@3x.png b/Telegram/Resources/icons/chat/input_draw@3x.png new file mode 100644 index 000000000..e2fd682f6 Binary files /dev/null and b/Telegram/Resources/icons/chat/input_draw@3x.png differ diff --git a/Telegram/Resources/icons/chat/input_replace.png b/Telegram/Resources/icons/chat/input_replace.png new file mode 100644 index 000000000..e0bc68cb3 Binary files /dev/null and b/Telegram/Resources/icons/chat/input_replace.png differ diff --git a/Telegram/Resources/icons/chat/input_replace@2x.png b/Telegram/Resources/icons/chat/input_replace@2x.png new file mode 100644 index 000000000..3a634692a Binary files /dev/null and b/Telegram/Resources/icons/chat/input_replace@2x.png differ diff --git a/Telegram/Resources/icons/chat/input_replace@3x.png b/Telegram/Resources/icons/chat/input_replace@3x.png new file mode 100644 index 000000000..edcd082af Binary files /dev/null and b/Telegram/Resources/icons/chat/input_replace@3x.png differ diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp index 253d42213..92e59a8a3 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -37,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "main/main_session.h" #include "main/main_session_settings.h" +#include "mainwidget.h" // controller->content() -> QWidget* #include "mtproto/mtproto_config.h" #include "platform/platform_specific.h" #include "storage/localimageloader.h" // SendMediaType @@ -69,7 +70,9 @@ namespace { constexpr auto kChangesDebounceTimeout = crl::time(1000); -auto ListFromMimeData(not_null data, bool premium) { +[[nodiscard]] Ui::PreparedList ListFromMimeData( + not_null data, + bool premium) { using Error = Ui::PreparedList::Error; const auto list = Core::ReadMimeUrls(data); auto result = !list.isEmpty() @@ -89,7 +92,7 @@ auto ListFromMimeData(not_null data, bool premium) { return result; } -Ui::AlbumType ComputeAlbumType(not_null item) { +[[nodiscard]] Ui::AlbumType ComputeAlbumType(not_null item) { if (item->groupId().empty()) { return Ui::AlbumType(); } @@ -109,17 +112,130 @@ Ui::AlbumType ComputeAlbumType(not_null item) { return Ui::AlbumType(); } -bool CanBeCompressed(Ui::AlbumType type) { +[[nodiscard]] bool CanBeCompressed(Ui::AlbumType type) { return (type == Ui::AlbumType::None) || (type == Ui::AlbumType::PhotoVideo); } +void ChooseReplacement( + not_null controller, + Ui::AlbumType type, + Fn chosen) { + const auto weak = base::make_weak(controller); + const auto callback = [=](FileDialog::OpenResult &&result) { + const auto strong = weak.get(); + if (!strong) { + return; + } + const auto showError = [=](tr::phrase<> t) { + if (const auto strong = weak.get()) { + strong->showToast({ t(tr::now) }); + } + }; + + const auto checkResult = [=](const Ui::PreparedList &list) { + if (list.files.size() != 1) { + return false; + } + const auto &file = list.files.front(); + const auto mime = file.information->filemime; + if (Core::IsMimeSticker(mime)) { + showError(tr::lng_edit_media_invalid_file); + return false; + } else if (type != Ui::AlbumType::None + && !file.canBeInAlbumType(type)) { + showError(tr::lng_edit_media_album_error); + return false; + } + return true; + }; + const auto premium = strong->session().premium(); + auto list = Storage::PreparedFileFromFilesDialog( + std::move(result), + checkResult, + showError, + st::sendMediaPreviewSize, + premium); + + if (list) { + chosen(std::move(*list)); + } + }; + + const auto filters = (type == Ui::AlbumType::PhotoVideo) + ? FileDialog::PhotoVideoFilesFilter() + : FileDialog::AllFilesFilter(); + FileDialog::GetOpenPath( + controller->content().get(), + tr::lng_choose_file(tr::now), + filters, + crl::guard(controller, callback)); +} + +void EditPhotoImage( + not_null controller, + std::shared_ptr media, + bool wasSpoiler, + Fn done) { + const auto large = media + ? media->image(Data::PhotoSize::Large) + : nullptr; + const auto parent = controller->content(); + const auto previewWidth = st::sendMediaPreviewSize; + auto callback = [=](const Editor::PhotoModifications &mods) { + if (!mods) { + return; + } + const auto large = media->image(Data::PhotoSize::Large); + if (!large) { + return; + } + auto copy = large->original(); + auto list = Storage::PrepareMediaFromImage( + std::move(copy), + QByteArray(), + previewWidth); + + using ImageInfo = Ui::PreparedFileInformation::Image; + auto &file = list.files.front(); + file.spoiler = wasSpoiler; + const auto image = std::get_if(&file.information->media); + + image->modifications = mods; + const auto sideLimit = PhotoSideLimit(); + Storage::UpdateImageDetails(file, previewWidth, sideLimit); + done(std::move(list)); + }; + const auto fileImage = std::make_shared(*large); + auto editor = base::make_unique_q( + parent, + &controller->window(), + fileImage, + Editor::PhotoModifications()); + const auto raw = editor.get(); + auto layer = std::make_unique( + parent, + std::move(editor)); + Editor::InitEditorLayer(layer.get(), raw, std::move(callback)); + controller->showLayer(std::move(layer), Ui::LayerOption::KeepOther); +} + } // namespace EditCaptionBox::EditCaptionBox( QWidget*, not_null controller, not_null item) +: EditCaptionBox({}, controller, item, PrepareEditText(item), {}, {}) { +} + +EditCaptionBox::EditCaptionBox( + QWidget*, + not_null controller, + not_null item, + TextWithTags &&text, + Ui::PreparedList &&list, + Fn saved) : _controller(controller) , _historyItem(item) , _isAllowedEditMedia(item->media() @@ -135,7 +251,10 @@ EditCaptionBox::EditCaptionBox( tr::lng_photo_caption())) , _emojiToggle(base::make_unique_q( this, - st::boxAttachEmoji)) { + st::boxAttachEmoji)) +, _initialText(std::move(text)) +, _initialList(std::move(list)) +, _saved(std::move(saved)) { Expects(item->media() != nullptr); Expects(item->media()->allowsEditCaption()); @@ -148,6 +267,57 @@ EditCaptionBox::EditCaptionBox( EditCaptionBox::~EditCaptionBox() = default; +void EditCaptionBox::StartMediaReplace( + not_null controller, + FullMsgId itemId, + TextWithTags text, + Fn saved) { + const auto session = &controller->session(); + const auto item = session->data().message(itemId); + if (!item) { + return; + } + const auto show = [=](Ui::PreparedList &&list) mutable { + controller->show(Box( + controller, + item, + std::move(text), + std::move(list), + std::move(saved))); + }; + ChooseReplacement( + controller, + ComputeAlbumType(item), + crl::guard(controller, show)); +} + +void EditCaptionBox::StartPhotoEdit( + not_null controller, + std::shared_ptr media, + FullMsgId itemId, + TextWithTags text, + Fn saved) { + const auto session = &controller->session(); + const auto item = session->data().message(itemId); + if (!item) { + return; + } + const auto hasSpoiler = item->media() && item->media()->hasSpoiler(); + EditPhotoImage(controller, media, hasSpoiler, [=]( + Ui::PreparedList &&list) mutable { + const auto item = session->data().message(itemId); + if (!item) { + return; + } + controller->show(Box( + controller, + item, + std::move(text), + std::move(list), + std::move(saved))); + }); +} + void EditCaptionBox::prepare() { addButton(tr::lng_settings_save(), [=] { save(); }); addButton(tr::lng_cancel(), [=] { closeBox(); }); @@ -158,7 +328,9 @@ void EditCaptionBox::prepare() { setupEmojiPanel(); setInitialText(); - rebuildPreview(); + if (!setPreparedList(std::move(_initialList))) { + rebuildPreview(); + } setupEditEventHandler(); SetupShadowsToScrollContent(this, _scroll, _contentHeight.events()); @@ -290,16 +462,15 @@ void EditCaptionBox::setupField() { } void EditCaptionBox::setInitialText() { - const auto initial = PrepareEditText(_historyItem); _field->setTextWithTags( - initial, + _initialText, Ui::InputField::HistoryAction::Clear); auto cursor = _field->textCursor(); cursor.movePosition(QTextCursor::End); _field->setTextCursor(cursor); _checkChangedTimer.setCallback([=] { - if (_field->getTextWithAppliedMarkdown() == initial) { + if (_field->getTextWithAppliedMarkdown() == _initialText) { setCloseByOutsideClick(true); } }); @@ -353,132 +524,44 @@ void EditCaptionBox::setupControls() { } void EditCaptionBox::setupEditEventHandler() { - const auto toastParent = Ui::BoxShow(this).toastParent(); - const auto callback = [=](FileDialog::OpenResult &&result) { - auto showError = [toastParent](tr::phrase<> t) { - Ui::Toast::Show(toastParent, t(tr::now)); - }; - - const auto checkResult = [=](const Ui::PreparedList &list) { - if (list.files.size() != 1) { - return false; - } - const auto &file = list.files.front(); - const auto mime = file.information->filemime; - if (Core::IsMimeSticker(mime)) { - showError(tr::lng_edit_media_invalid_file); - return false; - } else if (_albumType != Ui::AlbumType::None - && !file.canBeInAlbumType(_albumType)) { - showError(tr::lng_edit_media_album_error); - return false; - } - return true; - }; - const auto premium = _controller->session().premium(); - auto list = Storage::PreparedFileFromFilesDialog( - std::move(result), - checkResult, - showError, - st::sendMediaPreviewSize, - premium); - - if (list) { - setPreparedList(std::move(*list)); - } - }; - - const auto buttonCallback = [=] { - const auto filters = (_albumType == Ui::AlbumType::PhotoVideo) - ? FileDialog::PhotoVideoFilesFilter() - : FileDialog::AllFilesFilter(); - FileDialog::GetOpenPath( - this, - tr::lng_choose_file(tr::now), - filters, - crl::guard(this, callback)); - }; - _editMediaClicks.events( - ) | rpl::start_with_next( - buttonCallback, - lifetime()); + ) | rpl::start_with_next([=] { + ChooseReplacement(_controller, _albumType, crl::guard(this, [=]( + Ui::PreparedList &&list) { + setPreparedList(std::move(list)); + })); + }, lifetime()); } void EditCaptionBox::setupPhotoEditorEventHandler() { const auto openedOnce = lifetime().make_state(false); _photoEditorOpens.events( ) | rpl::start_with_next([=, controller = _controller] { - const auto increment = [=] { - if (*openedOnce) { - return; - } + if (_preparedList.files.empty() + && (!_photoMedia + || !_photoMedia->image(Data::PhotoSize::Large))) { + return; + } else if (!*openedOnce) { *openedOnce = true; controller->session().settings().incrementPhotoEditorHintShown(); controller->session().saveSettings(); - }; - const auto clearError = [=] { + } + if (!_error.isEmpty()) { _error = QString(); update(); - }; - const auto previewWidth = st::sendMediaPreviewSize; + } if (!_preparedList.files.empty()) { - increment(); - clearError(); Editor::OpenWithPreparedFile( this, controller, &_preparedList.files.front(), - previewWidth, + st::sendMediaPreviewSize, [=] { rebuildPreview(); }); - } else if (_photoMedia) { - const auto large = _photoMedia->image(Data::PhotoSize::Large); - if (!large) { - return; - } - increment(); - clearError(); - auto callback = [=](const Editor::PhotoModifications &mods) { - if (!mods || !_photoMedia) { - return; - } - const auto large = _photoMedia->image(Data::PhotoSize::Large); - if (!large) { - return; - } - auto copy = large->original(); - const auto wasSpoiler = hasSpoiler(); - - _preparedList = Storage::PrepareMediaFromImage( - std::move(copy), - QByteArray(), - previewWidth); - - using ImageInfo = Ui::PreparedFileInformation::Image; - auto &file = _preparedList.files.front(); - file.spoiler = wasSpoiler; - const auto image = std::get_if( - &file.information->media); - - image->modifications = mods; - const auto sideLimit = PhotoSideLimit(); - Storage::UpdateImageDetails(file, previewWidth, sideLimit); - rebuildPreview(); - }; - const auto fileImage = std::make_shared(*large); - auto editor = base::make_unique_q( - this, - &controller->window(), - fileImage, - Editor::PhotoModifications()); - const auto raw = editor.get(); - auto layer = std::make_unique( - this, - std::move(editor)); - Editor::InitEditorLayer(layer.get(), raw, std::move(callback)); - controller->showLayer( - std::move(layer), - Ui::LayerOption::KeepOther); + } else { + EditPhotoImage(_controller, _photoMedia, hasSpoiler(), [=]( + Ui::PreparedList &&list) { + setPreparedList(std::move(list)); + }); } }, lifetime()); } @@ -780,13 +863,13 @@ void EditCaptionBox::save() { : SendMediaType::File, _field->getTextWithAppliedMarkdown(), action); - closeBox(); + closeAfterSave(); return; } const auto done = crl::guard(this, [=] { _saveRequestId = 0; - closeBox(); + closeAfterSave(); }); const auto fail = crl::guard(this, [=](const QString &error) { @@ -795,7 +878,7 @@ void EditCaptionBox::save() { _error = tr::lng_edit_error(tr::now); update(); } else if (error == u"MESSAGE_NOT_MODIFIED"_q) { - closeBox(); + closeAfterSave(); } else if (error == u"MESSAGE_EMPTY"_q) { _field->setFocus(); _field->showError(); @@ -816,6 +899,16 @@ void EditCaptionBox::save() { _saveRequestId = Api::EditCaption(item, sending, options, done, fail); } +void EditCaptionBox::closeAfterSave() { + const auto weak = MakeWeak(this); + if (_saved) { + _saved(); + } + if (weak) { + closeBox(); + } +} + void EditCaptionBox::keyPressEvent(QKeyEvent *e) { const auto ctrl = e->modifiers().testFlag(Qt::ControlModifier); if ((e->key() == Qt::Key_E) && ctrl) { diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.h b/Telegram/SourceFiles/boxes/edit_caption_box.h index deb756b50..ff3924b3d 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.h +++ b/Telegram/SourceFiles/boxes/edit_caption_box.h @@ -36,8 +36,27 @@ public: QWidget*, not_null controller, not_null item); + EditCaptionBox( + QWidget*, + not_null controller, + not_null item, + TextWithTags &&text, + Ui::PreparedList &&list, + Fn saved); ~EditCaptionBox(); + static void StartMediaReplace( + not_null controller, + FullMsgId itemId, + TextWithTags text, + Fn saved); + static void StartPhotoEdit( + not_null controller, + std::shared_ptr media, + FullMsgId itemId, + TextWithTags text, + Fn saved); + protected: void prepare() override; void setInnerFocus() override; @@ -66,6 +85,7 @@ private: bool validateLength(const QString &text) const; void applyChanges(); void save(); + void closeAfterSave(); bool fileFromClipboard(not_null data); @@ -89,6 +109,10 @@ private: base::unique_qptr _emojiPanel; base::unique_qptr _emojiFilter; + const TextWithTags _initialText; + Ui::PreparedList _initialList; + Fn _saved; + std::shared_ptr _photoMedia; Ui::PreparedList _preparedList; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 71e023d04..30bd8cb0e 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -61,6 +61,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_web_page.h" #include "data/data_document.h" #include "data/data_photo.h" +#include "data/data_photo_media.h" #include "data/data_media_types.h" #include "data/data_channel.h" #include "data/data_chat.h" @@ -2121,6 +2122,8 @@ void HistoryWidget::showHistory( _saveEditMsgRequestId = 0; _processingReplyItem = _replyEditMsg = nullptr; _processingReplyId = _editMsgId = _replyToId = 0; + _photoEditMedia = nullptr; + updateReplaceMediaButton(); _previewData = nullptr; _previewCache.clear(); _fieldBarCancel->hide(); @@ -2504,6 +2507,25 @@ void HistoryWidget::clearAllLoadRequests() { } } +bool HistoryWidget::updateReplaceMediaButton() { + if (!_canReplaceMedia) { + const auto result = (_replaceMedia != nullptr); + _replaceMedia.destroy(); + return result; + } else if (_replaceMedia) { + return false; + } + _replaceMedia.create(this, st::historyReplaceMedia); + _replaceMedia->setClickedCallback([=] { + EditCaptionBox::StartMediaReplace( + controller(), + { _history->peer->id, _editMsgId }, + _field->getTextWithTags(), + crl::guard(_list, [=] { cancelEdit(); })); + }); + return true; +} + void HistoryWidget::updateFieldSubmitSettings() { const auto settings = _isInlineBot ? Ui::InputField::SubmitSettings::None @@ -2731,6 +2753,9 @@ void HistoryWidget::updateControlsVisibility() { _kbScroll->hide(); _fieldBarCancel->hide(); _attachToggle->hide(); + if (_replaceMedia) { + _replaceMedia->hide(); + } _tabbedSelectorToggle->hide(); _botKeyboardShow->hide(); _botKeyboardHide->hide(); @@ -2795,7 +2820,12 @@ void HistoryWidget::updateControlsVisibility() { _botCommandStart->setVisible(_cmdStartShown); } } - _attachToggle->show(); + if (_replaceMedia) { + _replaceMedia->show(); + _attachToggle->hide(); + } else { + _attachToggle->show(); + } if (_botMenuButton) { _botMenuButton->show(); } @@ -3685,7 +3715,8 @@ void HistoryWidget::saveEditMsg() { TextUtilities::ConvertTextTagsToEntities(textWithTags.tags) }; TextUtilities::PrepareForSending(left, prepareFlags); - if (!TextUtilities::CutPart(sending, left, MaxMessageSize)) { + if (!TextUtilities::CutPart(sending, left, MaxMessageSize) + && (!item->media() || !item->media()->allowsEditCaption())) { const auto suggestModerateActions = false; controller()->show( Box(item, suggestModerateActions)); @@ -4239,9 +4270,34 @@ void HistoryWidget::mouseMoveEvent(QMouseEvent *e) { } void HistoryWidget::updateOverStates(QPoint pos) { - auto inReplyEditForward = QRect(st::historyReplySkip, _field->y() - st::historySendPadding - st::historyReplyHeight, width() - st::historyReplySkip - _fieldBarCancel->width(), st::historyReplyHeight).contains(pos) && (_editMsgId || replyToId() || readyToForward()); + const auto replyEditForwardInfoRect = QRect( + st::historyReplySkip, + _field->y() - st::historySendPadding - st::historyReplyHeight, + width() - st::historyReplySkip - _fieldBarCancel->width(), + st::historyReplyHeight); + auto inReplyEditForward = (_editMsgId || replyToId() || readyToForward()) + && replyEditForwardInfoRect.contains(pos); + auto inPhotoEdit = inReplyEditForward + && _photoEditMedia + && QRect( + replyEditForwardInfoRect.x(), + replyEditForwardInfoRect.y() + st::msgReplyPadding.top(), + st::msgReplyBarSize.height(), + st::msgReplyBarSize.height()).contains(pos); auto inClickable = inReplyEditForward; - _inReplyEditForward = inReplyEditForward; + if (_inPhotoEdit != inPhotoEdit) { + _inPhotoEdit = inPhotoEdit; + if (_photoEditMedia) { + _inPhotoEditOver.start( + [=] { updateField(); }, + _inPhotoEdit ? 0. : 1., + _inPhotoEdit ? 1. : 0., + st::defaultMessageBar.duration); + } else { + _inPhotoEditOver.stop(); + } + } + _inReplyEditForward = inReplyEditForward && !inPhotoEdit; if (inClickable != _inClickable) { _inClickable = inClickable; setCursor(_inClickable ? style::cur_pointer : style::cur_default); @@ -4862,7 +4918,7 @@ void HistoryWidget::moveFieldControls() { _kbScroll->setGeometryToLeft(0, bottom, width(), keyboardHeight); } -// (_botMenuButton) _attachToggle (_sendAs) ---- _inlineResults ------------------------------ _tabbedPanel ------ _fieldBarCancel +// (_botMenuButton) (_attachToggle|_replaceMedia) (_sendAs) ---- _inlineResults ------------------------------ _tabbedPanel ------ _fieldBarCancel // (_attachDocument|_attachPhoto) _field (_ttlInfo) (_scheduled) (_silent|_cmdStart|_kbShow) (_kbHide|_tabbedSelectorToggle) _send // (_botStart|_unblock|_joinChannel|_muteUnmute|_reportMessages) @@ -4872,6 +4928,9 @@ void HistoryWidget::moveFieldControls() { const auto skip = st::historyBotMenuSkip; _botMenuButton->moveToLeft(left + skip, buttonsBottom + skip); left += skip + _botMenuButton->width(); } + if (_replaceMedia) { + _replaceMedia->moveToLeft(left, buttonsBottom); + } _attachToggle->moveToLeft(left, buttonsBottom); left += _attachToggle->width(); if (_sendAs) { _sendAs->moveToLeft(left, buttonsBottom); left += _sendAs->width(); @@ -6038,6 +6097,13 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) { st::historyReplyHeight).contains(e->pos()); if (_replyForwardPressed && !_fieldBarCancel->isHidden()) { updateField(); + } else if (_inPhotoEdit && _photoEditMedia) { + EditCaptionBox::StartPhotoEdit( + controller(), + _photoEditMedia, + { _history->peer->id, _editMsgId }, + _field->getTextWithTags(), + crl::guard(_list, [=] { cancelEdit(); })); } else if (_inReplyEditForward) { if (readyToForward()) { _forwardPanel->editOptions(controller()); @@ -6957,12 +7023,7 @@ void HistoryWidget::editMessage(FullMsgId itemId) { } void HistoryWidget::editMessage(not_null item) { - if (const auto media = item->media()) { - if (media->allowsEditCaption()) { - controller()->show(Box(controller(), item)); - return; - } - } else if (_chooseTheme) { + if (_chooseTheme) { toggleChooseChatTheme(_peer); } else if (_voiceRecordBar->isActive()) { controller()->showToast({ tr::lng_edit_caption_voice(tr::now) }); @@ -7132,6 +7193,9 @@ void HistoryWidget::cancelEdit() { return; } + _canReplaceMedia = false; + _photoEditMedia = nullptr; + updateReplaceMediaButton(); _replyEditMsg = nullptr; setEditMsgId(0); _history->clearLocalEditDraft({}); @@ -7588,6 +7652,22 @@ void HistoryWidget::updateReplyEditTexts(bool force) { _editMsgId ? _editMsgId : _replyToId); } if (_replyEditMsg) { + const auto media = _replyEditMsg->media(); + _canReplaceMedia = media && media->allowsEditMedia(); + _photoEditMedia = (_canReplaceMedia + && media->photo() + && !media->photo()->isNull()) + ? media->photo()->createMediaView() + : nullptr; + if (_photoEditMedia) { + _photoEditMedia->wanted( + Data::PhotoSize::Large, + _replyEditMsg->fullId()); + } + if (updateReplaceMediaButton()) { + updateControlsVisibility(); + updateControlsGeometry(); + } updateReplyEditText(_replyEditMsg); updateBotKeyboard(); updateReplyToName(); @@ -7695,6 +7775,9 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) { if (drawMsgText) { if (hasPreview) { if (preview) { + const auto overEdit = _photoEditMedia + ? _inPhotoEditOver.value(_inPhotoEdit ? 1. : 0.) + : 0.; auto to = QRect(replyLeft, backy + st::msgReplyPadding.top(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height()); p.drawPixmap(to.x(), to.y(), preview->pixSingle( preview->size() / style::DevicePixelRatio(), @@ -7703,12 +7786,21 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) { .outer = to.size(), })); if (_replySpoiler) { + if (overEdit > 0.) { + p.setOpacity(1. - overEdit); + } Ui::FillSpoilerRect( p, to, Ui::DefaultImageSpoiler().frame( _replySpoiler->index(now, pausedSpoiler))); } + if (overEdit > 0.) { + p.setOpacity(overEdit); + p.fillRect(to, st::historyEditMediaBg); + st::historyEditMedia.paintInCenter(p, to); + p.setOpacity(1.); + } } replyLeft += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x(); } diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index dd19eb3bd..778422cd8 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -31,6 +31,7 @@ class Error; namespace Data { enum class PreviewState : char; +class PhotoMedia; } // namespace Data namespace SendMenu { @@ -145,6 +146,7 @@ public: void firstLoadMessages(); void delayedShowAt(MsgId showAtMsgId); + bool updateReplaceMediaButton(); void updateFieldPlaceholder(); bool updateStickersByEmoji(); @@ -634,6 +636,8 @@ private: HistoryItem *_processingReplyItem = nullptr; MsgId _editMsgId = 0; + std::shared_ptr _photoEditMedia; + bool _canReplaceMedia = false; HistoryItem *_replyEditMsg = nullptr; Ui::Text::String _replyEditMsgText; @@ -732,6 +736,7 @@ private: object_ptr _botMenuButton = { nullptr }; QString _botMenuButtonText; object_ptr _attachToggle; + object_ptr _replaceMedia = { nullptr }; object_ptr _sendAs = { nullptr }; object_ptr _tabbedSelectorToggle; object_ptr _botKeyboardShow; @@ -746,7 +751,9 @@ private: bool _cmdStartShown = false; object_ptr _field; base::unique_qptr _fieldDisabled; + Ui::Animations::Simple _inPhotoEditOver; bool _inReplyEditForward = false; + bool _inPhotoEdit = false; bool _inClickable = false; bool _kbShown = false; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index a60ee8383..a06f0f1eb 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -482,6 +482,15 @@ historyMessagesTTL: IconButtonWithText { font: font(10px semibold); } +historyReplaceMedia: IconButton(historyAttach) { + icon: icon {{ "chat/input_replace", windowBgActive }}; + iconOver: icon {{ "chat/input_replace", windowBgActive }}; + ripple: RippleAnimation(defaultRippleAnimation) { + color: lightButtonBgOver; + } +} +historyEditMediaBg: videoPlayIconBg; +historyEditMedia: icon{{ "chat/input_draw", videoPlayIconFg }}; historyMessagesTTLPickerHeight: 200px; historyMessagesTTLPickerItemHeight: 40px; historyMessagesTTLLabel: FlatLabel(defaultFlatLabel) {