history_drag_area.cpp 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  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 "history/history_drag_area.h"
  8. #include "base/event_filter.h"
  9. #include "ui/boxes/confirm_box.h"
  10. #include "boxes/sticker_set_box.h"
  11. #include "inline_bots/inline_bot_result.h"
  12. #include "inline_bots/inline_bot_layout_item.h"
  13. #include "dialogs/ui/dialogs_layout.h"
  14. #include "history/history_widget.h"
  15. #include "storage/localstorage.h"
  16. #include "lang/lang_keys.h"
  17. #include "ui/widgets/shadow.h"
  18. #include "ui/painter.h"
  19. #include "ui/ui_utility.h"
  20. #include "ui/cached_round_corners.h"
  21. #include "mainwindow.h"
  22. #include "apiwrap.h"
  23. #include "mainwidget.h"
  24. #include "storage/storage_media_prepare.h"
  25. #include "styles/style_chat_helpers.h"
  26. #include "styles/style_layers.h"
  27. namespace {
  28. constexpr auto kDragAreaEvents = {
  29. QEvent::DragEnter,
  30. QEvent::DragLeave,
  31. QEvent::Drop,
  32. QEvent::MouseButtonRelease,
  33. QEvent::Leave,
  34. };
  35. } // namespace
  36. DragArea::Areas DragArea::SetupDragAreaToContainer(
  37. not_null<Ui::RpWidget*> container,
  38. Fn<bool(not_null<const QMimeData*>)> &&dragEnterFilter,
  39. Fn<void(bool)> &&setAcceptDropsField,
  40. Fn<void()> &&updateControlsGeometry,
  41. DragArea::CallbackComputeState &&computeState,
  42. bool hideSubtext) {
  43. using DragState = Storage::MimeDataState;
  44. auto &lifetime = container->lifetime();
  45. container->setAcceptDrops(true);
  46. const auto attachDragDocument
  47. = Ui::CreateChild<DragArea>(container.get());
  48. const auto attachDragPhoto = Ui::CreateChild<DragArea>(container.get());
  49. attachDragDocument->hide();
  50. attachDragPhoto->hide();
  51. attachDragDocument->raise();
  52. attachDragPhoto->raise();
  53. const auto attachDragState
  54. = lifetime.make_state<DragState>(DragState::None);
  55. const auto width = [=] {
  56. return container->width();
  57. };
  58. const auto height = [=] {
  59. return container->height();
  60. };
  61. const auto horizontalMargins = st::dragMargin.left()
  62. + st::dragMargin.right();
  63. const auto verticalMargins = st::dragMargin.top()
  64. + st::dragMargin.bottom();
  65. const auto resizeToFull = [=](not_null<DragArea*> w) {
  66. w->resize(width() - horizontalMargins, height() - verticalMargins);
  67. };
  68. const auto moveToTop = [=](not_null<DragArea*> w) {
  69. w->move(st::dragMargin.left(), st::dragMargin.top());
  70. };
  71. const auto updateAttachGeometry = crl::guard(container, [=] {
  72. if (updateControlsGeometry) {
  73. updateControlsGeometry();
  74. }
  75. switch (*attachDragState) {
  76. case DragState::Files:
  77. resizeToFull(attachDragDocument);
  78. moveToTop(attachDragDocument);
  79. break;
  80. case DragState::PhotoFiles:
  81. attachDragDocument->resize(
  82. width() - horizontalMargins,
  83. (height() - verticalMargins) / 2);
  84. moveToTop(attachDragDocument);
  85. attachDragPhoto->resize(
  86. attachDragDocument->width(),
  87. attachDragDocument->height());
  88. attachDragPhoto->move(
  89. st::dragMargin.left(),
  90. height()
  91. - attachDragPhoto->height()
  92. - st::dragMargin.bottom());
  93. break;
  94. case DragState::Image:
  95. resizeToFull(attachDragPhoto);
  96. moveToTop(attachDragPhoto);
  97. break;
  98. }
  99. });
  100. const auto updateDragAreas = [=] {
  101. if (setAcceptDropsField) {
  102. setAcceptDropsField(*attachDragState == DragState::None);
  103. }
  104. updateAttachGeometry();
  105. switch (*attachDragState) {
  106. case DragState::None:
  107. attachDragDocument->otherLeave();
  108. attachDragPhoto->otherLeave();
  109. break;
  110. case DragState::Files:
  111. attachDragDocument->setText(
  112. tr::lng_drag_files_here(tr::now),
  113. hideSubtext
  114. ? QString()
  115. : tr::lng_drag_to_send_files(tr::now));
  116. attachDragDocument->otherEnter();
  117. attachDragPhoto->hideFast();
  118. break;
  119. case DragState::PhotoFiles:
  120. attachDragDocument->setText(
  121. tr::lng_drag_images_here(tr::now),
  122. hideSubtext
  123. ? QString()
  124. : tr::lng_drag_to_send_no_compression(tr::now));
  125. attachDragPhoto->setText(
  126. tr::lng_drag_photos_here(tr::now),
  127. hideSubtext
  128. ? QString()
  129. : tr::lng_drag_to_send_quick(tr::now));
  130. attachDragDocument->otherEnter();
  131. attachDragPhoto->otherEnter();
  132. break;
  133. case DragState::Image:
  134. attachDragPhoto->setText(
  135. tr::lng_drag_images_here(tr::now),
  136. hideSubtext
  137. ? QString()
  138. : tr::lng_drag_to_send_quick(tr::now));
  139. attachDragDocument->hideFast();
  140. attachDragPhoto->otherEnter();
  141. break;
  142. };
  143. };
  144. container->sizeValue(
  145. ) | rpl::start_with_next(updateAttachGeometry, lifetime);
  146. const auto resetDragStateIfNeeded = [=] {
  147. if (*attachDragState != DragState::None
  148. || !attachDragPhoto->isHidden()
  149. || !attachDragDocument->isHidden()) {
  150. *attachDragState = DragState::None;
  151. updateDragAreas();
  152. }
  153. };
  154. const auto dragEnterEvent = [=](QDragEnterEvent *e) {
  155. if (dragEnterFilter && !dragEnterFilter(e->mimeData())) {
  156. return;
  157. }
  158. *attachDragState = computeState
  159. ? computeState(e->mimeData())
  160. : Storage::ComputeMimeDataState(e->mimeData());
  161. updateDragAreas();
  162. if (*attachDragState != DragState::None) {
  163. e->setDropAction(Qt::IgnoreAction);
  164. e->accept();
  165. }
  166. };
  167. const auto dragLeaveEvent = [=](QDragLeaveEvent *e) {
  168. resetDragStateIfNeeded();
  169. };
  170. const auto dropEvent = [=](QDropEvent *e) {
  171. // Hide fast to avoid visual bugs in resizable boxes.
  172. attachDragDocument->hideFast();
  173. attachDragPhoto->hideFast();
  174. *attachDragState = DragState::None;
  175. updateDragAreas();
  176. e->setDropAction(Qt::CopyAction);
  177. e->accept();
  178. };
  179. const auto processDragEvents = [=](not_null<QEvent*> event) {
  180. switch (event->type()) {
  181. case QEvent::DragEnter:
  182. dragEnterEvent(static_cast<QDragEnterEvent*>(event.get()));
  183. return true;
  184. case QEvent::DragLeave:
  185. dragLeaveEvent(static_cast<QDragLeaveEvent*>(event.get()));
  186. return true;
  187. case QEvent::Drop:
  188. dropEvent(static_cast<QDropEvent*>(event.get()));
  189. return true;
  190. };
  191. return false;
  192. };
  193. container->events(
  194. ) | rpl::filter([=](not_null<QEvent*> event) {
  195. return ranges::contains(kDragAreaEvents, event->type());
  196. }) | rpl::start_with_next([=](not_null<QEvent*> event) {
  197. const auto type = event->type();
  198. if (processDragEvents(event)) {
  199. return;
  200. } else if (type == QEvent::Leave
  201. || type == QEvent::MouseButtonRelease) {
  202. resetDragStateIfNeeded();
  203. }
  204. }, lifetime);
  205. const auto eventFilter = [=](not_null<QEvent*> event) {
  206. processDragEvents(event);
  207. return base::EventFilterResult::Continue;
  208. };
  209. base::install_event_filter(attachDragDocument, eventFilter);
  210. base::install_event_filter(attachDragPhoto, eventFilter);
  211. updateDragAreas();
  212. return {
  213. .document = attachDragDocument,
  214. .photo = attachDragPhoto,
  215. };
  216. }
  217. DragArea::DragArea(QWidget *parent) : Ui::RpWidget(parent) {
  218. setMouseTracking(true);
  219. setAcceptDrops(true);
  220. }
  221. bool DragArea::overlaps(const QRect &globalRect) {
  222. if (isHidden() || _a_opacity.animating()) {
  223. return false;
  224. }
  225. const auto inner = rect() - st::dragPadding;
  226. const auto testRect = QRect(
  227. mapFromGlobal(globalRect.topLeft()),
  228. globalRect.size());
  229. const auto h = QMargins(st::boxRadius, 0, st::boxRadius, 0);
  230. const auto v = QMargins(0, st::boxRadius, 0, st::boxRadius);
  231. return inner.marginsRemoved(h).contains(testRect)
  232. || inner.marginsRemoved(v).contains(testRect);
  233. }
  234. void DragArea::mouseMoveEvent(QMouseEvent *e) {
  235. if (_hiding) {
  236. return;
  237. }
  238. setIn((rect() - st::dragPadding).contains(e->pos()));
  239. }
  240. void DragArea::dragMoveEvent(QDragMoveEvent *e) {
  241. setIn((rect() - st::dragPadding).contains(e->pos()));
  242. e->setDropAction(_in ? Qt::CopyAction : Qt::IgnoreAction);
  243. e->accept();
  244. }
  245. void DragArea::setIn(bool in) {
  246. if (_in != in) {
  247. _in = in;
  248. _a_in.start(
  249. [=] { update(); },
  250. _in ? 0. : 1.,
  251. _in ? 1. : 0.,
  252. st::boxDuration);
  253. }
  254. }
  255. void DragArea::setText(const QString &text, const QString &subtext) {
  256. _text = text;
  257. _subtext = subtext;
  258. update();
  259. }
  260. void DragArea::paintEvent(QPaintEvent *e) {
  261. Painter p(this);
  262. const auto opacity = _a_opacity.value(_hiding ? 0. : 1.);
  263. if (!_a_opacity.animating() && _hiding) {
  264. return;
  265. }
  266. p.setOpacity(opacity);
  267. const auto inner = rect() - st::dragPadding;
  268. if (!_cache.isNull()) {
  269. p.drawPixmapLeft(
  270. inner.x() - st::boxRoundShadow.extend.left(),
  271. inner.y() - st::boxRoundShadow.extend.top(),
  272. width(),
  273. _cache);
  274. return;
  275. }
  276. Ui::Shadow::paint(p, inner, width(), st::boxRoundShadow);
  277. Ui::FillRoundRect(p, inner, st::boxBg, Ui::BoxCorners);
  278. p.setPen(anim::pen(
  279. st::dragColor,
  280. st::dragDropColor,
  281. _a_in.value(_in ? 1. : 0.)));
  282. p.setFont(st::dragFont);
  283. const auto rText = QRect(
  284. 0,
  285. (height() - st::dragHeight) / 2,
  286. width(),
  287. st::dragFont->height);
  288. p.drawText(rText, _text, QTextOption(style::al_top));
  289. p.setFont(st::dragSubfont);
  290. const auto rSubtext = QRect(
  291. 0,
  292. (height() + st::dragHeight) / 2 - st::dragSubfont->height,
  293. width(),
  294. st::dragSubfont->height * 2);
  295. p.drawText(rSubtext, _subtext, QTextOption(style::al_top));
  296. }
  297. void DragArea::dragEnterEvent(QDragEnterEvent *e) {
  298. e->setDropAction(Qt::IgnoreAction);
  299. e->accept();
  300. }
  301. void DragArea::dragLeaveEvent(QDragLeaveEvent *e) {
  302. setIn(false);
  303. }
  304. void DragArea::dropEvent(QDropEvent *e) {
  305. if (e->isAccepted() && _droppedCallback) {
  306. _droppedCallback(e->mimeData());
  307. }
  308. }
  309. void DragArea::otherEnter() {
  310. showStart();
  311. }
  312. void DragArea::otherLeave() {
  313. hideStart();
  314. }
  315. void DragArea::hideFast() {
  316. _a_opacity.stop();
  317. hide();
  318. }
  319. void DragArea::hideStart() {
  320. if (_hiding || isHidden()) {
  321. return;
  322. }
  323. if (_cache.isNull()) {
  324. _cache = Ui::GrabWidget(
  325. this,
  326. rect() - st::dragPadding + st::boxRoundShadow.extend);
  327. }
  328. _hiding = true;
  329. setIn(false);
  330. _a_opacity.start(
  331. [=] { opacityAnimationCallback(); },
  332. 1.,
  333. 0.,
  334. st::boxDuration);
  335. }
  336. void DragArea::hideFinish() {
  337. hide();
  338. _in = false;
  339. _a_in.stop();
  340. }
  341. void DragArea::showStart() {
  342. if (!_hiding && !isHidden()) {
  343. return;
  344. }
  345. _hiding = false;
  346. if (_cache.isNull()) {
  347. _cache = Ui::GrabWidget(
  348. this,
  349. rect() - st::dragPadding + st::boxRoundShadow.extend);
  350. }
  351. show();
  352. _a_opacity.start(
  353. [=] { opacityAnimationCallback(); },
  354. 0.,
  355. 1.,
  356. st::boxDuration);
  357. }
  358. void DragArea::opacityAnimationCallback() {
  359. update();
  360. if (!_a_opacity.animating()) {
  361. _cache = QPixmap();
  362. if (_hiding) {
  363. hideFinish();
  364. }
  365. }
  366. }