inline_results_inner.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "inline_bots/inline_results_inner.h"
  8. #include "api/api_common.h"
  9. #include "chat_helpers/gifs_list_widget.h" // ChatHelpers::AddGifAction
  10. #include "menu/menu_send.h" // SendMenu::FillSendMenu
  11. #include "core/click_handler_types.h"
  12. #include "data/data_document.h"
  13. #include "data/data_file_origin.h"
  14. #include "data/data_user.h"
  15. #include "data/data_changes.h"
  16. #include "data/data_chat_participant_status.h"
  17. #include "data/data_session.h"
  18. #include "inline_bots/bot_attach_web_view.h"
  19. #include "inline_bots/inline_bot_result.h"
  20. #include "inline_bots/inline_bot_layout_item.h"
  21. #include "lang/lang_keys.h"
  22. #include "layout/layout_position.h"
  23. #include "mainwindow.h"
  24. #include "main/main_session.h"
  25. #include "window/window_session_controller.h"
  26. #include "ui/text/text_utilities.h"
  27. #include "ui/widgets/popup_menu.h"
  28. #include "ui/widgets/buttons.h"
  29. #include "ui/widgets/labels.h"
  30. #include "ui/effects/path_shift_gradient.h"
  31. #include "ui/painter.h"
  32. #include "ui/ui_utility.h"
  33. #include "history/view/history_view_cursor_state.h"
  34. #include "history/history.h"
  35. #include "styles/style_chat_helpers.h"
  36. #include "styles/style_menu_icons.h"
  37. #include <QtWidgets/QApplication>
  38. namespace InlineBots {
  39. namespace Layout {
  40. namespace {
  41. constexpr auto kMinRepaintDelay = crl::time(33);
  42. constexpr auto kMinAfterScrollDelay = crl::time(33);
  43. } // namespace
  44. Inner::Inner(
  45. QWidget *parent,
  46. not_null<Window::SessionController*> controller)
  47. : RpWidget(parent)
  48. , _controller(controller)
  49. , _pathGradient(std::make_unique<Ui::PathShiftGradient>(
  50. st::windowBgRipple,
  51. st::windowBgOver,
  52. [=] { repaintItems(); }))
  53. , _updateInlineItems([=] { updateInlineItems(); })
  54. , _mosaic(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft)
  55. , _previewTimer([=] { showPreview(); }) {
  56. resize(st::emojiPanWidth - st::emojiScroll.width - st::roundRadiusSmall, st::inlineResultsMinHeight);
  57. setMouseTracking(true);
  58. setAttribute(Qt::WA_OpaquePaintEvent);
  59. _controller->session().downloaderTaskFinished(
  60. ) | rpl::start_with_next([=] {
  61. updateInlineItems();
  62. }, lifetime());
  63. controller->gifPauseLevelChanged(
  64. ) | rpl::start_with_next([=] {
  65. if (!_controller->isGifPausedAtLeastFor(
  66. Window::GifPauseReason::InlineResults)) {
  67. updateInlineItems();
  68. }
  69. }, lifetime());
  70. _controller->session().changes().peerUpdates(
  71. Data::PeerUpdate::Flag::Rights
  72. ) | rpl::filter([=](const Data::PeerUpdate &update) {
  73. return (update.peer.get() == _inlineQueryPeer);
  74. }) | rpl::start_with_next([=] {
  75. auto isRestricted = (_restrictedLabel != nullptr);
  76. if (isRestricted != isRestrictedView()) {
  77. auto h = countHeight();
  78. if (h != height()) resize(width(), h);
  79. }
  80. }, lifetime());
  81. sizeValue(
  82. ) | rpl::start_with_next([=](const QSize &s) {
  83. _mosaic.setFullWidth(s.width());
  84. }, lifetime());
  85. _mosaic.setRightSkip(st::inlineResultsSkip);
  86. }
  87. void Inner::visibleTopBottomUpdated(
  88. int visibleTop,
  89. int visibleBottom) {
  90. _visibleBottom = visibleBottom;
  91. if (_visibleTop != visibleTop) {
  92. _visibleTop = visibleTop;
  93. _lastScrolledAt = crl::now();
  94. update();
  95. }
  96. }
  97. void Inner::checkRestrictedPeer() {
  98. if (_inlineQueryPeer) {
  99. const auto error = Data::RestrictionError(
  100. _inlineQueryPeer,
  101. ChatRestriction::SendInline);
  102. const auto changed = (_restrictedLabelKey != error.text);
  103. if (!changed) {
  104. return;
  105. }
  106. _restrictedLabelKey = error.text;
  107. if (error) {
  108. const auto window = _controller;
  109. const auto peer = _inlineQueryPeer;
  110. _restrictedLabel.create(
  111. this,
  112. rpl::single(error.boostsToLift
  113. ? Ui::Text::Link(error.text)
  114. : TextWithEntities{ error.text }),
  115. st::stickersRestrictedLabel);
  116. const auto lifting = error.boostsToLift;
  117. _restrictedLabel->setClickHandlerFilter([=](auto...) {
  118. window->resolveBoostState(peer->asChannel(), lifting);
  119. return false;
  120. });
  121. _restrictedLabel->show();
  122. updateRestrictedLabelGeometry();
  123. if (_switchPmButton) {
  124. _switchPmButton->hide();
  125. }
  126. repaintItems();
  127. return;
  128. }
  129. } else {
  130. _restrictedLabelKey = QString();
  131. }
  132. if (_restrictedLabel) {
  133. _restrictedLabel.destroy();
  134. if (_switchPmButton) {
  135. _switchPmButton->show();
  136. }
  137. repaintItems();
  138. }
  139. }
  140. void Inner::updateRestrictedLabelGeometry() {
  141. if (!_restrictedLabel) {
  142. return;
  143. }
  144. auto labelWidth = width() - st::stickerPanPadding * 2;
  145. _restrictedLabel->resizeToWidth(labelWidth);
  146. _restrictedLabel->moveToLeft(
  147. (width() - _restrictedLabel->width()) / 2,
  148. st::stickerPanPadding);
  149. }
  150. bool Inner::isRestrictedView() {
  151. checkRestrictedPeer();
  152. return (_restrictedLabel != nullptr);
  153. }
  154. int Inner::countHeight() {
  155. if (isRestrictedView()) {
  156. return st::stickerPanPadding + _restrictedLabel->height() + st::stickerPanPadding;
  157. } else if (_mosaic.empty() && !_switchPmButton) {
  158. return st::stickerPanPadding + st::normalFont->height + st::stickerPanPadding;
  159. }
  160. auto result = st::stickerPanPadding;
  161. if (_switchPmButton) {
  162. result += _switchPmButton->height() + st::inlineResultsSkip;
  163. }
  164. for (auto i = 0, l = _mosaic.rowsCount(); i < l; ++i) {
  165. result += _mosaic.rowHeightAt(i);
  166. }
  167. return result + st::stickerPanPadding;
  168. }
  169. QString Inner::tooltipText() const {
  170. if (const auto lnk = ClickHandler::getActive()) {
  171. return lnk->tooltip();
  172. }
  173. return QString();
  174. }
  175. QPoint Inner::tooltipPos() const {
  176. return _lastMousePos;
  177. }
  178. bool Inner::tooltipWindowActive() const {
  179. return Ui::AppInFocus() && Ui::InFocusChain(window());
  180. }
  181. rpl::producer<> Inner::inlineRowsCleared() const {
  182. return _inlineRowsCleared.events();
  183. }
  184. Inner::~Inner() = default;
  185. void Inner::resizeEvent(QResizeEvent *e) {
  186. updateRestrictedLabelGeometry();
  187. }
  188. void Inner::paintEvent(QPaintEvent *e) {
  189. Painter p(this);
  190. QRect r = e ? e->rect() : rect();
  191. if (r != rect()) {
  192. p.setClipRect(r);
  193. }
  194. p.fillRect(r, st::emojiPanBg);
  195. paintInlineItems(p, r);
  196. }
  197. void Inner::paintInlineItems(Painter &p, const QRect &r) {
  198. if (_restrictedLabel) {
  199. return;
  200. }
  201. if (_mosaic.empty() && !_switchPmButton) {
  202. p.setFont(st::normalFont);
  203. p.setPen(st::noContactsColor);
  204. p.drawText(QRect(0, 0, width(), (height() / 3) * 2 + st::normalFont->height), tr::lng_inline_bot_no_results(tr::now), style::al_center);
  205. return;
  206. }
  207. const auto gifPaused = _controller->isGifPausedAtLeastFor(
  208. Window::GifPauseReason::InlineResults);
  209. using namespace InlineBots::Layout;
  210. PaintContext context(crl::now(), false, gifPaused, false);
  211. context.pathGradient = _pathGradient.get();
  212. context.pathGradient->startFrame(0, width(), width() / 2);
  213. auto paintItem = [&](not_null<const ItemBase*> item, QPoint point) {
  214. p.translate(point.x(), point.y());
  215. item->paint(
  216. p,
  217. r.translated(-point),
  218. &context);
  219. p.translate(-point.x(), -point.y());
  220. };
  221. _mosaic.paint(std::move(paintItem), r);
  222. }
  223. void Inner::mousePressEvent(QMouseEvent *e) {
  224. if (e->button() != Qt::LeftButton) {
  225. return;
  226. }
  227. _lastMousePos = e->globalPos();
  228. updateSelected();
  229. _pressed = _selected;
  230. ClickHandler::pressed();
  231. _previewTimer.callOnce(QApplication::startDragTime());
  232. }
  233. void Inner::mouseReleaseEvent(QMouseEvent *e) {
  234. _previewTimer.cancel();
  235. auto pressed = std::exchange(_pressed, -1);
  236. auto activated = ClickHandler::unpressed();
  237. if (_previewShown) {
  238. _previewShown = false;
  239. return;
  240. }
  241. _lastMousePos = e->globalPos();
  242. updateSelected();
  243. if (_selected < 0 || _selected != pressed || !activated) {
  244. return;
  245. }
  246. using namespace InlineBots::Layout;
  247. const auto open = dynamic_cast<OpenFileClickHandler*>(activated.get());
  248. if (dynamic_cast<SendClickHandler*>(activated.get()) || open) {
  249. selectInlineResult(_selected, {}, !!open);
  250. } else {
  251. ActivateClickHandler(window(), activated, {
  252. e->button(),
  253. QVariant::fromValue(ClickHandlerContext{
  254. .sessionWindow = base::make_weak(_controller),
  255. })
  256. });
  257. }
  258. }
  259. void Inner::selectInlineResult(
  260. int index,
  261. Api::SendOptions options,
  262. bool open) {
  263. const auto item = _mosaic.maybeItemAt(index);
  264. if (!item) {
  265. return;
  266. }
  267. const auto messageSendingFrom = [&]() -> Ui::MessageSendingAnimationFrom {
  268. const auto document = item->getDocument()
  269. ? item->getDocument()
  270. : item->getPreviewDocument();
  271. if (options.scheduled
  272. || item->isFullLine()
  273. || !document
  274. || (!document->sticker() && !document->isGifv())) {
  275. return {};
  276. }
  277. using Type = Ui::MessageSendingAnimationFrom::Type;
  278. const auto type = document->sticker()
  279. ? Type::Sticker
  280. : document->isGifv()
  281. ? Type::Gif
  282. : Type::None;
  283. const auto rect = item->innerContentRect().translated(
  284. _mosaic.findRect(index).topLeft());
  285. return {
  286. .type = type,
  287. .localId = _controller->session().data().nextLocalMessageId(),
  288. .globalStartGeometry = mapToGlobal(rect),
  289. .crop = document->isGifv(),
  290. };
  291. };
  292. if (const auto inlineResult = item->getResult()) {
  293. if (inlineResult->onChoose(item)) {
  294. _resultSelectedCallback({
  295. .result = std::move(inlineResult),
  296. .bot = _inlineBot,
  297. .options = std::move(options),
  298. .messageSendingFrom = messageSendingFrom(),
  299. .open = open,
  300. });
  301. }
  302. }
  303. }
  304. void Inner::mouseMoveEvent(QMouseEvent *e) {
  305. _lastMousePos = e->globalPos();
  306. updateSelected();
  307. }
  308. void Inner::leaveEventHook(QEvent *e) {
  309. clearSelection();
  310. Ui::Tooltip::Hide();
  311. }
  312. void Inner::leaveToChildEvent(QEvent *e, QWidget *child) {
  313. clearSelection();
  314. }
  315. void Inner::enterFromChildEvent(QEvent *e, QWidget *child) {
  316. _lastMousePos = QCursor::pos();
  317. updateSelected();
  318. }
  319. void Inner::contextMenuEvent(QContextMenuEvent *e) {
  320. if (_selected < 0 || _pressed >= 0) {
  321. return;
  322. }
  323. auto details = _sendMenuDetails
  324. ? _sendMenuDetails()
  325. : SendMenu::Details();
  326. // inline results don't have effects
  327. details.effectAllowed = false;
  328. _menu = base::make_unique_q<Ui::PopupMenu>(
  329. this,
  330. st::popupMenuWithIcons);
  331. const auto selected = _selected;
  332. const auto send = crl::guard(this, [=](Api::SendOptions options) {
  333. selectInlineResult(selected, options, false);
  334. });
  335. const auto show = _controller->uiShow();
  336. // In case we're adding items after FillSendMenu we have
  337. // to pass nullptr for showForEffect and attach selector later.
  338. // Otherwise added items widths won't be respected in menu geometry.
  339. SendMenu::FillSendMenu(
  340. _menu,
  341. nullptr, // showForEffect
  342. details,
  343. SendMenu::DefaultCallback(show, send));
  344. const auto item = _mosaic.itemAt(_selected);
  345. if (const auto previewDocument = item->getPreviewDocument()) {
  346. auto callback = [&](
  347. const QString &text,
  348. Fn<void()> &&done,
  349. const style::icon *icon) {
  350. _menu->addAction(text, std::move(done), icon);
  351. };
  352. ChatHelpers::AddGifAction(
  353. std::move(callback),
  354. _controller->uiShow(),
  355. previewDocument);
  356. }
  357. SendMenu::AttachSendMenuEffect(
  358. _menu,
  359. show,
  360. details,
  361. SendMenu::DefaultCallback(show, send));
  362. if (!_menu->empty()) {
  363. _menu->popup(QCursor::pos());
  364. }
  365. }
  366. void Inner::clearSelection() {
  367. if (_selected >= 0) {
  368. ClickHandler::clearActive(_mosaic.itemAt(_selected));
  369. setCursor(style::cur_default);
  370. }
  371. _selected = _pressed = -1;
  372. updateInlineItems();
  373. }
  374. void Inner::hideFinished() {
  375. clearHeavyData();
  376. }
  377. void Inner::clearHeavyData() {
  378. clearInlineRows(false);
  379. for (const auto &[result, layout] : _inlineLayouts) {
  380. layout->unloadHeavyPart();
  381. }
  382. }
  383. void Inner::inlineBotChanged() {
  384. refreshInlineRows(nullptr, nullptr, nullptr, true);
  385. }
  386. void Inner::clearInlineRows(bool resultsDeleted) {
  387. if (resultsDeleted) {
  388. _selected = _pressed = -1;
  389. } else {
  390. clearSelection();
  391. }
  392. _mosaic.clearRows(resultsDeleted);
  393. }
  394. ItemBase *Inner::layoutPrepareInlineResult(std::shared_ptr<Result> result) {
  395. const auto raw = result.get();
  396. auto it = _inlineLayouts.find(raw);
  397. if (it == _inlineLayouts.cend()) {
  398. if (auto layout = ItemBase::createLayout(
  399. this,
  400. std::move(result),
  401. _inlineWithThumb)) {
  402. it = _inlineLayouts.emplace(raw, std::move(layout)).first;
  403. it->second->initDimensions();
  404. } else {
  405. return nullptr;
  406. }
  407. }
  408. if (!it->second->maxWidth()) {
  409. return nullptr;
  410. }
  411. return it->second.get();
  412. }
  413. void Inner::deleteUnusedInlineLayouts() {
  414. if (_mosaic.empty()) { // delete all
  415. _inlineLayouts.clear();
  416. } else {
  417. for (auto i = _inlineLayouts.begin(); i != _inlineLayouts.cend();) {
  418. if (i->second->position() < 0) {
  419. i = _inlineLayouts.erase(i);
  420. } else {
  421. ++i;
  422. }
  423. }
  424. }
  425. }
  426. void Inner::preloadImages() {
  427. _mosaic.forEach([](not_null<const ItemBase*> item) {
  428. item->preload();
  429. });
  430. }
  431. void Inner::hideInlineRowsPanel() {
  432. clearInlineRows(false);
  433. }
  434. void Inner::clearInlineRowsPanel() {
  435. clearInlineRows(false);
  436. }
  437. void Inner::refreshMosaicOffset() {
  438. const auto top = _switchPmButton
  439. ? (_switchPmButton->height() + st::inlineResultsSkip)
  440. : 0;
  441. _mosaic.setPadding(st::emojiPanMargins + QMargins(0, top, 0, 0));
  442. }
  443. void Inner::refreshSwitchPmButton(const CacheEntry *entry) {
  444. if (!entry || entry->switchPmText.isEmpty()) {
  445. _switchPmButton.destroy();
  446. _switchPmStartToken.clear();
  447. _switchPmUrl = QByteArray();
  448. } else {
  449. if (!_switchPmButton) {
  450. _switchPmButton.create(this, nullptr, st::switchPmButton);
  451. _switchPmButton->show();
  452. _switchPmButton->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
  453. _switchPmButton->addClickHandler([=] { switchPm(); });
  454. }
  455. _switchPmButton->setText(rpl::single(entry->switchPmText));
  456. _switchPmStartToken = entry->switchPmStartToken;
  457. _switchPmUrl = entry->switchPmUrl;
  458. const auto buttonTop = st::stickerPanPadding;
  459. _switchPmButton->move(st::inlineResultsLeft - st::roundRadiusSmall, buttonTop);
  460. if (isRestrictedView()) {
  461. _switchPmButton->hide();
  462. }
  463. }
  464. repaintItems();
  465. }
  466. int Inner::refreshInlineRows(PeerData *queryPeer, UserData *bot, const CacheEntry *entry, bool resultsDeleted) {
  467. _inlineBot = bot;
  468. _inlineQueryPeer = queryPeer;
  469. refreshSwitchPmButton(entry);
  470. refreshMosaicOffset();
  471. auto clearResults = [&] {
  472. if (!entry) {
  473. return true;
  474. }
  475. if (entry->results.empty() && entry->switchPmText.isEmpty()) {
  476. return true;
  477. }
  478. return false;
  479. };
  480. auto clearResultsResult = clearResults(); // Clang workaround.
  481. if (clearResultsResult) {
  482. if (resultsDeleted) {
  483. clearInlineRows(true);
  484. deleteUnusedInlineLayouts();
  485. }
  486. _inlineRowsCleared.fire({});
  487. return 0;
  488. }
  489. clearSelection();
  490. Assert(_inlineBot != 0);
  491. const auto count = int(entry->results.size());
  492. const auto from = validateExistingInlineRows(entry->results);
  493. auto added = 0;
  494. if (count) {
  495. const auto resultItems = entry->results | ranges::views::slice(
  496. from,
  497. count
  498. ) | ranges::views::transform([&](const std::shared_ptr<Result> &r) {
  499. return layoutPrepareInlineResult(r);
  500. }) | ranges::views::filter([](const ItemBase *item) {
  501. return item != nullptr;
  502. }) | ranges::to<std::vector<not_null<ItemBase*>>>;
  503. _mosaic.addItems(resultItems);
  504. added = resultItems.size();
  505. preloadImages();
  506. }
  507. auto h = countHeight();
  508. if (h != height()) resize(width(), h);
  509. repaintItems();
  510. _lastMousePos = QCursor::pos();
  511. updateSelected();
  512. return added;
  513. }
  514. int Inner::validateExistingInlineRows(const Results &results) {
  515. const auto until = _mosaic.validateExistingRows([&](
  516. not_null<const ItemBase*> item,
  517. int untilIndex) {
  518. return item->getResult().get() != results[untilIndex].get();
  519. }, results.size());
  520. if (_mosaic.empty()) {
  521. _inlineWithThumb = false;
  522. for (int i = until; i < results.size(); ++i) {
  523. if (results.at(i)->hasThumbDisplay()) {
  524. _inlineWithThumb = true;
  525. break;
  526. }
  527. }
  528. }
  529. return until;
  530. }
  531. void Inner::inlineItemLayoutChanged(const ItemBase *layout) {
  532. if (_selected < 0 || !isVisible()) {
  533. return;
  534. }
  535. if (const auto item = _mosaic.maybeItemAt(_selected)) {
  536. if (layout == item) {
  537. updateSelected();
  538. }
  539. }
  540. }
  541. void Inner::inlineItemRepaint(const ItemBase *layout) {
  542. updateInlineItems();
  543. }
  544. bool Inner::inlineItemVisible(const ItemBase *layout) {
  545. int32 position = layout->position();
  546. if (position < 0 || !isVisible()) {
  547. return false;
  548. }
  549. const auto &[row, column] = ::Layout::IndexToPosition(position);
  550. auto top = st::stickerPanPadding;
  551. for (auto i = 0; i != row; ++i) {
  552. top += _mosaic.rowHeightAt(i);
  553. }
  554. return (top < _visibleBottom)
  555. && (top + _mosaic.itemAt(row, column)->height() > _visibleTop);
  556. }
  557. Data::FileOrigin Inner::inlineItemFileOrigin() {
  558. return Data::FileOrigin();
  559. }
  560. void Inner::updateSelected() {
  561. if (_pressed >= 0 && !_previewShown) {
  562. return;
  563. }
  564. const auto p = mapFromGlobal(_lastMousePos);
  565. const auto sx = rtl() ? (width() - p.x()) : p.x();
  566. const auto sy = p.y();
  567. const auto &[index, exact, relative] = _mosaic.findByPoint({ sx, sy });
  568. const auto selected = exact ? index : -1;
  569. const auto item = exact ? _mosaic.itemAt(selected).get() : nullptr;
  570. const auto link = exact ? item->getState(relative, {}).link : nullptr;
  571. if (_selected != selected) {
  572. if (const auto s = _mosaic.maybeItemAt(_selected)) {
  573. s->update();
  574. }
  575. _selected = selected;
  576. if (item) {
  577. item->update();
  578. }
  579. if (_previewShown && _selected >= 0 && _pressed != _selected) {
  580. _pressed = _selected;
  581. if (item) {
  582. if (const auto preview = item->getPreviewDocument()) {
  583. _controller->widget()->showMediaPreview(
  584. Data::FileOrigin(),
  585. preview);
  586. } else if (const auto preview = item->getPreviewPhoto()) {
  587. _controller->widget()->showMediaPreview(
  588. Data::FileOrigin(),
  589. preview);
  590. }
  591. }
  592. }
  593. }
  594. if (ClickHandler::setActive(link, item)) {
  595. setCursor(link ? style::cur_pointer : style::cur_default);
  596. Ui::Tooltip::Hide();
  597. }
  598. if (link) {
  599. Ui::Tooltip::Show(1000, this);
  600. }
  601. }
  602. void Inner::showPreview() {
  603. if (_pressed < 0) {
  604. return;
  605. }
  606. if (const auto layout = _mosaic.maybeItemAt(_pressed)) {
  607. if (const auto previewDocument = layout->getPreviewDocument()) {
  608. _previewShown = _controller->widget()->showMediaPreview(
  609. Data::FileOrigin(),
  610. previewDocument);
  611. } else if (const auto previewPhoto = layout->getPreviewPhoto()) {
  612. _previewShown = _controller->widget()->showMediaPreview(
  613. Data::FileOrigin(),
  614. previewPhoto);
  615. }
  616. }
  617. }
  618. void Inner::updateInlineItems() {
  619. const auto now = crl::now();
  620. const auto delay = std::max(
  621. _lastScrolledAt + kMinAfterScrollDelay - now,
  622. _lastUpdatedAt + kMinRepaintDelay - now);
  623. if (delay <= 0) {
  624. repaintItems();
  625. } else if (!_updateInlineItems.isActive()
  626. || _updateInlineItems.remainingTime() > kMinRepaintDelay) {
  627. _updateInlineItems.callOnce(std::max(delay, kMinRepaintDelay));
  628. }
  629. }
  630. void Inner::repaintItems(crl::time now) {
  631. _lastUpdatedAt = now ? now : crl::now();
  632. update();
  633. }
  634. void Inner::switchPm() {
  635. if (!_inlineBot || !_inlineBot->isBot()) {
  636. return;
  637. } else if (!_switchPmUrl.isEmpty()) {
  638. const auto bot = _inlineBot;
  639. _inlineBot->session().attachWebView().open({
  640. .bot = bot,
  641. .context = { .controller = _controller },
  642. .button = { .url = _switchPmUrl },
  643. .source = InlineBots::WebViewSourceSwitch(),
  644. });
  645. } else {
  646. _inlineBot->botInfo->startToken = _switchPmStartToken;
  647. _inlineBot->botInfo->inlineReturnTo
  648. = _controller->dialogsEntryStateCurrent();
  649. _controller->showPeerHistory(
  650. _inlineBot,
  651. Window::SectionShow::Way::ClearStack,
  652. ShowAndStartBotMsgId);
  653. }
  654. }
  655. void Inner::setSendMenuDetails(Fn<SendMenu::Details()> &&callback) {
  656. _sendMenuDetails = std::move(callback);
  657. }
  658. } // namespace Layout
  659. } // namespace InlineBots