Allow editing photos in messages in ComposeControls.
This commit is contained in:
parent
e71f614f4d
commit
37ab65d952
|
@ -347,7 +347,7 @@ public:
|
||||||
void setHistory(const SetHistoryArgs &args);
|
void setHistory(const SetHistoryArgs &args);
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
void editMessage(FullMsgId id);
|
void editMessage(FullMsgId id, bool photoEditAllowed = false);
|
||||||
void replyToMessage(FullMsgId id);
|
void replyToMessage(FullMsgId id);
|
||||||
void updateForwarding(
|
void updateForwarding(
|
||||||
Data::Thread *thread,
|
Data::Thread *thread,
|
||||||
|
@ -363,8 +363,10 @@ public:
|
||||||
[[nodiscard]] bool readyToForward() const;
|
[[nodiscard]] bool readyToForward() const;
|
||||||
[[nodiscard]] const HistoryItemsList &forwardItems() const;
|
[[nodiscard]] const HistoryItemsList &forwardItems() const;
|
||||||
[[nodiscard]] FullMsgId replyingToMessage() const;
|
[[nodiscard]] FullMsgId replyingToMessage() const;
|
||||||
[[nodiscard]] rpl::producer<FullMsgId> editMsgId() const;
|
[[nodiscard]] FullMsgId editMsgId() const;
|
||||||
|
[[nodiscard]] rpl::producer<FullMsgId> editMsgIdValue() const;
|
||||||
[[nodiscard]] rpl::producer<FullMsgId> scrollToItemRequests() const;
|
[[nodiscard]] rpl::producer<FullMsgId> scrollToItemRequests() const;
|
||||||
|
[[nodiscard]] rpl::producer<> editPhotoRequests() const;
|
||||||
[[nodiscard]] MessageToEdit queryToEdit();
|
[[nodiscard]] MessageToEdit queryToEdit();
|
||||||
[[nodiscard]] WebPageId webPageId() const;
|
[[nodiscard]] WebPageId webPageId() const;
|
||||||
|
|
||||||
|
@ -425,16 +427,24 @@ private:
|
||||||
HistoryItem *_shownMessage = nullptr;
|
HistoryItem *_shownMessage = nullptr;
|
||||||
Ui::Text::String _shownMessageName;
|
Ui::Text::String _shownMessageName;
|
||||||
Ui::Text::String _shownMessageText;
|
Ui::Text::String _shownMessageText;
|
||||||
|
std::unique_ptr<Ui::SpoilerAnimation> _shownPreviewSpoiler;
|
||||||
|
Ui::Animations::Simple _inPhotoEditOver;
|
||||||
int _shownMessageNameVersion = -1;
|
int _shownMessageNameVersion = -1;
|
||||||
bool _repaintScheduled = false;
|
bool _shownMessageHasPreview : 1 = false;
|
||||||
|
bool _inPhotoEdit : 1 = false;
|
||||||
|
bool _photoEditAllowed : 1 = false;
|
||||||
|
bool _repaintScheduled : 1 = false;
|
||||||
|
bool _inClickable : 1 = false;
|
||||||
|
|
||||||
const not_null<Data::Session*> _data;
|
const not_null<Data::Session*> _data;
|
||||||
const not_null<Ui::IconButton*> _cancel;
|
const not_null<Ui::IconButton*> _cancel;
|
||||||
|
|
||||||
QRect _clickableRect;
|
QRect _clickableRect;
|
||||||
|
QRect _shownMessagePreviewRect;
|
||||||
|
|
||||||
rpl::event_stream<bool> _visibleChanged;
|
rpl::event_stream<bool> _visibleChanged;
|
||||||
rpl::event_stream<FullMsgId> _scrollToItemRequests;
|
rpl::event_stream<FullMsgId> _scrollToItemRequests;
|
||||||
|
rpl::event_stream<> _editPhotoRequests;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -554,26 +564,45 @@ void FieldHeader::init() {
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
setMouseTracking(true);
|
setMouseTracking(true);
|
||||||
const auto inClickable = lifetime().make_state<bool>(false);
|
|
||||||
events(
|
events(
|
||||||
) | rpl::filter([=](not_null<QEvent*> event) {
|
) | rpl::filter([=](not_null<QEvent*> event) {
|
||||||
return ranges::contains(kMouseEvents, event->type())
|
const auto type = event->type();
|
||||||
|
const auto leaving = (type == QEvent::Leave);
|
||||||
|
return (ranges::contains(kMouseEvents, type) || leaving)
|
||||||
&& (isEditingMessage()
|
&& (isEditingMessage()
|
||||||
|| readyToForward()
|
|| readyToForward()
|
||||||
|| replyingToMessage());
|
|| replyingToMessage());
|
||||||
}) | rpl::start_with_next([=](not_null<QEvent*> event) {
|
}) | rpl::start_with_next([=](not_null<QEvent*> event) {
|
||||||
const auto type = event->type();
|
const auto updateOver = [&](bool inClickable, bool inPhotoEdit) {
|
||||||
const auto e = static_cast<QMouseEvent*>(event.get());
|
if (_inClickable != inClickable) {
|
||||||
const auto pos = e ? e->pos() : mapFromGlobal(QCursor::pos());
|
_inClickable = inClickable;
|
||||||
const auto inPreviewRect = _clickableRect.contains(pos);
|
setCursor(_inClickable
|
||||||
|
|
||||||
if (type == QEvent::MouseMove) {
|
|
||||||
if (inPreviewRect != *inClickable) {
|
|
||||||
*inClickable = inPreviewRect;
|
|
||||||
setCursor(*inClickable
|
|
||||||
? style::cur_pointer
|
? style::cur_pointer
|
||||||
: style::cur_default);
|
: style::cur_default);
|
||||||
}
|
}
|
||||||
|
if (_inPhotoEdit != inPhotoEdit) {
|
||||||
|
_inPhotoEdit = inPhotoEdit;
|
||||||
|
_inPhotoEditOver.start(
|
||||||
|
[=] { update(); },
|
||||||
|
_inPhotoEdit ? 0. : 1.,
|
||||||
|
_inPhotoEdit ? 1. : 0.,
|
||||||
|
st::defaultMessageBar.duration);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const auto type = event->type();
|
||||||
|
if (type == QEvent::Leave) {
|
||||||
|
updateOver(false, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto e = static_cast<QMouseEvent*>(event.get());
|
||||||
|
const auto pos = e ? e->pos() : mapFromGlobal(QCursor::pos());
|
||||||
|
const auto inPreviewRect = _clickableRect.contains(pos);
|
||||||
|
const auto inPhotoEdit = _shownMessageHasPreview
|
||||||
|
&& _photoEditAllowed
|
||||||
|
&& _shownMessagePreviewRect.contains(pos);
|
||||||
|
|
||||||
|
if (type == QEvent::MouseMove) {
|
||||||
|
updateOver(inPreviewRect, inPhotoEdit);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto isLeftIcon = (pos.x() < st::historyReplySkip);
|
const auto isLeftIcon = (pos.x() < st::historyReplySkip);
|
||||||
|
@ -582,6 +611,8 @@ void FieldHeader::init() {
|
||||||
if (isLeftButton && isLeftIcon) {
|
if (isLeftButton && isLeftIcon) {
|
||||||
*leftIconPressed = true;
|
*leftIconPressed = true;
|
||||||
update();
|
update();
|
||||||
|
} else if (isLeftButton && inPhotoEdit) {
|
||||||
|
_editPhotoRequests.fire({});
|
||||||
} else if (isLeftButton && inPreviewRect) {
|
} else if (isLeftButton && inPreviewRect) {
|
||||||
if (!isEditingMessage() && readyToForward()) {
|
if (!isEditingMessage() && readyToForward()) {
|
||||||
_forwardPanel->editOptions(_show);
|
_forwardPanel->editOptions(_show);
|
||||||
|
@ -794,20 +825,74 @@ void FieldHeader::paintEditOrReplyToMessage(Painter &p) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto media = _shownMessage->media();
|
||||||
|
_shownMessageHasPreview = media && media->hasReplyPreview();
|
||||||
|
const auto preview = _shownMessageHasPreview
|
||||||
|
? media->replyPreview()
|
||||||
|
: nullptr;
|
||||||
|
const auto spoilered = preview && media->hasSpoiler();
|
||||||
|
if (!spoilered) {
|
||||||
|
_shownPreviewSpoiler = nullptr;
|
||||||
|
} else if (!_shownPreviewSpoiler) {
|
||||||
|
_shownPreviewSpoiler = std::make_unique<Ui::SpoilerAnimation>([=] {
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const auto previewSkipValue = st::msgReplyBarSize.height()
|
||||||
|
+ st::msgReplyBarSkip
|
||||||
|
- st::msgReplyBarSize.width()
|
||||||
|
- st::msgReplyBarPos.x();
|
||||||
|
const auto previewSkip = _shownMessageHasPreview ? previewSkipValue : 0;
|
||||||
|
const auto textLeft = replySkip + previewSkip;
|
||||||
|
const auto textAvailableWidth = availableWidth - previewSkip;
|
||||||
|
if (preview) {
|
||||||
|
const auto overEdit = _photoEditAllowed
|
||||||
|
? _inPhotoEditOver.value(_inPhotoEdit ? 1. : 0.)
|
||||||
|
: 0.;
|
||||||
|
const auto to = QRect(
|
||||||
|
replySkip,
|
||||||
|
st::msgReplyPadding.top(),
|
||||||
|
st::msgReplyBarSize.height(),
|
||||||
|
st::msgReplyBarSize.height());
|
||||||
|
p.drawPixmap(to.x(), to.y(), preview->pixSingle(
|
||||||
|
preview->size() / style::DevicePixelRatio(),
|
||||||
|
{
|
||||||
|
.options = Images::Option::RoundSmall,
|
||||||
|
.outer = to.size(),
|
||||||
|
}));
|
||||||
|
if (_shownPreviewSpoiler) {
|
||||||
|
if (overEdit > 0.) {
|
||||||
|
p.setOpacity(1. - overEdit);
|
||||||
|
}
|
||||||
|
Ui::FillSpoilerRect(
|
||||||
|
p,
|
||||||
|
to,
|
||||||
|
Ui::DefaultImageSpoiler().frame(
|
||||||
|
_shownPreviewSpoiler->index(crl::now(), p.inactive())));
|
||||||
|
}
|
||||||
|
if (overEdit > 0.) {
|
||||||
|
p.setOpacity(overEdit);
|
||||||
|
p.fillRect(to, st::historyEditMediaBg);
|
||||||
|
st::historyEditMedia.paintInCenter(p, to);
|
||||||
|
p.setOpacity(1.);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
p.setPen(st::historyReplyNameFg);
|
p.setPen(st::historyReplyNameFg);
|
||||||
p.setFont(st::msgServiceNameFont);
|
p.setFont(st::msgServiceNameFont);
|
||||||
_shownMessageName.drawElided(
|
_shownMessageName.drawElided(
|
||||||
p,
|
p,
|
||||||
replySkip,
|
textLeft,
|
||||||
st::msgReplyPadding.top(),
|
st::msgReplyPadding.top(),
|
||||||
availableWidth);
|
textAvailableWidth);
|
||||||
|
|
||||||
p.setPen(st::historyComposeAreaFg);
|
p.setPen(st::historyComposeAreaFg);
|
||||||
_shownMessageText.draw(p, {
|
_shownMessageText.draw(p, {
|
||||||
.position = QPoint(
|
.position = QPoint(
|
||||||
replySkip,
|
textLeft,
|
||||||
st::msgReplyPadding.top() + st::msgServiceNameFont->height),
|
st::msgReplyPadding.top() + st::msgServiceNameFont->height),
|
||||||
.availableWidth = availableWidth,
|
.availableWidth = textAvailableWidth,
|
||||||
.palette = &st::historyComposeAreaPalette,
|
.palette = &st::historyComposeAreaPalette,
|
||||||
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
||||||
.now = crl::now(),
|
.now = crl::now(),
|
||||||
|
@ -848,6 +933,10 @@ bool FieldHeader::isEditingMessage() const {
|
||||||
return !!_editMsgId.current();
|
return !!_editMsgId.current();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FullMsgId FieldHeader::editMsgId() const {
|
||||||
|
return _editMsgId.current();
|
||||||
|
}
|
||||||
|
|
||||||
bool FieldHeader::readyToForward() const {
|
bool FieldHeader::readyToForward() const {
|
||||||
return !_forwardPanel->empty();
|
return !_forwardPanel->empty();
|
||||||
}
|
}
|
||||||
|
@ -879,10 +968,21 @@ void FieldHeader::updateControlsGeometry(QSize size) {
|
||||||
0,
|
0,
|
||||||
width() - st::historyReplySkip - _cancel->width(),
|
width() - st::historyReplySkip - _cancel->width(),
|
||||||
height());
|
height());
|
||||||
|
_shownMessagePreviewRect = QRect(
|
||||||
|
st::historyReplySkip,
|
||||||
|
st::msgReplyPadding.top(),
|
||||||
|
st::msgReplyBarSize.height(),
|
||||||
|
st::msgReplyBarSize.height());
|
||||||
}
|
}
|
||||||
|
|
||||||
void FieldHeader::editMessage(FullMsgId id) {
|
void FieldHeader::editMessage(FullMsgId id, bool photoEditAllowed) {
|
||||||
|
_photoEditAllowed = photoEditAllowed;
|
||||||
_editMsgId = id;
|
_editMsgId = id;
|
||||||
|
if (!photoEditAllowed) {
|
||||||
|
_inPhotoEdit = false;
|
||||||
|
_inPhotoEditOver.stop();
|
||||||
|
}
|
||||||
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FieldHeader::replyToMessage(FullMsgId id) {
|
void FieldHeader::replyToMessage(FullMsgId id) {
|
||||||
|
@ -898,7 +998,7 @@ void FieldHeader::updateForwarding(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<FullMsgId> FieldHeader::editMsgId() const {
|
rpl::producer<FullMsgId> FieldHeader::editMsgIdValue() const {
|
||||||
return _editMsgId.value();
|
return _editMsgId.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -906,6 +1006,10 @@ rpl::producer<FullMsgId> FieldHeader::scrollToItemRequests() const {
|
||||||
return _scrollToItemRequests.events();
|
return _scrollToItemRequests.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpl::producer<> FieldHeader::editPhotoRequests() const {
|
||||||
|
return _editPhotoRequests.events();
|
||||||
|
}
|
||||||
|
|
||||||
MessageToEdit FieldHeader::queryToEdit() {
|
MessageToEdit FieldHeader::queryToEdit() {
|
||||||
const auto item = _data->message(_editMsgId.current());
|
const auto item = _data->message(_editMsgId.current());
|
||||||
if (!isEditingMessage() || !item) {
|
if (!isEditingMessage() || !item) {
|
||||||
|
@ -1470,7 +1574,7 @@ void ComposeControls::init() {
|
||||||
paintBackground(clip);
|
paintBackground(clip);
|
||||||
}, _wrap->lifetime());
|
}, _wrap->lifetime());
|
||||||
|
|
||||||
_header->editMsgId(
|
_header->editMsgIdValue(
|
||||||
) | rpl::start_with_next([=](const auto &id) {
|
) | rpl::start_with_next([=](const auto &id) {
|
||||||
unregisterDraftSources();
|
unregisterDraftSources();
|
||||||
updateSendButtonType();
|
updateSendButtonType();
|
||||||
|
@ -1482,6 +1586,16 @@ void ComposeControls::init() {
|
||||||
registerDraftSource();
|
registerDraftSource();
|
||||||
}, _wrap->lifetime());
|
}, _wrap->lifetime());
|
||||||
|
|
||||||
|
_header->editPhotoRequests(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
EditCaptionBox::StartPhotoEdit(
|
||||||
|
_regularWindow,
|
||||||
|
_photoEditMedia,
|
||||||
|
_editingId,
|
||||||
|
_field->getTextWithTags(),
|
||||||
|
crl::guard(_wrap.get(), [=] { cancelEditMessage(); }));
|
||||||
|
}, _wrap->lifetime());
|
||||||
|
|
||||||
_header->previewCancelled(
|
_header->previewCancelled(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
if (_preview) {
|
if (_preview) {
|
||||||
|
@ -1521,7 +1635,7 @@ void ComposeControls::init() {
|
||||||
_voiceRecordBar->requestToSendWithOptions(options);
|
_voiceRecordBar->requestToSendWithOptions(options);
|
||||||
}, _wrap->lifetime());
|
}, _wrap->lifetime());
|
||||||
|
|
||||||
_header->editMsgId(
|
_header->editMsgIdValue(
|
||||||
) | rpl::start_with_next([=](const auto &id) {
|
) | rpl::start_with_next([=](const auto &id) {
|
||||||
_editingId = id;
|
_editingId = id;
|
||||||
}, _wrap->lifetime());
|
}, _wrap->lifetime());
|
||||||
|
@ -2051,7 +2165,43 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (draft == editDraft) {
|
if (draft == editDraft) {
|
||||||
_header->editMessage(editingId);
|
const auto resolve = [=] {
|
||||||
|
if (const auto item = _history->owner().message(editingId)) {
|
||||||
|
const auto media = item->media();
|
||||||
|
_canReplaceMedia = media && media->allowsEditMedia();
|
||||||
|
_photoEditMedia = (_canReplaceMedia
|
||||||
|
&& _regularWindow
|
||||||
|
&& media->photo()
|
||||||
|
&& !media->photo()->isNull())
|
||||||
|
? media->photo()->createMediaView()
|
||||||
|
: nullptr;
|
||||||
|
if (_photoEditMedia) {
|
||||||
|
_photoEditMedia->wanted(
|
||||||
|
Data::PhotoSize::Large,
|
||||||
|
item->fullId());
|
||||||
|
}
|
||||||
|
_header->editMessage(editingId, _photoEditMedia != nullptr);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
_canReplaceMedia = false;
|
||||||
|
_photoEditMedia = nullptr;
|
||||||
|
_header->editMessage(editingId, false);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if (!resolve()) {
|
||||||
|
const auto callback = crl::guard(_header.get(), [=] {
|
||||||
|
if (_header->editMsgId() == editingId
|
||||||
|
&& resolve()
|
||||||
|
&& updateReplaceMediaButton()) {
|
||||||
|
updateControlsVisibility();
|
||||||
|
updateControlsGeometry(_wrap->size());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_history->session().api().requestMessageData(
|
||||||
|
_history->peer,
|
||||||
|
editingId.msg,
|
||||||
|
callback);
|
||||||
|
}
|
||||||
_header->replyToMessage({});
|
_header->replyToMessage({});
|
||||||
} else {
|
} else {
|
||||||
_canReplaceMedia = false;
|
_canReplaceMedia = false;
|
||||||
|
@ -2710,17 +2860,6 @@ void ComposeControls::editMessage(not_null<HistoryItem*> item) {
|
||||||
cursor,
|
cursor,
|
||||||
previewState));
|
previewState));
|
||||||
applyDraft();
|
applyDraft();
|
||||||
|
|
||||||
const auto media = item->media();
|
|
||||||
_canReplaceMedia = media && media->allowsEditMedia();
|
|
||||||
_photoEditMedia = (_canReplaceMedia
|
|
||||||
&& media->photo()
|
|
||||||
&& !media->photo()->isNull())
|
|
||||||
? media->photo()->createMediaView()
|
|
||||||
: nullptr;
|
|
||||||
if (_photoEditMedia) {
|
|
||||||
_photoEditMedia->wanted(Data::PhotoSize::Large, item->fullId());
|
|
||||||
}
|
|
||||||
if (updateReplaceMediaButton()) {
|
if (updateReplaceMediaButton()) {
|
||||||
updateControlsVisibility();
|
updateControlsVisibility();
|
||||||
updateControlsGeometry(_wrap->size());
|
updateControlsGeometry(_wrap->size());
|
||||||
|
|
Loading…
Reference in New Issue