diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index 3aef7bc3f..220f5b7ce 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -54,9 +54,9 @@ void hideSingleUseKeyboard(const HistoryItem *msg) { } } -bool insertBotCommand(const QString &cmd, bool specialGif) { +bool insertBotCommand(const QString &cmd) { if (auto m = main()) { - return m->insertBotCommand(cmd, specialGif); + return m->insertBotCommand(cmd); } return false; } @@ -264,20 +264,6 @@ void repaintHistoryItem(const HistoryItem *item) { } } -void repaintInlineItem(const InlineBots::Layout::ItemBase *layout) { - if (!layout) return; - if (auto main = App::main()) { - main->ui_repaintInlineItem(layout); - } -} - -bool isInlineItemVisible(const InlineBots::Layout::ItemBase *layout) { - if (auto main = App::main()) { - return main->ui_isInlineItemVisible(layout); - } - return false; -} - void autoplayMediaInlineAsync(const FullMsgId &msgId) { if (auto main = App::main()) { QMetaObject::invokeMethod(main, "ui_autoplayMediaInlineAsync", Qt::QueuedConnection, Q_ARG(qint32, msgId.channel), Q_ARG(qint32, msgId.msg)); @@ -386,10 +372,6 @@ void historyItemLayoutChanged(const HistoryItem *item) { if (MainWidget *m = App::main()) m->notify_historyItemLayoutChanged(item); } -void inlineItemLayoutChanged(const InlineBots::Layout::ItemBase *layout) { - if (MainWidget *m = App::main()) m->notify_inlineItemLayoutChanged(layout); -} - void historyMuteUpdated(History *history) { if (MainWidget *m = App::main()) m->notify_historyMuteUpdated(history); } diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index 9dfa264ec..b08ceff1b 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -67,7 +67,7 @@ inline base::lambda_once LambdaDelayedOnce(int duration, PointersAndLamb } void sendBotCommand(PeerData *peer, UserData *bot, const QString &cmd, MsgId replyTo = 0); -bool insertBotCommand(const QString &cmd, bool specialGif = false); +bool insertBotCommand(const QString &cmd); void activateBotCommand(const HistoryItem *msg, int row, int col); void searchByHashtag(const QString &tag, PeerData *inPeer); void openPeerByName(const QString &username, MsgId msgId = ShowAtUnreadMsgId, const QString &startToken = QString()); @@ -109,8 +109,6 @@ bool isMediaViewShown(); bool isInlineItemBeingChosen(); void repaintHistoryItem(const HistoryItem *item); -void repaintInlineItem(const InlineBots::Layout::ItemBase *layout); -bool isInlineItemVisible(const InlineBots::Layout::ItemBase *reader); void autoplayMediaInlineAsync(const FullMsgId &msgId); void showPeerProfile(const PeerId &peer); @@ -180,7 +178,6 @@ void migrateUpdated(PeerData *peer); void clipStopperHidden(ClipStopperType type); void historyItemLayoutChanged(const HistoryItem *item); -void inlineItemLayoutChanged(const InlineBots::Layout::ItemBase *layout); void historyMuteUpdated(History *history); // handle pending resize() / paint() on history items diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 1e46c7ed6..0f15166c5 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -63,6 +63,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "auth_session.h" #include "window/notifications_manager.h" #include "window/window_controller.h" +#include "inline_bots/inline_results_widget.h" namespace { @@ -3427,11 +3428,15 @@ void HistoryWidget::applyInlineBotQuery(UserData *bot, const QString &query) { _inlineBot = bot; inlineBotChanged(); } - if (_inlineBot->username == cInlineGifBotUsername() && query.isEmpty()) { - _emojiPan->clearInlineBot(); - } else { - _emojiPan->queryInlineBot(_inlineBot, _peer, query); + if (!_inlineResults) { + _inlineResults.create(this); + _inlineResults->setResultSelectedCallback([this](InlineBots::Result *result, UserData *bot) { + onInlineResultSend(result, bot); + }); + updateControlsGeometry(); + orderWidgets(); } + _inlineResults->queryInlineBot(_inlineBot, _peer, query); if (!_fieldAutocomplete->isHidden()) { _fieldAutocomplete->hideAnimated(); } @@ -3440,6 +3445,20 @@ void HistoryWidget::applyInlineBotQuery(UserData *bot, const QString &query) { } } +void HistoryWidget::orderWidgets() { + _reportSpamPanel->raise(); + _topShadow->raise(); + if (_membersDropdown) { + _membersDropdown->raise(); + } + if (_inlineResults) { + _inlineResults->raise(); + } + _emojiPan->raise(); + _attachDragDocument->raise(); + _attachDragPhoto->raise(); +} + void HistoryWidget::updateStickersByEmoji() { int len = 0; if (!_editMsgId) { @@ -4557,10 +4576,11 @@ void HistoryWidget::updateNotifySettings() { } bool HistoryWidget::contentOverlapped(const QRect &globalRect) { - return (_attachDragDocument->overlaps(globalRect) || - _attachDragPhoto->overlaps(globalRect) || - _fieldAutocomplete->overlaps(globalRect) || - _emojiPan->overlaps(globalRect)); + return (_attachDragDocument->overlaps(globalRect) + || _attachDragPhoto->overlaps(globalRect) + || _fieldAutocomplete->overlaps(globalRect) + || _emojiPan->overlaps(globalRect) + || (_inlineResults && _inlineResults->overlaps(globalRect))); } void HistoryWidget::updateReportSpamStatus() { @@ -4692,6 +4712,9 @@ void HistoryWidget::updateControlsVisibility() { _botKeyboardHide->hide(); _botCommandStart->hide(); _emojiPan->hide(); + if (_inlineResults) { + _inlineResults->hide(); + } if (_pinnedBar) { _pinnedBar->cancel->hide(); _pinnedBar->shadow->hide(); @@ -4750,6 +4773,9 @@ void HistoryWidget::updateControlsVisibility() { _botKeyboardHide->hide(); _botCommandStart->hide(); _emojiPan->hide(); + if (_inlineResults) { + _inlineResults->hide(); + } if (!_field->isHidden()) { _field->hide(); resizeEvent(0); @@ -4861,6 +4887,9 @@ void HistoryWidget::updateControlsVisibility() { _botKeyboardHide->hide(); _botCommandStart->hide(); _emojiPan->hide(); + if (_inlineResults) { + _inlineResults->hide(); + } _kbScroll->hide(); if (!_field->isHidden()) { _field->hide(); @@ -5379,6 +5408,9 @@ bool HistoryWidget::saveEditMsgFail(History *history, const RPCError &error, mtp void HistoryWidget::hideSelectorControlsAnimated() { _fieldAutocomplete->hideAnimated(); _emojiPan->hideAnimated(); + if (_inlineResults) { + _inlineResults->hideAnimated(); + } } void HistoryWidget::onSend(bool ctrlShiftEnter, MsgId replyTo) { @@ -5971,7 +6003,7 @@ bool HistoryWidget::botCallbackFail(BotCallbackInfo info, const RPCError &error, return true; } -bool HistoryWidget::insertBotCommand(const QString &cmd, bool specialGif) { +bool HistoryWidget::insertBotCommand(const QString &cmd) { if (!canWriteMessage()) return false; bool insertingInlineBot = !cmd.isEmpty() && (cmd.at(0) == '@'); @@ -5989,34 +6021,26 @@ bool HistoryWidget::insertBotCommand(const QString &cmd, bool specialGif) { if (!insertingInlineBot) { auto &textWithTags = _field->getTextWithTags(); - if (specialGif) { - if (textWithTags.text.trimmed() == '@' + cInlineGifBotUsername() && textWithTags.text.at(0) == '@') { - clearFieldText(TextUpdateEvent::SaveDraft, Ui::FlatTextarea::AddToUndoHistory); - } + TextWithTags textWithTagsToSet; + QRegularExpressionMatch m = QRegularExpression(qsl("^/[A-Za-z_0-9]{0,64}(@[A-Za-z_0-9]{0,32})?(\\s|$)")).match(textWithTags.text); + if (m.hasMatch()) { + textWithTagsToSet = _field->getTextWithTagsPart(m.capturedLength()); } else { - TextWithTags textWithTagsToSet; - QRegularExpressionMatch m = QRegularExpression(qsl("^/[A-Za-z_0-9]{0,64}(@[A-Za-z_0-9]{0,32})?(\\s|$)")).match(textWithTags.text); - if (m.hasMatch()) { - textWithTagsToSet = _field->getTextWithTagsPart(m.capturedLength()); - } else { - textWithTagsToSet = textWithTags; - } - textWithTagsToSet.text = toInsert + textWithTagsToSet.text; - for (auto &tag : textWithTagsToSet.tags) { - tag.offset += toInsert.size(); - } - _field->setTextWithTags(textWithTagsToSet); + textWithTagsToSet = textWithTags; + } + textWithTagsToSet.text = toInsert + textWithTagsToSet.text; + for (auto &tag : textWithTagsToSet.tags) { + tag.offset += toInsert.size(); + } + _field->setTextWithTags(textWithTagsToSet); - QTextCursor cur(_field->textCursor()); - cur.movePosition(QTextCursor::End); - _field->setTextCursor(cur); - } + QTextCursor cur(_field->textCursor()); + cur.movePosition(QTextCursor::End); + _field->setTextCursor(cur); } else { - if (!specialGif || _field->isEmpty()) { - setFieldText({ toInsert, TextWithTags::Tags() }, TextUpdateEvent::SaveDraft, Ui::FlatTextarea::AddToUndoHistory); - _field->setFocus(); - return true; - } + setFieldText({ toInsert, TextWithTags::Tags() }, TextUpdateEvent::SaveDraft, Ui::FlatTextarea::AddToUndoHistory); + _field->setFocus(); + return true; } return false; } @@ -6498,7 +6522,7 @@ void HistoryWidget::moveFieldControls() { _kbScroll->setGeometry(0, bottom, width(), keyboardHeight); } -// _attachToggle --------------------------------------------------------- _emojiPan --------- _fieldBarCancel +// _attachToggle -------- _inlineResults ---------------------------------- _emojiPan -------- _fieldBarCancel // (_attachDocument|_attachPhoto) _field (_silent|_cmdStart|_kbShow) (_kbHide|_attachEmoji) [_broadcast] _send // (_botStart|_unblock|_joinChannel|_muteUnmute) @@ -6515,6 +6539,9 @@ void HistoryWidget::moveFieldControls() { _silent->moveToRight(right, buttonsBottom); _fieldBarCancel->moveToRight(0, _field->y() - st::historySendPadding - _fieldBarCancel->height()); + if (_inlineResults) { + _inlineResults->moveBottom(_field->y() - st::historySendPadding); + } _emojiPan->moveBottom(_field->y() - st::historySendPadding); auto fullWidthButtonRect = QRect(0, bottom - _botStart->height(), width(), _botStart->height()); @@ -6546,7 +6573,9 @@ void HistoryWidget::clearInlineBot() { inlineBotChanged(); _field->finishPlaceholder(); } - _emojiPan->clearInlineBot(); + if (_inlineResults) { + _inlineResults->clearInlineBot(); + } onCheckFieldAutocomplete(); } @@ -7132,16 +7161,9 @@ void HistoryWidget::onUpdateHistoryItems() { } } -void HistoryWidget::ui_repaintInlineItem(const InlineBots::Layout::ItemBase *layout) { - _emojiPan->ui_repaintInlineItem(layout); -} - -bool HistoryWidget::ui_isInlineItemVisible(const InlineBots::Layout::ItemBase *layout) { - return _emojiPan->ui_isInlineItemVisible(layout); -} - bool HistoryWidget::ui_isInlineItemBeingChosen() { - return _emojiPan->ui_isInlineItemBeingChosen(); + return _emojiPan->ui_isInlineItemBeingChosen() + || (_inlineResults && _inlineResults->ui_isInlineItemBeingChosen()); } PeerData *HistoryWidget::ui_getPeerForMouseAction() { @@ -7154,10 +7176,6 @@ void HistoryWidget::notify_historyItemLayoutChanged(const HistoryItem *item) { } } -void HistoryWidget::notify_inlineItemLayoutChanged(const InlineBots::Layout::ItemBase *layout) { - _emojiPan->notify_inlineItemLayoutChanged(layout); -} - void HistoryWidget::notify_handlePendingHistoryUpdate() { if (hasPendingResizedItems()) { updateListSize(); @@ -7200,6 +7218,10 @@ void HistoryWidget::updateControlsGeometry() { _emojiPan->setMinTop(0); _emojiPan->setMinBottom(_attachEmoji->height()); + if (_inlineResults) { + _inlineResults->setMinTop(0); + _inlineResults->setMinBottom(_attachEmoji->height()); + } if (_membersDropdown) { _membersDropdown->setMaxHeight(countMembersDropdownHeightMax()); } @@ -7776,14 +7798,7 @@ bool HistoryWidget::pinnedMsgVisibilityUpdated() { _pinnedBar->shadow->show(); } connect(_pinnedBar->cancel, SIGNAL(clicked()), this, SLOT(onPinnedHide())); - _reportSpamPanel->raise(); - _topShadow->raise(); - if (_membersDropdown) { - _membersDropdown->raise(); - } - _emojiPan->raise(); - _attachDragDocument->raise(); - _attachDragPhoto->raise(); + orderWidgets(); updatePinnedBar(); result = true; diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index c9aaecafe..7bc0c190c 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -32,6 +32,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org namespace InlineBots { namespace Layout { class ItemBase; +class Widget; } // namespace Layout class Result; } // namespace InlineBots @@ -661,7 +662,7 @@ public: void sendBotCommand(PeerData *peer, UserData *bot, const QString &cmd, MsgId replyTo); void hideSingleUseKeyboard(PeerData *peer, MsgId replyTo); - bool insertBotCommand(const QString &cmd, bool specialGif); + bool insertBotCommand(const QString &cmd); bool eventFilter(QObject *obj, QEvent *e) override; @@ -712,13 +713,10 @@ public: void app_sendBotCallback(const HistoryMessageReplyMarkup::Button *button, const HistoryItem *msg, int row, int col); void ui_repaintHistoryItem(const HistoryItem *item); - void ui_repaintInlineItem(const InlineBots::Layout::ItemBase *gif); - bool ui_isInlineItemVisible(const InlineBots::Layout::ItemBase *layout); bool ui_isInlineItemBeingChosen(); PeerData *ui_getPeerForMouseAction(); void notify_historyItemLayoutChanged(const HistoryItem *item); - void notify_inlineItemLayoutChanged(const InlineBots::Layout::ItemBase *layout); void notify_botCommandsChanged(UserData *user); void notify_inlineBotRequesting(bool requesting); void notify_replyMarkupUpdated(const HistoryItem *item); @@ -848,6 +846,15 @@ private slots: void updateField(); private: + struct SendingFilesLists { + QList nonLocalUrls; + QStringList directories; + QStringList emptyFiles; + QStringList tooLargeFiles; + QStringList filesToSend; + bool allFilesForCompress = true; + }; + void topBarClick(); void animationCallback(); @@ -858,14 +865,6 @@ private: void chooseAttach(); void historyDownAnimationFinish(); void sendButtonClicked(); - struct SendingFilesLists { - QList nonLocalUrls; - QStringList directories; - QStringList emptyFiles; - QStringList tooLargeFiles; - QStringList filesToSend; - bool allFilesForCompress = true; - }; SendingFilesLists getSendingFilesLists(const QList &files); SendingFilesLists getSendingFilesLists(const QStringList &files); void getSendingLocalFileInfo(SendingFilesLists &result, const QString &filepath); @@ -887,6 +886,7 @@ private: bool historyHasNotFreezedUnreadBar(History *history) const; bool canWriteMessage() const; + void orderWidgets(); void clearInlineBot(); void inlineBotChanged(); @@ -1159,6 +1159,7 @@ private: object_ptr _membersDropdown = { nullptr }; QTimer _membersDropdownShowTimer; + object_ptr _inlineResults = { nullptr }; object_ptr _emojiPan; DragState _attachDrag = DragStateNone; object_ptr _attachDragDocument, _attachDragPhoto; diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index 90ebf7b84..d0316c9da 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -37,10 +37,10 @@ namespace InlineBots { namespace Layout { namespace internal { -FileBase::FileBase(Result *result) : ItemBase(result) { +FileBase::FileBase(gsl::not_null context, Result *result) : ItemBase(context, result) { } -FileBase::FileBase(DocumentData *document) : ItemBase(document) { +FileBase::FileBase(gsl::not_null context, DocumentData *document) : ItemBase(context, document) { } DocumentData *FileBase::getShownDocument() const { @@ -94,11 +94,13 @@ ImagePtr FileBase::content_thumb() const { return getResultThumb(); } -Gif::Gif(Result *result) : FileBase(result) { +Gif::Gif(gsl::not_null context, Result *result) : FileBase(context, result) { } -Gif::Gif(DocumentData *document, bool hasDeleteButton) : FileBase(document) -, _delete(hasDeleteButton ? new DeleteSavedGifClickHandler(document) : nullptr) { +Gif::Gif(gsl::not_null context, DocumentData *document, bool hasDeleteButton) : FileBase(context, document) { + if (hasDeleteButton) { + _delete = MakeShared(document); + } } void Gif::initDimensions() { @@ -338,7 +340,7 @@ void Gif::clipCallback(Media::Clip::Notification notification) { int32 height = st::inlineMediaHeight; QSize frame = countFrameSize(); _gif->start(frame.width(), frame.height(), _width, height, ImageRoundRadius::None, ImageRoundCorner::None); - } else if (_gif->autoPausedGif() && !Ui::isInlineItemVisible(this)) { + } else if (_gif->autoPausedGif() && !context()->inlineItemVisible(this)) { _gif.reset(); getShownDocument()->forget(); } @@ -355,7 +357,7 @@ void Gif::clipCallback(Media::Clip::Notification notification) { } } -Sticker::Sticker(Result *result) : FileBase(result) { +Sticker::Sticker(gsl::not_null context, Result *result) : FileBase(context, result) { } void Sticker::initDimensions() { @@ -455,7 +457,7 @@ void Sticker::prepareThumb() const { } } -Photo::Photo(Result *result) : ItemBase(result) { +Photo::Photo(gsl::not_null context, Result *result) : ItemBase(context, result) { } void Photo::initDimensions() { @@ -551,7 +553,7 @@ void Photo::prepareThumb(int32 width, int32 height, const QSize &frame) const { } } -Video::Video(Result *result) : FileBase(result) +Video::Video(gsl::not_null context, Result *result) : FileBase(context, result) , _link(getResultContentUrlHandler()) , _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) , _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) { @@ -668,11 +670,11 @@ void CancelFileClickHandler::onClickImpl() const { _result->cancelFile(); } -File::File(Result *result) : FileBase(result) +File::File(gsl::not_null context, Result *result) : FileBase(context, result) , _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::msgFileSize - st::inlineThumbSkip) , _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::msgFileSize - st::inlineThumbSkip) -, _open(new OpenFileClickHandler(result)) -, _cancel(new CancelFileClickHandler(result)) { +, _open(MakeShared(result)) +, _cancel(MakeShared(result)) { updateStatusText(); regDocumentItem(getShownDocument(), this); } @@ -789,12 +791,12 @@ File::~File() { } void File::thumbAnimationCallback() { - Ui::repaintInlineItem(this); + update(); } void File::step_radial(TimeMs ms, bool timer) { if (timer) { - Ui::repaintInlineItem(this); + update(); } else { DocumentData *document = getShownDocument(); _animation->radial.update(document->progress(), !document->loading() || document->loaded(), ms); @@ -806,7 +808,7 @@ void File::step_radial(TimeMs ms, bool timer) { void File::ensureAnimation() const { if (!_animation) { - _animation.reset(new AnimationData(animation(const_cast(this), &File::step_radial))); + _animation = std::make_unique(animation(const_cast(this), &File::step_radial)); } } @@ -877,7 +879,7 @@ void File::setStatusSize(int32 newSize, int32 fullSize, int32 duration, qint64 r } } -Contact::Contact(Result *result) : ItemBase(result) +Contact::Contact(gsl::not_null context, Result *result) : ItemBase(context, result) , _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) , _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) { } @@ -966,7 +968,7 @@ void Contact::prepareThumb(int width, int height) const { } } -Article::Article(Result *result, bool withThumb) : ItemBase(result) +Article::Article(gsl::not_null context, Result *result, bool withThumb) : ItemBase(context, result) , _url(getResultUrlHandler()) , _link(getResultContentUrlHandler()) , _withThumb(withThumb) @@ -974,7 +976,7 @@ Article::Article(Result *result, bool withThumb) : ItemBase(result) , _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) { LocationCoords location; if (!_link && result->getLocationCoords(&location)) { - _link.reset(new LocationClickHandler(location)); + _link = MakeShared(location); } _thumbLetter = getResultThumbLetter(); } @@ -1113,7 +1115,7 @@ void Article::prepareThumb(int width, int height) const { } } -Game::Game(Result *result) : ItemBase(result) +Game::Game(gsl::not_null context, Result *result) : ItemBase(context, result) , _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) , _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) { countFrameSize(); @@ -1322,7 +1324,7 @@ void Game::clipCallback(Media::Clip::Notification notification) { getResultDocument()->forget(); } else if (_gif->ready() && !_gif->started()) { _gif->start(_frameSize.width(), _frameSize.height(), st::inlineThumbSize, st::inlineThumbSize, ImageRoundRadius::None, ImageRoundCorner::None); - } else if (_gif->autoPausedGif() && !Ui::isInlineItemVisible(this)) { + } else if (_gif->autoPausedGif() && !context()->inlineItemVisible(this)) { _gif.reset(); getResultDocument()->forget(); } diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h index 0f19325dd..7685fdb2e 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h @@ -30,9 +30,9 @@ namespace internal { class FileBase : public ItemBase { public: - FileBase(Result *result); + FileBase(gsl::not_null context, Result *result); // for saved gif layouts - FileBase(DocumentData *doc); + FileBase(gsl::not_null context, DocumentData *doc); protected: DocumentData *getShownDocument() const; @@ -58,8 +58,8 @@ private: class Gif : public FileBase { public: - Gif(Result *result); - Gif(DocumentData *doc, bool hasDeleteButton); + Gif(gsl::not_null context, Result *result); + Gif(gsl::not_null context, DocumentData *doc, bool hasDeleteButton); void setPosition(int32 position) override; void initDimensions() override; @@ -117,9 +117,9 @@ private: class Photo : public ItemBase { public: - Photo(Result *result); + Photo(gsl::not_null context, Result *result); // Not used anywhere currently. - //LayoutInlinePhoto(PhotoData *photo); + //Photo(gsl::not_null context, PhotoData *photo); void initDimensions() override; @@ -146,9 +146,9 @@ private: class Sticker : public FileBase { public: - Sticker(Result *result); + Sticker(gsl::not_null context, Result *result); // Not used anywhere currently. - //LayoutInlineSticker(DocumentData *document); + //Sticker(gsl::not_null context, DocumentData *document); void initDimensions() override; @@ -167,7 +167,6 @@ public: void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; private: - QSize getThumbSize() const; mutable Animation _a_over; @@ -181,7 +180,7 @@ private: class Video : public FileBase { public: - Video(Result *result); + Video(gsl::not_null context, Result *result); void initDimensions() override; @@ -189,7 +188,6 @@ public: void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override; private: - ClickHandlerPtr _link; mutable QPixmap _thumb; @@ -229,7 +227,7 @@ private: class File : public FileBase { public: - File(Result *result); + File(gsl::not_null context, Result *result); void initDimensions() override; @@ -291,7 +289,7 @@ private: class Contact : public ItemBase { public: - Contact(Result *result); + Contact(gsl::not_null context, Result *result); void initDimensions() override; int resizeGetHeight(int width) override; @@ -300,7 +298,6 @@ public: void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override; private: - mutable QPixmap _thumb; Text _title, _description; @@ -310,7 +307,7 @@ private: class Article : public ItemBase { public: - Article(Result *result, bool withThumb); + Article(gsl::not_null context, Result *result, bool withThumb); void initDimensions() override; int resizeGetHeight(int width) override; @@ -319,7 +316,6 @@ public: void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override; private: - ClickHandlerPtr _url, _link; bool _withThumb; @@ -334,7 +330,7 @@ private: class Game : public ItemBase { public: - Game(Result *result); + Game(gsl::not_null context, Result *result); void setPosition(int32 position) override; void initDimensions() override; diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp index d1a003c9c..f79ef4e0c 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp @@ -95,31 +95,37 @@ void ItemBase::preload() const { void ItemBase::update() { if (_position >= 0) { - Ui::repaintInlineItem(this); + context()->inlineItemRepaint(this); } } -std::unique_ptr ItemBase::createLayout(Result *result, bool forceThumb) { +void ItemBase::layoutChanged() { + if (_position >= 0) { + context()->inlineItemLayoutChanged(this); + } +} + +std::unique_ptr ItemBase::createLayout(gsl::not_null context, Result *result, bool forceThumb) { using Type = Result::Type; switch (result->_type) { - case Type::Photo: return std::make_unique(result); break; + case Type::Photo: return std::make_unique(context, result); break; case Type::Audio: - case Type::File: return std::make_unique(result); break; - case Type::Video: return std::make_unique(result); break; - case Type::Sticker: return std::make_unique(result); break; - case Type::Gif: return std::make_unique(result); break; + case Type::File: return std::make_unique(context, result); break; + case Type::Video: return std::make_unique(context, result); break; + case Type::Sticker: return std::make_unique(context, result); break; + case Type::Gif: return std::make_unique(context, result); break; case Type::Article: case Type::Geo: - case Type::Venue: return std::make_unique(result, forceThumb); break; - case Type::Game: return std::make_unique(result); break; - case Type::Contact: return std::make_unique(result); break; + case Type::Venue: return std::make_unique(context, result, forceThumb); break; + case Type::Game: return std::make_unique(context, result); break; + case Type::Contact: return std::make_unique(context, result); break; } return std::unique_ptr(); } -std::unique_ptr ItemBase::createLayoutGif(DocumentData *document) { - return std::make_unique(document, true); +std::unique_ptr ItemBase::createLayoutGif(gsl::not_null context, DocumentData *document) { + return std::make_unique(context, document, true); } DocumentData *ItemBase::getResultDocument() const { diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.h b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.h index c284bac06..810405c93 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.h +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.h @@ -47,15 +47,21 @@ public: } }; +class Context { +public: + virtual void inlineItemLayoutChanged(const ItemBase *layout) = 0; + virtual bool inlineItemVisible(const ItemBase *item) = 0; + virtual void inlineItemRepaint(const ItemBase *item) = 0; +}; + class ItemBase : public LayoutItemBase { public: - - ItemBase(Result *result) : _result(result) { + ItemBase(gsl::not_null context, Result *result) : _context(context), _result(result) { } - ItemBase(DocumentData *doc) : _doc(doc) { + ItemBase(gsl::not_null context, DocumentData *doc) : _context(context), _doc(doc) { } // Not used anywhere currently. - //ItemBase(PhotoData *photo) : _photo(photo) { + //ItemBase(gsl::not_null context, PhotoData *photo) : _context(context), _photo(photo) { //} virtual void paint(Painter &p, const QRect &clip, const PaintContext *context) const = 0; @@ -82,6 +88,7 @@ public: virtual void preload() const; void update(); + void layoutChanged(); // ClickHandlerHost interface void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override { @@ -91,8 +98,8 @@ public: update(); } - static std::unique_ptr createLayout(Result *result, bool forceThumb); - static std::unique_ptr createLayoutGif(DocumentData *document); + static std::unique_ptr createLayout(gsl::not_null context, Result *result, bool forceThumb); + static std::unique_ptr createLayoutGif(gsl::not_null context, DocumentData *document); protected: DocumentData *getResultDocument() const; @@ -105,6 +112,10 @@ protected: ClickHandlerPtr getResultContentUrlHandler() const; QString getResultThumbLetter() const; + gsl::not_null context() const { + return _context; + } + Result *_result = nullptr; DocumentData *_doc = nullptr; PhotoData *_photo = nullptr; @@ -113,6 +124,9 @@ protected: int _position = 0; // < 0 means removed from layout +private: + gsl::not_null _context; + }; using DocumentItems = QMap>; diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp index 152229340..91c320be2 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp @@ -121,13 +121,13 @@ std::unique_ptr Result::create(uint64 queryId, const MTPBotInlineResult auto &r = message->c_botInlineMessageMediaAuto(); if (result->_type == Type::Photo) { result->createPhoto(); - result->sendData.reset(new internal::SendPhoto(result->_photo, qs(r.vcaption))); + result->sendData = std::make_unique(result->_photo, qs(r.vcaption)); } else if (result->_type == Type::Game) { result->createGame(); - result->sendData.reset(new internal::SendGame(result->_game)); + result->sendData = std::make_unique(result->_game); } else { result->createDocument(); - result->sendData.reset(new internal::SendFile(result->_document, qs(r.vcaption))); + result->sendData = std::make_unique(result->_document, qs(r.vcaption)); } if (r.has_reply_markup()) { result->_mtpKeyboard = std::make_unique(r.vreply_markup); @@ -137,7 +137,7 @@ std::unique_ptr Result::create(uint64 queryId, const MTPBotInlineResult case mtpc_botInlineMessageText: { auto &r = message->c_botInlineMessageText(); auto entities = r.has_entities() ? entitiesFromMTP(r.ventities.v) : EntitiesInText(); - result->sendData.reset(new internal::SendText(qs(r.vmessage), entities, r.is_no_webpage())); + result->sendData = std::make_unique(qs(r.vmessage), entities, r.is_no_webpage()); if (result->_type == Type::Photo) { result->createPhoto(); } else if (result->_type == Type::Audio || result->_type == Type::File || result->_type == Type::Video || result->_type == Type::Sticker || result->_type == Type::Gif) { @@ -151,7 +151,7 @@ std::unique_ptr Result::create(uint64 queryId, const MTPBotInlineResult case mtpc_botInlineMessageMediaGeo: { auto &r = message->c_botInlineMessageMediaGeo(); if (r.vgeo.type() == mtpc_geoPoint) { - result->sendData.reset(new internal::SendGeo(r.vgeo.c_geoPoint())); + result->sendData = std::make_unique(r.vgeo.c_geoPoint()); } else { badAttachment = true; } @@ -163,7 +163,7 @@ std::unique_ptr Result::create(uint64 queryId, const MTPBotInlineResult case mtpc_botInlineMessageMediaVenue: { auto &r = message->c_botInlineMessageMediaVenue(); if (r.vgeo.type() == mtpc_geoPoint) { - result->sendData.reset(new internal::SendVenue(r.vgeo.c_geoPoint(), qs(r.vvenue_id), qs(r.vprovider), qs(r.vtitle), qs(r.vaddress))); + result->sendData = std::make_unique(r.vgeo.c_geoPoint(), qs(r.vvenue_id), qs(r.vprovider), qs(r.vtitle), qs(r.vaddress)); } else { badAttachment = true; } @@ -174,7 +174,7 @@ std::unique_ptr Result::create(uint64 queryId, const MTPBotInlineResult case mtpc_botInlineMessageMediaContact: { auto &r = message->c_botInlineMessageMediaContact(); - result->sendData.reset(new internal::SendContact(qs(r.vfirst_name), qs(r.vlast_name), qs(r.vphone_number))); + result->sendData = std::make_unique(qs(r.vfirst_name), qs(r.vlast_name), qs(r.vphone_number)); if (r.has_reply_markup()) { result->_mtpKeyboard = std::make_unique(r.vreply_markup); } diff --git a/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp b/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp new file mode 100644 index 000000000..be7ec30d7 --- /dev/null +++ b/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp @@ -0,0 +1,1128 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org +*/ +#include "inline_bots/inline_results_widget.h" + +#include "styles/style_stickers.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/shadow.h" +#include "ui/effects/ripple_animation.h" +#include "boxes/confirmbox.h" +#include "inline_bots/inline_bot_result.h" +#include "inline_bots/inline_bot_layout_item.h" +#include "dialogs/dialogs_layout.h" +#include "historywidget.h" +#include "storage/localstorage.h" +#include "lang.h" +#include "mainwindow.h" +#include "apiwrap.h" +#include "mainwidget.h" +#include "auth_session.h" + +namespace InlineBots { +namespace Layout { +namespace internal { + +Inner::Inner(QWidget *parent) : TWidget(parent) { + setMaxHeight(st::emojiPanMaxHeight - st::emojiCategory.height); + + setMouseTracking(true); + setAttribute(Qt::WA_OpaquePaintEvent); + + _previewTimer.setSingleShot(true); + connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreview())); + + _updateInlineItems.setSingleShot(true); + connect(&_updateInlineItems, SIGNAL(timeout()), this, SLOT(onUpdateInlineItems())); + + subscribe(AuthSession::CurrentDownloaderTaskFinished(), [this] { + update(); + }); +} + +void Inner::setMaxHeight(int maxHeight) { + _maxHeight = maxHeight; + resize(st::emojiPanWidth - st::emojiScroll.width - st::buttonRadius, countHeight()); +} + +void Inner::setVisibleTopBottom(int visibleTop, int visibleBottom) { + _visibleBottom = visibleBottom; + if (_visibleTop != visibleTop) { + _visibleTop = visibleTop; + _lastScrolled = getms(); + } +} + +int Inner::countHeight(bool plain) { + auto result = st::stickerPanPadding; + auto minLastH = plain ? 0 : (_maxHeight - st::stickerPanPadding); + if (_switchPmButton) { + result += _switchPmButton->height() + st::inlineResultsSkip; + } + for (int i = 0, l = _rows.count(); i < l; ++i) { + result += _rows[i].height; + } + return qMax(minLastH, result) + st::stickerPanPadding; +} + +Inner::~Inner() = default; + +void Inner::paintEvent(QPaintEvent *e) { + Painter p(this); + QRect r = e ? e->rect() : rect(); + if (r != rect()) { + p.setClipRect(r); + } + p.fillRect(r, st::emojiPanBg); + + paintInlineItems(p, r); +} + +void Inner::paintInlineItems(Painter &p, const QRect &r) { + if (_rows.isEmpty() && !_switchPmButton) { + p.setFont(st::normalFont); + p.setPen(st::noContactsColor); + p.drawText(QRect(0, 0, width(), (height() / 3) * 2 + st::normalFont->height), lang(lng_inline_bot_no_results), style::al_center); + return; + } + auto gifPaused = Ui::isLayerShown() || Ui::isMediaViewShown() || _previewShown || !App::wnd()->isActive(); + InlineBots::Layout::PaintContext context(getms(), false, gifPaused, false); + + auto top = st::stickerPanPadding; + if (_switchPmButton) { + top += _switchPmButton->height() + st::inlineResultsSkip; + } + + auto fromx = rtl() ? (width() - r.x() - r.width()) : r.x(); + auto tox = rtl() ? (width() - r.x()) : (r.x() + r.width()); + for (auto row = 0, rows = _rows.size(); row != rows; ++row) { + auto &inlineRow = _rows[row]; + if (top >= r.top() + r.height()) break; + if (top + inlineRow.height > r.top()) { + auto left = st::inlineResultsLeft - st::buttonRadius; + if (row == rows - 1) context.lastRow = true; + for (int col = 0, cols = inlineRow.items.size(); col < cols; ++col) { + if (left >= tox) break; + + auto item = inlineRow.items.at(col); + auto w = item->width(); + if (left + w > fromx) { + p.translate(left, top); + item->paint(p, r.translated(-left, -top), &context); + p.translate(-left, -top); + } + left += w; + if (item->hasRightSkip()) { + left += st::inlineResultsSkip; + } + } + } + top += inlineRow.height; + } +} + +void Inner::mousePressEvent(QMouseEvent *e) { + if (e->button() != Qt::LeftButton) { + return; + } + _lastMousePos = e->globalPos(); + updateSelected(); + + _pressed = _selected; + ClickHandler::pressed(); + _previewTimer.start(QApplication::startDragTime()); +} + +void Inner::mouseReleaseEvent(QMouseEvent *e) { + _previewTimer.stop(); + + auto pressed = std::exchange(_pressed, -1); + auto activated = ClickHandler::unpressed(); + + if (_previewShown) { + _previewShown = false; + return; + } + + _lastMousePos = e->globalPos(); + updateSelected(); + + if (_selected < 0 || _selected != pressed || !activated) { + return; + } + + if (dynamic_cast(activated.data())) { + int row = _selected / MatrixRowShift, column = _selected % MatrixRowShift; + selectInlineResult(row, column); + } else { + App::activateClickHandler(activated, e->button()); + } +} + +void Inner::selectInlineResult(int row, int column) { + if (row >= _rows.size() || column >= _rows.at(row).items.size()) { + return; + } + + auto item = _rows[row].items[column]; + if (auto inlineResult = item->getResult()) { + if (inlineResult->onChoose(item)) { + _resultSelectedCallback(inlineResult, _inlineBot); + } + } +} + +void Inner::mouseMoveEvent(QMouseEvent *e) { + _lastMousePos = e->globalPos(); + updateSelected(); +} + +void Inner::leaveEventHook(QEvent *e) { + clearSelection(); +} + +void Inner::leaveToChildEvent(QEvent *e, QWidget *child) { + clearSelection(); +} + +void Inner::enterFromChildEvent(QEvent *e, QWidget *child) { + _lastMousePos = QCursor::pos(); + updateSelected(); +} + +void Inner::clearSelection() { + if (_selected >= 0) { + int srow = _selected / MatrixRowShift, scol = _selected % MatrixRowShift; + t_assert(srow >= 0 && srow < _rows.size() && scol >= 0 && scol < _rows.at(srow).items.size()); + ClickHandler::clearActive(_rows.at(srow).items.at(scol)); + setCursor(style::cur_default); + } + _selected = _pressed = -1; + update(); +} + +void Inner::hideFinish(bool completely) { + if (completely) { + auto itemForget = [](auto &item) { + if (auto document = item->getDocument()) { + document->forget(); + } + if (auto photo = item->getPhoto()) { + photo->forget(); + } + if (auto result = item->getResult()) { + result->forget(); + } + }; + clearInlineRows(false); + for_const (auto &item, _inlineLayouts) { + itemForget(item.second); + } + } +} + +bool Inner::inlineRowsAddItem(Result *result, Row &row, int32 &sumWidth) { + auto layout = layoutPrepareInlineResult(result, (_rows.size() * MatrixRowShift) + row.items.size()); + if (!layout) return false; + + layout->preload(); + if (inlineRowFinalize(row, sumWidth, layout->isFullLine())) { + layout->setPosition(_rows.size() * MatrixRowShift); + } + + sumWidth += layout->maxWidth(); + if (!row.items.isEmpty() && row.items.back()->hasRightSkip()) { + sumWidth += st::inlineResultsSkip; + } + + row.items.push_back(layout); + return true; +} + +bool Inner::inlineRowFinalize(Row &row, int32 &sumWidth, bool force) { + if (row.items.isEmpty()) return false; + + auto full = (row.items.size() >= kInlineItemsMaxPerRow); + auto big = (sumWidth >= st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft); + if (full || big || force) { + _rows.push_back(layoutInlineRow(row, (full || big) ? sumWidth : 0)); + row = Row(); + row.items.reserve(kInlineItemsMaxPerRow); + sumWidth = 0; + return true; + } + return false; +} + +void Inner::inlineBotChanged() { + refreshInlineRows(nullptr, nullptr, true); +} + +void Inner::clearInlineRows(bool resultsDeleted) { + if (resultsDeleted) { + _selected = _pressed = -1; + } else { + clearSelection(); + for_const (auto &row, _rows) { + for_const (auto &item, row.items) { + item->setPosition(-1); + } + } + } + _rows.clear(); +} + +ItemBase *Inner::layoutPrepareInlineResult(Result *result, int32 position) { + auto it = _inlineLayouts.find(result); + if (it == _inlineLayouts.cend()) { + if (auto layout = ItemBase::createLayout(this, result, _inlineWithThumb)) { + it = _inlineLayouts.emplace(result, std::move(layout)).first; + it->second->initDimensions(); + } else { + return nullptr; + } + } + if (!it->second->maxWidth()) { + return nullptr; + } + + it->second->setPosition(position); + return it->second.get(); +} + +void Inner::deleteUnusedInlineLayouts() { + if (_rows.isEmpty()) { // delete all + _inlineLayouts.clear(); + } else { + for (auto i = _inlineLayouts.begin(); i != _inlineLayouts.cend();) { + if (i->second->position() < 0) { + i = _inlineLayouts.erase(i); + } else { + ++i; + } + } + } +} + +Inner::Row &Inner::layoutInlineRow(Row &row, int32 sumWidth) { + auto count = int(row.items.size()); + t_assert(count <= kInlineItemsMaxPerRow); + + // enumerate items in the order of growing maxWidth() + // for that sort item indices by maxWidth() + int indices[kInlineItemsMaxPerRow]; + for (auto i = 0; i != count; ++i) { + indices[i] = i; + } + std::sort(indices, indices + count, [&row](int a, int b) -> bool { + return row.items.at(a)->maxWidth() < row.items.at(b)->maxWidth(); + }); + + row.height = 0; + int availw = width() - (st::inlineResultsLeft - st::buttonRadius); + for (int i = 0; i < count; ++i) { + int index = indices[i]; + int w = sumWidth ? (row.items.at(index)->maxWidth() * availw / sumWidth) : row.items.at(index)->maxWidth(); + int actualw = qMax(w, int(st::inlineResultsMinWidth)); + row.height = qMax(row.height, row.items.at(index)->resizeGetHeight(actualw)); + if (sumWidth) { + availw -= actualw; + sumWidth -= row.items.at(index)->maxWidth(); + if (index > 0 && row.items.at(index - 1)->hasRightSkip()) { + availw -= st::inlineResultsSkip; + sumWidth -= st::inlineResultsSkip; + } + } + } + return row; +} + +void Inner::preloadImages() { + for (auto row = 0, rows = _rows.size(); row != rows; ++row) { + for (auto col = 0, cols = _rows[row].items.size(); col != cols; ++col) { + _rows[row].items[col]->preload(); + } + } +} + +void Inner::hideInlineRowsPanel() { + clearInlineRows(false); +} + +void Inner::clearInlineRowsPanel() { + clearInlineRows(false); +} + +void Inner::refreshSwitchPmButton(const CacheEntry *entry) { + if (!entry || entry->switchPmText.isEmpty()) { + _switchPmButton.destroy(); + _switchPmStartToken.clear(); + } else { + if (!_switchPmButton) { + _switchPmButton.create(this, QString(), st::switchPmButton); + _switchPmButton->show(); + _switchPmButton->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); + connect(_switchPmButton, SIGNAL(clicked()), this, SLOT(onSwitchPm())); + } + _switchPmButton->setText(entry->switchPmText); // doesn't perform text.toUpper() + _switchPmStartToken = entry->switchPmStartToken; + auto buttonTop = st::stickerPanPadding; + _switchPmButton->move(st::inlineResultsLeft - st::buttonRadius, buttonTop); + } + update(); +} + +int Inner::refreshInlineRows(UserData *bot, const CacheEntry *entry, bool resultsDeleted) { + _inlineBot = bot; + refreshSwitchPmButton(entry); + auto clearResults = [this, entry]() { + if (!entry) { + return true; + } + if (entry->results.empty() && entry->switchPmText.isEmpty()) { + return true; + } + return false; + }; + if (clearResults()) { + if (resultsDeleted) { + clearInlineRows(true); + deleteUnusedInlineLayouts(); + } + emit emptyInlineRows(); + return 0; + } + + clearSelection(); + + t_assert(_inlineBot != 0); + _inlineBotTitle = lng_inline_bot_results(lt_inline_bot, _inlineBot->username.isEmpty() ? _inlineBot->name : ('@' + _inlineBot->username)); + + auto count = int(entry->results.size()); + auto from = validateExistingInlineRows(entry->results); + auto added = 0; + + if (count) { + _rows.reserve(count); + auto row = Row(); + row.items.reserve(kInlineItemsMaxPerRow); + auto sumWidth = 0; + for (auto i = from; i != count; ++i) { + if (inlineRowsAddItem(entry->results[i].get(), row, sumWidth)) { + ++added; + } + } + inlineRowFinalize(row, sumWidth, true); + } + + int32 h = countHeight(); + if (h != height()) resize(width(), h); + update(); + + _lastMousePos = QCursor::pos(); + updateSelected(); + + return added; +} + +int Inner::validateExistingInlineRows(const Results &results) { + int count = results.size(), until = 0, untilrow = 0, untilcol = 0; + for (; until < count;) { + if (untilrow >= _rows.size() || _rows[untilrow].items[untilcol]->getResult() != results[until].get()) { + break; + } + ++until; + if (++untilcol == _rows[untilrow].items.size()) { + ++untilrow; + untilcol = 0; + } + } + if (until == count) { // all items are layed out + if (untilrow == _rows.size()) { // nothing changed + return until; + } + + for (int i = untilrow, l = _rows.size(), skip = untilcol; i < l; ++i) { + for (int j = 0, s = _rows[i].items.size(); j < s; ++j) { + if (skip) { + --skip; + } else { + _rows[i].items[j]->setPosition(-1); + } + } + } + if (!untilcol) { // all good rows are filled + _rows.resize(untilrow); + return until; + } + _rows.resize(untilrow + 1); + _rows[untilrow].items.resize(untilcol); + _rows[untilrow] = layoutInlineRow(_rows[untilrow]); + return until; + } + if (untilrow && !untilcol) { // remove last row, maybe it is not full + --untilrow; + untilcol = _rows[untilrow].items.size(); + } + until -= untilcol; + + for (int i = untilrow, l = _rows.size(); i < l; ++i) { + for (int j = 0, s = _rows[i].items.size(); j < s; ++j) { + _rows[i].items[j]->setPosition(-1); + } + } + _rows.resize(untilrow); + + if (_rows.isEmpty()) { + _inlineWithThumb = false; + for (int i = until; i < count; ++i) { + if (results.at(i)->hasThumbDisplay()) { + _inlineWithThumb = true; + break; + } + } + } + return until; +} + +void Inner::inlineItemLayoutChanged(const ItemBase *layout) { + if (_selected < 0 || !isVisible()) { + return; + } + + int row = _selected / MatrixRowShift, col = _selected % MatrixRowShift; + if (row < _rows.size() && col < _rows.at(row).items.size()) { + if (layout == _rows.at(row).items.at(col)) { + updateSelected(); + } + } +} + +void Inner::inlineItemRepaint(const ItemBase *layout) { + auto ms = getms(); + if (_lastScrolled + 100 <= ms) { + update(); + } else { + _updateInlineItems.start(_lastScrolled + 100 - ms); + } +} + +bool Inner::inlineItemVisible(const ItemBase *layout) { + int32 position = layout->position(); + if (position < 0 || !isVisible()) { + return false; + } + + int row = position / MatrixRowShift, col = position % MatrixRowShift; + t_assert((row < _rows.size()) && (col < _rows[row].items.size())); + + auto &inlineItems = _rows[row].items; + int top = st::stickerPanPadding; + for (int32 i = 0; i < row; ++i) { + top += _rows.at(i).height; + } + + return (top < _visibleTop + _maxHeight) && (top + _rows[row].items[col]->height() > _visibleTop); +} + +bool Inner::ui_isInlineItemBeingChosen() { + return true; +} + +void Inner::updateSelected() { + if (_pressed >= 0 && !_previewShown) { + return; + } + + auto newSelected = -1; + auto p = mapFromGlobal(_lastMousePos); + + int sx = (rtl() ? width() - p.x() : p.x()) - (st::inlineResultsLeft - st::buttonRadius); + int sy = p.y() - st::stickerPanPadding; + if (_switchPmButton) { + sy -= _switchPmButton->height() + st::inlineResultsSkip; + } + int row = -1, col = -1, sel = -1; + ClickHandlerPtr lnk; + ClickHandlerHost *lnkhost = nullptr; + HistoryCursorState cursor = HistoryDefaultCursorState; + if (sy >= 0) { + row = 0; + for (int rows = _rows.size(); row < rows; ++row) { + if (sy < _rows.at(row).height) { + break; + } + sy -= _rows.at(row).height; + } + } + if (sx >= 0 && row >= 0 && row < _rows.size()) { + auto &inlineItems = _rows[row].items; + col = 0; + for (int cols = inlineItems.size(); col < cols; ++col) { + int width = inlineItems.at(col)->width(); + if (sx < width) { + break; + } + sx -= width; + if (inlineItems.at(col)->hasRightSkip()) { + sx -= st::inlineResultsSkip; + } + } + if (col < inlineItems.size()) { + sel = row * MatrixRowShift + col; + inlineItems.at(col)->getState(lnk, cursor, sx, sy); + lnkhost = inlineItems.at(col); + } else { + row = col = -1; + } + } else { + row = col = -1; + } + int srow = (_selected >= 0) ? (_selected / MatrixRowShift) : -1; + int scol = (_selected >= 0) ? (_selected % MatrixRowShift) : -1; + if (_selected != sel) { + if (srow >= 0 && scol >= 0) { + t_assert(srow >= 0 && srow < _rows.size() && scol >= 0 && scol < _rows.at(srow).items.size()); + _rows[srow].items[scol]->update(); + } + _selected = sel; + if (row >= 0 && col >= 0) { + t_assert(row >= 0 && row < _rows.size() && col >= 0 && col < _rows.at(row).items.size()); + _rows[row].items[col]->update(); + } + if (_previewShown && _selected >= 0 && _pressed != _selected) { + _pressed = _selected; + if (row >= 0 && col >= 0) { + auto layout = _rows.at(row).items.at(col); + if (auto previewDocument = layout->getPreviewDocument()) { + Ui::showMediaPreview(previewDocument); + } else if (auto previewPhoto = layout->getPreviewPhoto()) { + Ui::showMediaPreview(previewPhoto); + } + } + } + } + if (ClickHandler::setActive(lnk, lnkhost)) { + setCursor(lnk ? style::cur_pointer : style::cur_default); + } +} + +void Inner::onPreview() { + if (_pressed < 0) return; + + int row = _pressed / MatrixRowShift, col = _pressed % MatrixRowShift; + if (row < _rows.size() && col < _rows.at(row).items.size()) { + auto layout = _rows.at(row).items.at(col); + if (auto previewDocument = layout->getPreviewDocument()) { + Ui::showMediaPreview(previewDocument); + _previewShown = true; + } else if (auto previewPhoto = layout->getPreviewPhoto()) { + Ui::showMediaPreview(previewPhoto); + _previewShown = true; + } + } +} + +void Inner::onUpdateInlineItems() { + auto ms = getms(); + if (_lastScrolled + 100 <= ms) { + update(); + } else { + _updateInlineItems.start(_lastScrolled + 100 - ms); + } +} + +void Inner::onSwitchPm() { + if (_inlineBot && _inlineBot->botInfo) { + _inlineBot->botInfo->startToken = _switchPmStartToken; + Ui::showPeerHistory(_inlineBot, ShowAndStartBotMsgId); + } +} + +} // namespace internal + +Widget::Widget(QWidget *parent) : TWidget(parent) +, _contentMaxHeight(st::emojiPanMaxHeight) +, _contentHeight(_contentMaxHeight) +, _scroll(this, st::inlineBotsScroll) { + resize(QRect(0, 0, st::emojiPanWidth, _contentHeight).marginsAdded(innerPadding()).size()); + _width = width(); + _height = height(); + + _scroll->resize(st::emojiPanWidth - st::buttonRadius, _contentHeight); + + _scroll->move(verticalRect().topLeft()); + _inner = _scroll->setOwnedWidget(object_ptr(this)); + + _inner->moveToLeft(0, 0, _scroll->width()); + + connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); + + connect(_inner, SIGNAL(emptyInlineRows()), this, SLOT(onEmptyInlineRows())); + + // inline bots + _inlineRequestTimer.setSingleShot(true); + connect(&_inlineRequestTimer, SIGNAL(timeout()), this, SLOT(onInlineRequest())); + + if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) { + connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWndActiveChanged())); + } + + // Inner widget has OpaquePaintEvent attribute so it doesn't repaint on scroll. + // But we should force it to repaint so that GIFs will continue to animate without update() calls. + // We do that by creating a transparent widget above our _inner. + auto forceRepaintOnScroll = object_ptr(this); + forceRepaintOnScroll->setGeometry(innerRect().x() + st::buttonRadius, innerRect().y() + st::buttonRadius, st::buttonRadius, st::buttonRadius); + forceRepaintOnScroll->setAttribute(Qt::WA_TransparentForMouseEvents); + forceRepaintOnScroll->show(); + + setMouseTracking(true); + setAttribute(Qt::WA_OpaquePaintEvent, false); +} + +void Widget::setMinTop(int minTop) { + _minTop = minTop; + updateContentHeight(); +} + +void Widget::setMinBottom(int minBottom) { + _minBottom = minBottom; + updateContentHeight(); +} + +void Widget::moveBottom(int bottom) { + _bottom = bottom; + updateContentHeight(); +} + +void Widget::updateContentHeight() { + auto wantedBottom = countBottom(); + auto maxContentHeight = wantedBottom - st::emojiPanMargins.top() - st::emojiPanMargins.bottom(); + auto contentHeight = qMin(_contentMaxHeight, maxContentHeight); + auto resultTop = wantedBottom - st::emojiPanMargins.bottom() - contentHeight - st::emojiPanMargins.top(); + accumulate_max(resultTop, _minTop); + auto hs = contentHeight; + if (contentHeight == _contentHeight) { + move(x(), resultTop); + return; + } + + auto was = _contentHeight; + _contentHeight = contentHeight; + + resize(QRect(0, 0, innerRect().width(), _contentHeight).marginsAdded(innerPadding()).size()); + _height = height(); + move(x(), resultTop); + + if (was > _contentHeight) { + _scroll->resize(_scroll->width(), _contentHeight); + _inner->setMaxHeight(_contentHeight); + } else { + _inner->setMaxHeight(_contentHeight); + _scroll->resize(_scroll->width(), _contentHeight); + } + + update(); +} + +void Widget::onWndActiveChanged() { + if (!App::wnd()->windowHandle()->isActive() && !isHidden()) { + leaveEvent(0); + } +} + +void Widget::paintEvent(QPaintEvent *e) { + Painter p(this); + + auto ms = getms(); + + // This call can finish _a_show animation and destroy _showAnimation. + auto opacityAnimating = _a_opacity.animating(ms); + + auto showAnimating = _a_show.animating(ms); + if (_showAnimation && !showAnimating) { + _showAnimation.reset(); + if (!opacityAnimating) { + showChildren(); + } + } + + if (showAnimating) { + t_assert(_showAnimation != nullptr); + if (auto opacity = _a_opacity.current(_hiding ? 0. : 1.)) { + _showAnimation->paintFrame(p, 0, 0, width(), _a_show.current(1.), opacity); + } + } else if (opacityAnimating) { + p.setOpacity(_a_opacity.current(_hiding ? 0. : 1.)); + p.drawPixmap(0, 0, _cache); + } else if (_hiding || isHidden()) { + hideFinished(); + } else { + if (!_cache.isNull()) _cache = QPixmap(); + if (!_inPanelGrab) Ui::Shadow::paint(p, innerRect(), width(), st::emojiPanAnimation.shadow); + paintContent(p); + } +} + +void Widget::paintContent(Painter &p) { + auto inner = innerRect(); + App::roundRect(p, inner, st::emojiPanBg, ImageRoundRadius::Small, App::RectPart::TopFull | App::RectPart::BottomFull); + + auto horizontal = horizontalRect(); + auto sidesTop = horizontal.y(); + auto sidesHeight = horizontal.height(); + p.fillRect(myrtlrect(inner.x() + inner.width() - st::emojiScroll.width, sidesTop, st::emojiScroll.width, sidesHeight), st::emojiPanBg); + p.fillRect(myrtlrect(inner.x(), sidesTop, st::buttonRadius, sidesHeight), st::emojiPanBg); +} + +int Widget::countBottom() const { + return _bottom; +} + +void Widget::moveByBottom() { + moveToLeft(0, y()); + updateContentHeight(); +} + +void Widget::hideFast() { + if (isHidden()) return; + + _hiding = false; + _a_opacity.finish(); + hideFinished(); +} + +void Widget::opacityAnimationCallback() { + update(); + if (!_a_opacity.animating()) { + if (_hiding) { + _hiding = false; + hideFinished(); + } else if (!_a_show.animating()) { + showChildren(); + } + } +} + +void Widget::prepareCache() { + if (_a_opacity.animating()) return; + + auto showAnimation = base::take(_a_show); + auto showAnimationData = base::take(_showAnimation); + showChildren(); + _cache = myGrab(this); + _showAnimation = base::take(showAnimationData); + _a_show = base::take(showAnimation); + if (_a_show.animating()) { + hideChildren(); + } +} + +void Widget::startOpacityAnimation(bool hiding) { + _hiding = false; + prepareCache(); + _hiding = hiding; + hideChildren(); + _a_opacity.start([this] { opacityAnimationCallback(); }, _hiding ? 1. : 0., _hiding ? 0. : 1., st::emojiPanDuration); +} + +void Widget::startShowAnimation() { + if (!_a_show.animating()) { + auto cache = base::take(_cache); + auto opacityAnimation = base::take(_a_opacity); + showChildren(); + auto image = grabForPanelAnimation(); + _a_opacity = base::take(opacityAnimation); + _cache = base::take(_cache); + + _showAnimation = std::make_unique(st::emojiPanAnimation, Ui::PanelAnimation::Origin::BottomLeft); + auto inner = rect().marginsRemoved(st::emojiPanMargins); + _showAnimation->setFinalImage(std::move(image), QRect(inner.topLeft() * cIntRetinaFactor(), inner.size() * cIntRetinaFactor())); + auto corners = App::cornersMask(ImageRoundRadius::Small); + _showAnimation->setCornerMasks(QImage(*corners[0]), QImage(*corners[1]), QImage(*corners[2]), QImage(*corners[3])); + _showAnimation->start(); + } + hideChildren(); + _a_show.start([this] { update(); }, 0., 1., st::emojiPanShowDuration); +} + +QImage Widget::grabForPanelAnimation() { + myEnsureResized(this); + auto result = QImage(size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); + result.setDevicePixelRatio(cRetinaFactor()); + result.fill(Qt::transparent); + _inPanelGrab = true; + render(&result); + _inPanelGrab = false; + return result; +} + +void Widget::hideAnimated() { + if (isHidden()) return; + if (_hiding) return; + + startOpacityAnimation(true); +} + +Widget::~Widget() = default; + +void Widget::hideFinished() { + hide(); + _inner->hideFinish(true); + _a_show.finish(); + _showAnimation.reset(); + _cache = QPixmap(); + _horizontal = false; + _hiding = false; + + _scroll->scrollToY(0); + + Notify::clipStopperHidden(ClipStopperSavedGifsPanel); +} + +void Widget::showAnimated() { + showStarted(); +} + +void Widget::showStarted() { + if (isHidden()) { + recountContentMaxHeight(); + _inner->preloadImages(); + moveByBottom(); + show(); + startShowAnimation(); + } else if (_hiding) { + startOpacityAnimation(false); + } +} + +bool Widget::ui_isInlineItemBeingChosen() { + if (!isHidden()) { + return _inner->ui_isInlineItemBeingChosen(); + } + return false; +} + +void Widget::onScroll() { + auto st = _scroll->scrollTop(); + if (st + _scroll->height() > _scroll->scrollTopMax()) { + onInlineRequest(); + } + _inner->setVisibleTopBottom(st, st + _scroll->height()); +} + +style::margins Widget::innerPadding() const { + return st::emojiPanMargins; +} + +QRect Widget::innerRect() const { + return rect().marginsRemoved(innerPadding()); +} + +QRect Widget::horizontalRect() const { + return innerRect().marginsRemoved(style::margins(0, st::buttonRadius, 0, st::buttonRadius)); +} + +QRect Widget::verticalRect() const { + return innerRect().marginsRemoved(style::margins(st::buttonRadius, 0, st::buttonRadius, 0)); +} + +void Widget::clearInlineBot() { + inlineBotChanged(); +} + +bool Widget::overlaps(const QRect &globalRect) const { + if (isHidden() || !_cache.isNull()) return false; + + auto testRect = QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size()); + auto inner = rect().marginsRemoved(st::emojiPanMargins); + return inner.marginsRemoved(QMargins(st::buttonRadius, 0, st::buttonRadius, 0)).contains(testRect) + || inner.marginsRemoved(QMargins(0, st::buttonRadius, 0, st::buttonRadius)).contains(testRect); +} + +void Widget::inlineBotChanged() { + if (!_inlineBot) return; + + if (!isHidden() && !_hiding) { + hideAnimated(); + } + + if (_inlineRequestId) MTP::cancel(_inlineRequestId); + _inlineRequestId = 0; + _inlineQuery = _inlineNextQuery = _inlineNextOffset = QString(); + _inlineBot = nullptr; + _inlineCache.clear(); + _inner->inlineBotChanged(); + _inner->hideInlineRowsPanel(); + + Notify::inlineBotRequesting(false); +} + +void Widget::inlineResultsDone(const MTPmessages_BotResults &result) { + _inlineRequestId = 0; + Notify::inlineBotRequesting(false); + + auto it = _inlineCache.find(_inlineQuery); + auto adding = (it != _inlineCache.cend()); + if (result.type() == mtpc_messages_botResults) { + auto &d = result.c_messages_botResults(); + auto &v = d.vresults.v; + auto queryId = d.vquery_id.v; + + if (it == _inlineCache.cend()) { + it = _inlineCache.emplace(_inlineQuery, std::make_unique()).first; + } + auto entry = it->second.get(); + entry->nextOffset = qs(d.vnext_offset); + if (d.has_switch_pm() && d.vswitch_pm.type() == mtpc_inlineBotSwitchPM) { + auto &switchPm = d.vswitch_pm.c_inlineBotSwitchPM(); + entry->switchPmText = qs(switchPm.vtext); + entry->switchPmStartToken = qs(switchPm.vstart_param); + } + + if (auto count = v.size()) { + entry->results.reserve(entry->results.size() + count); + } + auto added = 0; + for_const (const auto &res, v) { + if (auto result = InlineBots::Result::create(queryId, res)) { + ++added; + entry->results.push_back(std::move(result)); + } + } + + if (!added) { + entry->nextOffset = QString(); + } + } else if (adding) { + it->second->nextOffset = QString(); + } + + if (!showInlineRows(!adding)) { + it->second->nextOffset = QString(); + } + onScroll(); +} + +void Widget::queryInlineBot(UserData *bot, PeerData *peer, QString query) { + bool force = false; + _inlineQueryPeer = peer; + if (bot != _inlineBot) { + inlineBotChanged(); + _inlineBot = bot; + force = true; + //if (_inlineBot->isBotInlineGeo()) { + // Ui::show(Box(lang(lng_bot_inline_geo_unavailable))); + //} + } + //if (_inlineBot && _inlineBot->isBotInlineGeo()) { + // return; + //} + + if (_inlineQuery != query || force) { + if (_inlineRequestId) { + MTP::cancel(_inlineRequestId); + _inlineRequestId = 0; + Notify::inlineBotRequesting(false); + } + if (_inlineCache.find(query) != _inlineCache.cend()) { + _inlineRequestTimer.stop(); + _inlineQuery = _inlineNextQuery = query; + showInlineRows(true); + } else { + _inlineNextQuery = query; + _inlineRequestTimer.start(InlineBotRequestDelay); + } + } +} + +void Widget::onInlineRequest() { + if (_inlineRequestId || !_inlineBot || !_inlineQueryPeer) return; + _inlineQuery = _inlineNextQuery; + + QString nextOffset; + auto it = _inlineCache.find(_inlineQuery); + if (it != _inlineCache.cend()) { + nextOffset = it->second->nextOffset; + if (nextOffset.isEmpty()) return; + } + Notify::inlineBotRequesting(true); + _inlineRequestId = request(MTPmessages_GetInlineBotResults(MTP_flags(0), _inlineBot->inputUser, _inlineQueryPeer->input, MTPInputGeoPoint(), MTP_string(_inlineQuery), MTP_string(nextOffset))).done([this](const MTPmessages_BotResults &result, mtpRequestId requestId) { + inlineResultsDone(result); + }).fail([this](const RPCError &error) { + // show error? + Notify::inlineBotRequesting(false); + _inlineRequestId = 0; + }).handleAllErrors().send(); +} + +void Widget::onEmptyInlineRows() { + hideAnimated(); + _inner->clearInlineRowsPanel(); +} + +bool Widget::refreshInlineRows(int *added) { + auto it = _inlineCache.find(_inlineQuery); + const internal::CacheEntry *entry = nullptr; + if (it != _inlineCache.cend()) { + if (!it->second->results.empty() || !it->second->switchPmText.isEmpty()) { + entry = it->second.get(); + } + _inlineNextOffset = it->second->nextOffset; + } + if (!entry) prepareCache(); + auto result = _inner->refreshInlineRows(_inlineBot, entry, false); + if (added) *added = result; + return (entry != nullptr); +} + +int Widget::showInlineRows(bool newResults) { + auto added = 0; + auto clear = !refreshInlineRows(&added); + if (newResults) { + _scroll->scrollToY(0); + } + + bool hidden = isHidden(); + if (!hidden && !clear) { + recountContentMaxHeight(); + } + if (clear) { + if (!hidden) { + hideAnimated(); + } else if (!_hiding) { + _cache = QPixmap(); // clear after refreshInlineRows() + } + } else { + if (hidden || _hiding) { + showAnimated(); + } + } + + return added; +} + +void Widget::recountContentMaxHeight() { + _contentMaxHeight = qMin(_inner->countHeight(true), st::emojiPanMaxHeight); + updateContentHeight(); +} + +} // namespace Layout +} // namespace InlineBots diff --git a/Telegram/SourceFiles/inline_bots/inline_results_widget.h b/Telegram/SourceFiles/inline_bots/inline_results_widget.h new file mode 100644 index 000000000..8523ac900 --- /dev/null +++ b/Telegram/SourceFiles/inline_bots/inline_results_widget.h @@ -0,0 +1,274 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "ui/twidget.h" +#include "ui/abstract_button.h" +#include "ui/effects/panel_animation.h" +#include "mtproto/sender.h" +#include "inline_bots/inline_bot_layout_item.h" + +namespace Ui { +class ScrollArea; +class IconButton; +class LinkButton; +class RoundButton; +class RippleAnimation; +} // namesapce Ui + +namespace InlineBots { + +class Result; + +namespace Layout { + +class ItemBase; + +namespace internal { + +constexpr int kInlineItemsMaxPerRow = 5; + +using Results = std::vector>; + +struct CacheEntry { + QString nextOffset; + QString switchPmText, switchPmStartToken; + Results results; +}; + +class Inner : public TWidget, public Context, private base::Subscriber { + Q_OBJECT + +public: + Inner(QWidget *parent); + + void setMaxHeight(int maxHeight); + + void hideFinish(bool completely); + + void clearSelection(); + + int refreshInlineRows(UserData *bot, const CacheEntry *results, bool resultsDeleted); + void inlineBotChanged(); + void hideInlineRowsPanel(); + void clearInlineRowsPanel(); + + void setVisibleTopBottom(int visibleTop, int visibleBottom) override; + void preloadImages(); + + void inlineItemLayoutChanged(const ItemBase *layout) override; + void inlineItemRepaint(const ItemBase *layout) override; + bool inlineItemVisible(const ItemBase *layout) override; + bool ui_isInlineItemBeingChosen(); + + int countHeight(bool plain = false); + + void setResultSelectedCallback(base::lambda callback) { + _resultSelectedCallback = std::move(callback); + } + + ~Inner(); + +protected: + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void paintEvent(QPaintEvent *e) override; + void leaveEventHook(QEvent *e) override; + void leaveToChildEvent(QEvent *e, QWidget *child) override; + void enterFromChildEvent(QEvent *e, QWidget *child) override; + +private slots: + void onPreview(); + void onUpdateInlineItems(); + void onSwitchPm(); + +signals: + void emptyInlineRows(); + +private: + static constexpr bool kRefreshIconsScrollAnimation = true; + static constexpr bool kRefreshIconsNoAnimation = false; + + void updateSelected(); + + void paintInlineItems(Painter &p, const QRect &r); + + void refreshSwitchPmButton(const CacheEntry *entry); + + int32 _maxHeight; + + int _visibleTop = 0; + int _visibleBottom = 0; + + UserData *_inlineBot; + QString _inlineBotTitle; + TimeMs _lastScrolled = 0; + QTimer _updateInlineItems; + bool _inlineWithThumb = false; + + object_ptr _switchPmButton = { nullptr }; + QString _switchPmStartToken; + + struct Row { + int height = 0; + QVector items; + }; + QVector _rows; + void clearInlineRows(bool resultsDeleted); + + std::map> _inlineLayouts; + ItemBase *layoutPrepareInlineResult(Result *result, int32 position); + + bool inlineRowsAddItem(Result *result, Row &row, int32 &sumWidth); + bool inlineRowFinalize(Row &row, int32 &sumWidth, bool force = false); + + Row &layoutInlineRow(Row &row, int32 sumWidth = 0); + void deleteUnusedInlineLayouts(); + + int validateExistingInlineRows(const Results &results); + void selectInlineResult(int row, int column); + + int _selected = -1; + int _pressed = -1; + QPoint _lastMousePos; + + QTimer _previewTimer; + bool _previewShown = false; + + base::lambda _resultSelectedCallback; + +}; + +} // namespace internal + +class Widget : public TWidget, private MTP::Sender { + Q_OBJECT + +public: + Widget(QWidget *parent); + + void setMinTop(int minTop); + void setMinBottom(int minBottom); + void moveBottom(int bottom); + + void hideFast(); + bool hiding() const { + return _hiding; + } + + void queryInlineBot(UserData *bot, PeerData *peer, QString query); + void clearInlineBot(); + + bool overlaps(const QRect &globalRect) const; + + bool ui_isInlineItemBeingChosen(); + + void showAnimated(); + void hideAnimated(); + + void setResultSelectedCallback(base::lambda callback) { + _inner->setResultSelectedCallback(std::move(callback)); + } + + ~Widget(); + +protected: + void paintEvent(QPaintEvent *e) override; + +private slots: + void onWndActiveChanged(); + + void onScroll(); + + void onInlineRequest(); + void onEmptyInlineRows(); + +private: + int countBottom() const; + void moveByBottom(); + void paintContent(Painter &p); + + style::margins innerPadding() const; + + // Rounded rect which has shadow around it. + QRect innerRect() const; + + // Inner rect with removed st::buttonRadius from top and bottom. + // This one is allowed to be not rounded. + QRect horizontalRect() const; + + // Inner rect with removed st::buttonRadius from left and right. + // This one is allowed to be not rounded. + QRect verticalRect() const; + + QImage grabForPanelAnimation(); + void startShowAnimation(); + void startOpacityAnimation(bool hiding); + void prepareCache(); + + class Container; + void opacityAnimationCallback(); + + void hideFinished(); + void showStarted(); + + void updateContentHeight(); + + void inlineBotChanged(); + int showInlineRows(bool newResults); + void recountContentMaxHeight(); + bool refreshInlineRows(int *added = nullptr); + void inlineResultsDone(const MTPmessages_BotResults &result); + + int _minTop = 0; + int _minBottom = 0; + int _contentMaxHeight = 0; + int _contentHeight = 0; + bool _horizontal = false; + + int _width = 0; + int _height = 0; + int _bottom = 0; + + std::unique_ptr _showAnimation; + Animation _a_show; + + bool _hiding = false; + QPixmap _cache; + Animation _a_opacity; + bool _inPanelGrab = false; + + object_ptr _scroll; + QPointer _inner; + + std::map> _inlineCache; + QTimer _inlineRequestTimer; + + UserData *_inlineBot = nullptr; + PeerData *_inlineQueryPeer = nullptr; + QString _inlineQuery, _inlineNextQuery, _inlineNextOffset; + mtpRequestId _inlineRequestId = 0; + +}; + +} // namespace Layout +} // namespace InlineBots diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 3ad48209f..1fa4fa648 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -554,14 +554,6 @@ void MainWidget::ui_repaintHistoryItem(const HistoryItem *item) { if (_overview) _overview->ui_repaintHistoryItem(item); } -void MainWidget::ui_repaintInlineItem(const InlineBots::Layout::ItemBase *layout) { - _history->ui_repaintInlineItem(layout); -} - -bool MainWidget::ui_isInlineItemVisible(const InlineBots::Layout::ItemBase *layout) { - return _history->ui_isInlineItemVisible(layout); -} - bool MainWidget::ui_isInlineItemBeingChosen() { return _history->ui_isInlineItemBeingChosen(); } @@ -571,10 +563,6 @@ void MainWidget::notify_historyItemLayoutChanged(const HistoryItem *item) { if (_overview) _overview->notify_historyItemLayoutChanged(item); } -void MainWidget::notify_inlineItemLayoutChanged(const InlineBots::Layout::ItemBase *layout) { - _history->notify_inlineItemLayoutChanged(layout); -} - void MainWidget::notify_historyMuteUpdated(History *history) { _dialogs->notify_historyMuteUpdated(history); } @@ -1339,8 +1327,8 @@ void MainWidget::app_sendBotCallback(const HistoryMessageReplyMarkup::Button *bu _history->app_sendBotCallback(button, msg, row, col); } -bool MainWidget::insertBotCommand(const QString &cmd, bool specialGif) { - return _history->insertBotCommand(cmd, specialGif); +bool MainWidget::insertBotCommand(const QString &cmd) { + return _history->insertBotCommand(cmd); } void MainWidget::searchMessages(const QString &query, PeerData *inPeer) { @@ -1564,7 +1552,7 @@ void MainWidget::handleAudioUpdate(const AudioMsgId &audioId) { } if (auto items = InlineBots::Layout::documentItems()) { for (auto item : items->value(audioId.audio())) { - Ui::repaintInlineItem(item); + item->update(); } } } diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index d71c9daef..8c3a89432 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -306,7 +306,7 @@ public: void sendBotCommand(PeerData *peer, UserData *bot, const QString &cmd, MsgId replyTo); void hideSingleUseKeyboard(PeerData *peer, MsgId replyTo); - bool insertBotCommand(const QString &cmd, bool specialGif); + bool insertBotCommand(const QString &cmd); void jumpToDate(PeerData *peer, const QDate &date); void searchMessages(const QString &query, PeerData *inPeer); @@ -383,8 +383,6 @@ public: void app_sendBotCallback(const HistoryMessageReplyMarkup::Button *button, const HistoryItem *msg, int row, int col); void ui_repaintHistoryItem(const HistoryItem *item); - void ui_repaintInlineItem(const InlineBots::Layout::ItemBase *layout); - bool ui_isInlineItemVisible(const InlineBots::Layout::ItemBase *layout); bool ui_isInlineItemBeingChosen(); void ui_showPeerHistory(quint64 peer, qint32 msgId, Ui::ShowWay way); PeerData *ui_getPeerForMouseAction(); @@ -399,7 +397,6 @@ public: void notify_migrateUpdated(PeerData *peer); void notify_clipStopperHidden(ClipStopperType type); void notify_historyItemLayoutChanged(const HistoryItem *item); - void notify_inlineItemLayoutChanged(const InlineBots::Layout::ItemBase *layout); void notify_historyMuteUpdated(History *history); void notify_handlePendingHistoryUpdate(); diff --git a/Telegram/SourceFiles/settings.h b/Telegram/SourceFiles/settings.h index 6c131ec35..76a66bafe 100644 --- a/Telegram/SourceFiles/settings.h +++ b/Telegram/SourceFiles/settings.h @@ -61,9 +61,6 @@ DeclareSetting(uint64, RealBetaVersion); DeclareSetting(QByteArray, BetaPrivateKey); DeclareSetting(bool, TestMode); -inline QString cInlineGifBotUsername() { - return cTestMode() ? qstr("contextbot") : qstr("gif"); -} DeclareSetting(QString, LoggedPhoneNumber); DeclareSetting(bool, AutoStart); DeclareSetting(bool, StartMinimized); diff --git a/Telegram/SourceFiles/stickers/emoji_pan.cpp b/Telegram/SourceFiles/stickers/emoji_pan.cpp index a74be354f..657211850 100644 --- a/Telegram/SourceFiles/stickers/emoji_pan.cpp +++ b/Telegram/SourceFiles/stickers/emoji_pan.cpp @@ -1304,10 +1304,6 @@ void StickerPanInner::hideFinish(bool completely) { } clearInstalledLocally(); } - if (_setGifCommand && _section == Section::Gifs) { - App::insertBotCommand(qsl(""), true); - } - _setGifCommand = false; // Reset to the recent stickers section. if (_section == Section::Featured) { @@ -1420,7 +1416,6 @@ void StickerPanInner::refreshSavedGifs() { } void StickerPanInner::inlineBotChanged() { - _setGifCommand = false; refreshInlineRows(nullptr, nullptr, true); } @@ -1445,7 +1440,7 @@ void StickerPanInner::clearInlineRows(bool resultsDeleted) { InlineItem *StickerPanInner::layoutPrepareSavedGif(DocumentData *doc, int32 position) { auto it = _gifLayouts.find(doc); if (it == _gifLayouts.cend()) { - if (auto layout = InlineItem::createLayoutGif(doc)) { + if (auto layout = InlineItem::createLayoutGif(this, doc)) { it = _gifLayouts.emplace(doc, std::move(layout)).first; it->second->initDimensions(); } else { @@ -1461,7 +1456,7 @@ InlineItem *StickerPanInner::layoutPrepareSavedGif(DocumentData *doc, int32 posi InlineItem *StickerPanInner::layoutPrepareInlineResult(InlineResult *result, int32 position) { auto it = _inlineLayouts.find(result); if (it == _inlineLayouts.cend()) { - if (auto layout = InlineItem::createLayout(result, _inlineWithThumb)) { + if (auto layout = InlineItem::createLayout(this, result, _inlineWithThumb)) { it = _inlineLayouts.emplace(result, std::move(layout)).first; it->second->initDimensions(); } else { @@ -1632,7 +1627,7 @@ int StickerPanInner::refreshInlineRows(UserData *bot, const InlineCacheEntry *en return true; } if (entry->results.empty() && entry->switchPmText.isEmpty()) { - if (!_inlineBot || _inlineBot->username != cInlineGifBotUsername()) { + if (!_inlineBot) { return true; } } @@ -1745,8 +1740,8 @@ int StickerPanInner::validateExistingInlineRows(const InlineResults &results) { return until; } -void StickerPanInner::notify_inlineItemLayoutChanged(const InlineItem *layout) { - if (_selected < 0 || !showingInlineItems()) { +void StickerPanInner::inlineItemLayoutChanged(const InlineItem *layout) { + if (_selected < 0 || !showingInlineItems() || !isVisible()) { return; } @@ -1758,7 +1753,7 @@ void StickerPanInner::notify_inlineItemLayoutChanged(const InlineItem *layout) { } } -void StickerPanInner::ui_repaintInlineItem(const InlineItem *layout) { +void StickerPanInner::inlineItemRepaint(const InlineItem *layout) { auto ms = getms(); if (_lastScrolled + 100 <= ms) { update(); @@ -1767,9 +1762,9 @@ void StickerPanInner::ui_repaintInlineItem(const InlineItem *layout) { } } -bool StickerPanInner::ui_isInlineItemVisible(const InlineItem *layout) { +bool StickerPanInner::inlineItemVisible(const InlineItem *layout) { int32 position = layout->position(); - if (!showingInlineItems() || position < 0) { + if (!showingInlineItems() || position < 0 || !isVisible()) { return false; } @@ -2015,12 +2010,12 @@ void StickerPanInner::updateSelected() { if (_selected != sel) { if (srow >= 0 && scol >= 0) { t_assert(srow >= 0 && srow < _inlineRows.size() && scol >= 0 && scol < _inlineRows.at(srow).items.size()); - Ui::repaintInlineItem(_inlineRows.at(srow).items.at(scol)); + _inlineRows[srow].items[scol]->update(); } _selected = sel; if (row >= 0 && col >= 0) { t_assert(row >= 0 && row < _inlineRows.size() && col >= 0 && col < _inlineRows.at(row).items.size()); - Ui::repaintInlineItem(_inlineRows.at(row).items.at(col)); + _inlineRows[row].items[col]->update(); } if (_previewShown && _selected >= 0 && _pressed != _selected) { _pressed = _selected; @@ -2193,16 +2188,10 @@ void StickerPanInner::showStickerSet(uint64 setId) { refreshSavedGifs(); emit scrollToY(0); emit scrollUpdated(); - showFinish(); return; } if (showingInlineItems()) { - if (_setGifCommand && _section == Section::Gifs) { - App::insertBotCommand(qsl(""), true); - } - _setGifCommand = false; - cSetShowingSavedGifs(false); emit saveConfigDelayed(kSaveRecentEmojiTimeout); Notify::clipStopperHidden(ClipStopperSavedGifsPanel); @@ -2259,12 +2248,6 @@ void StickerPanInner::updateShowingSavedGifs() { } } -void StickerPanInner::showFinish() { - if (_section == Section::Gifs) { - _setGifCommand = App::insertBotCommand('@' + cInlineGifBotUsername(), true); - } -} - EmojiPanel::EmojiPanel(QWidget *parent, const QString &text, uint64 setId, bool special, int32 wantedY) : TWidget(parent) , _wantedY(wantedY) , _setId(setId) @@ -2388,22 +2371,6 @@ void EmojiSwitchButton::paintEvent(QPaintEvent *e) { namespace { -FORCE_INLINE uint32 twoImagesOnBgWithAlpha( - const anim::Shifted shiftedBg, - const uint32 source1Alpha, - const uint32 source2Alpha, - const uint32 source1, - const uint32 source2, - const uint32 alpha) { - auto source1Pattern = anim::reshifted(anim::shifted(source1) * source1Alpha); - auto bg1Alpha = 256 - anim::getAlpha(source1Pattern); - auto mixed1Pattern = anim::reshifted(shiftedBg * bg1Alpha) + source1Pattern; - auto source2Pattern = anim::reshifted(anim::shifted(source2) * source2Alpha); - auto bg2Alpha = 256 - anim::getAlpha(source2Pattern); - auto mixed2Pattern = anim::reshifted(mixed1Pattern * bg2Alpha) + source2Pattern; - return anim::unshifted(mixed2Pattern * alpha); -} - FORCE_INLINE uint32 oneImageOnBgWithAlpha( const anim::Shifted shiftedBg, const uint32 sourceAlpha, @@ -2966,23 +2933,19 @@ bool EmojiPan::inlineResultsShown() const { } int EmojiPan::countBottom() const { - return (_origin == Ui::PanelAnimation::Origin::BottomLeft) ? _bottom : (parentWidget()->height() - _minBottom); + return (parentWidget()->height() - _minBottom); } void EmojiPan::moveByBottom() { - if (inlineResultsShown()) { - setOrigin(Ui::PanelAnimation::Origin::BottomLeft); - moveToLeft(0, y()); - } else { - setOrigin(Ui::PanelAnimation::Origin::BottomRight); - moveToRight(0, y()); - } + moveToRight(0, y()); updateContentHeight(); } void EmojiPan::enterEventHook(QEvent *e) { _hideTimer.stop(); - if (_hiding) showAnimated(_origin); + if (_hiding) { + showAnimated(); + } } bool EmojiPan::preventAutoHide() const { @@ -2990,7 +2953,9 @@ bool EmojiPan::preventAutoHide() const { } void EmojiPan::leaveEventHook(QEvent *e) { - if (preventAutoHide() || s_inner->inlineResultsShown()) return; + if (preventAutoHide()) { + return; + } auto ms = getms(); if (_a_show.animating(ms) || _a_opacity.animating(ms)) { hideAnimated(); @@ -3002,7 +2967,7 @@ void EmojiPan::leaveEventHook(QEvent *e) { void EmojiPan::otherEnter() { _hideTimer.stop(); - showAnimated(_origin); + showAnimated(); } void EmojiPan::otherLeave() { @@ -3244,7 +3209,7 @@ void EmojiPan::opacityAnimationCallback() { } void EmojiPan::hideByTimerOrLeave() { - if (isHidden() || preventAutoHide() || s_inner->inlineResultsShown()) return; + if (isHidden() || preventAutoHide()) return; hideAnimated(); } @@ -3286,7 +3251,7 @@ void EmojiPan::startShowAnimation() { _a_opacity = base::take(opacityAnimation); _cache = base::take(_cache); - _showAnimation = std::make_unique(st::emojiPanAnimation, _origin); + _showAnimation = std::make_unique(st::emojiPanAnimation, Ui::PanelAnimation::Origin::BottomRight); auto inner = rect().marginsRemoved(st::emojiPanMargins); _showAnimation->setFinalImage(std::move(image), QRect(inner.topLeft() * cIntRetinaFactor(), inner.size() * cIntRetinaFactor())); auto corners = App::cornersMask(ImageRoundRadius::Small); @@ -3343,12 +3308,7 @@ void EmojiPan::hideFinished() { Notify::clipStopperHidden(ClipStopperSavedGifsPanel); } -void EmojiPan::setOrigin(Ui::PanelAnimation::Origin origin) { - _origin = origin; -} - -void EmojiPan::showAnimated(Ui::PanelAnimation::Origin origin) { - setOrigin(origin); +void EmojiPan::showAnimated() { _hideTimer.stop(); showStarted(); } @@ -3397,7 +3357,7 @@ bool EmojiPan::eventFilter(QObject *obj, QEvent *e) { } else if (e->type() == QEvent::MouseButtonPress && static_cast(e)->button() == Qt::LeftButton/* && !dynamic_cast(obj)*/) { if (isHidden() || _hiding) { _hideTimer.stop(); - showAnimated(_origin); + showAnimated(); } else { hideAnimated(); } @@ -3415,26 +3375,7 @@ void EmojiPan::stickersInstalled(uint64 setId) { showAll(); s_inner->showStickerSet(setId); updateContentHeight(); - showAnimated(Ui::PanelAnimation::Origin::BottomRight); -} - -void EmojiPan::notify_inlineItemLayoutChanged(const InlineBots::Layout::ItemBase *layout) { - if (!_emojiShown && !isHidden()) { - s_inner->notify_inlineItemLayoutChanged(layout); - } -} - -void EmojiPan::ui_repaintInlineItem(const InlineBots::Layout::ItemBase *layout) { - if (!_emojiShown && !isHidden()) { - s_inner->ui_repaintInlineItem(layout); - } -} - -bool EmojiPan::ui_isInlineItemVisible(const InlineBots::Layout::ItemBase *layout) { - if (!_emojiShown && !isHidden()) { - return s_inner->ui_isInlineItemVisible(layout); - } - return false; + showAnimated(); } bool EmojiPan::ui_isInlineItemBeingChosen() { @@ -3640,9 +3581,6 @@ void EmojiPan::performSwitch() { } else { s_inner->updateShowingSavedGifs(); } - if (cShowingSavedGifs()) { - s_inner->showFinish(); - } validateSelectedIcon(ValidateIconAnimations::None); updateContentHeight(); } @@ -3745,15 +3683,11 @@ bool EmojiPan::overlaps(const QRect &globalRect) const { || inner.marginsRemoved(QMargins(0, st::buttonRadius, 0, st::buttonRadius)).contains(testRect); } -bool EmojiPan::hideOnNoInlineResults() { - return _inlineBot && inlineResultsShown() && (_shownFromInlineQuery || _inlineBot->username != cInlineGifBotUsername()); -} - void EmojiPan::inlineBotChanged() { if (!_inlineBot) return; if (!isHidden() && !_hiding) { - if (hideOnNoInlineResults() || !rect().contains(mapFromGlobal(QCursor::pos()))) { + if (!rect().contains(mapFromGlobal(QCursor::pos()))) { hideAnimated(); } } @@ -3868,7 +3802,7 @@ void EmojiPan::onInlineRequest() { } void EmojiPan::onEmptyInlineRows() { - if (_shownFromInlineQuery || hideOnNoInlineResults()) { + if (_shownFromInlineQuery) { hideAnimated(); s_inner->clearInlineRowsPanel(); } else if (!_inlineBot) { @@ -3906,15 +3840,13 @@ int32 EmojiPan::showInlineRows(bool newResults) { recountContentMaxHeight(); } if (clear) { - if (!hidden && hideOnNoInlineResults()) { - hideAnimated(); - } else if (!_hiding) { + if (!_hiding) { _cache = QPixmap(); // clear after refreshInlineRows() } } else { _hideTimer.stop(); if (hidden || _hiding) { - showAnimated(_origin); + showAnimated(); } else if (_emojiShown) { onSwitch(); } diff --git a/Telegram/SourceFiles/stickers/emoji_pan.h b/Telegram/SourceFiles/stickers/emoji_pan.h index f56f2475c..f5adef051 100644 --- a/Telegram/SourceFiles/stickers/emoji_pan.h +++ b/Telegram/SourceFiles/stickers/emoji_pan.h @@ -24,6 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "ui/abstract_button.h" #include "ui/effects/panel_animation.h" #include "mtproto/sender.h" +#include "inline_bots/inline_bot_layout_item.h" namespace InlineBots { namespace Layout { @@ -199,7 +200,7 @@ struct StickerIcon { int pixh = 0; }; -class StickerPanInner : public TWidget, private base::Subscriber { +class StickerPanInner : public TWidget, public InlineBots::Layout::Context, private base::Subscriber { Q_OBJECT public: @@ -208,7 +209,6 @@ public: void setMaxHeight(int maxHeight); void hideFinish(bool completely); - void showFinish(); void showStickerSet(uint64 setId); void updateShowingSavedGifs(); @@ -233,9 +233,9 @@ public: uint64 currentSet(int yOffset) const; - void notify_inlineItemLayoutChanged(const InlineItem *layout); - void ui_repaintInlineItem(const InlineItem *layout); - bool ui_isInlineItemVisible(const InlineItem *layout); + void inlineItemLayoutChanged(const InlineItem *layout) override; + void inlineItemRepaint(const InlineItem *layout) override; + bool inlineItemVisible(const InlineItem *layout) override; bool ui_isInlineItemBeingChosen(); bool inlineResultsShown() const { @@ -353,7 +353,6 @@ private: Stickers, }; Section _section = Section::Stickers; - bool _setGifCommand = false; UserData *_inlineBot; QString _inlineBotTitle; TimeMs _lastScrolled = 0; @@ -487,13 +486,9 @@ public: bool overlaps(const QRect &globalRect) const; - void notify_inlineItemLayoutChanged(const InlineBots::Layout::ItemBase *layout); - void ui_repaintInlineItem(const InlineBots::Layout::ItemBase *layout); - bool ui_isInlineItemVisible(const InlineBots::Layout::ItemBase *layout); bool ui_isInlineItemBeingChosen(); - void setOrigin(Ui::PanelAnimation::Origin origin); - void showAnimated(Ui::PanelAnimation::Origin origin); + void showAnimated(); void hideAnimated(); ~EmojiPan(); @@ -614,7 +609,6 @@ private: int _height = 0; int _bottom = 0; - Ui::PanelAnimation::Origin _origin = Ui::PanelAnimation::Origin::BottomRight; std::unique_ptr _showAnimation; Animation _a_show; @@ -675,7 +669,6 @@ private: void inlineBotChanged(); int32 showInlineRows(bool newResults); - bool hideOnNoInlineResults(); void recountContentMaxHeight(); bool refreshInlineRows(int32 *added = 0); UserData *_inlineBot = nullptr; diff --git a/Telegram/SourceFiles/stickers/stickers.style b/Telegram/SourceFiles/stickers/stickers.style index bdcc5fbf9..07d546379 100644 --- a/Telegram/SourceFiles/stickers/stickers.style +++ b/Telegram/SourceFiles/stickers/stickers.style @@ -206,3 +206,8 @@ hashtagClose: IconButton { stickersToastMaxWidth: 340px; stickersToastPadding: margins(16px, 13px, 16px, 12px); + +inlineBotsScroll: ScrollArea(defaultSolidScroll) { + deltat: stickerPanPadding; + deltab: stickerPanPadding; +} diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index 79e5f923b..f6c0dfb67 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -1641,7 +1641,7 @@ void DocumentData::notifyLayoutChanged() const { if (auto items = InlineBots::Layout::documentItems()) { for (auto item : items->value(const_cast(this))) { - Notify::inlineItemLayoutChanged(item); + item->layoutChanged(); } } } diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index 876cdcb68..6acac682f 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -125,6 +125,8 @@ <(src_loc)/inline_bots/inline_bot_result.h <(src_loc)/inline_bots/inline_bot_send_data.cpp <(src_loc)/inline_bots/inline_bot_send_data.h +<(src_loc)/inline_bots/inline_results_widget.cpp +<(src_loc)/inline_bots/inline_results_widget.h <(src_loc)/intro/introwidget.cpp <(src_loc)/intro/introwidget.h <(src_loc)/intro/introcode.cpp