section_widget.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  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 "window/section_widget.h"
  8. #include "mainwidget.h"
  9. #include "mainwindow.h"
  10. #include "ui/ui_utility.h"
  11. #include "ui/chat/chat_theme.h"
  12. #include "ui/painter.h"
  13. #include "boxes/premium_preview_box.h"
  14. #include "data/data_peer.h"
  15. #include "data/data_user.h"
  16. #include "data/data_document.h"
  17. #include "data/data_document_media.h"
  18. #include "data/data_changes.h"
  19. #include "data/data_session.h"
  20. #include "data/data_cloud_themes.h"
  21. #include "data/data_message_reactions.h"
  22. #include "data/data_peer_values.h"
  23. #include "history/history.h"
  24. #include "history/history_item.h"
  25. #include "settings/settings_premium.h"
  26. #include "main/main_session.h"
  27. #include "window/section_memento.h"
  28. #include "window/window_slide_animation.h"
  29. #include "window/window_session_controller.h"
  30. #include "window/themes/window_theme.h"
  31. #include <rpl/range.h>
  32. namespace Window {
  33. namespace {
  34. [[nodiscard]] rpl::producer<QString> PeerThemeEmojiValue(
  35. not_null<PeerData*> peer) {
  36. return peer->session().changes().peerFlagsValue(
  37. peer,
  38. Data::PeerUpdate::Flag::ChatThemeEmoji
  39. ) | rpl::map([=] {
  40. return peer->themeEmoji();
  41. });
  42. }
  43. struct ResolvedPaper {
  44. Data::WallPaper paper;
  45. std::shared_ptr<Data::DocumentMedia> media;
  46. };
  47. [[nodiscard]] rpl::producer<const Data::WallPaper*> PeerWallPaperMapped(
  48. not_null<PeerData*> peer) {
  49. return peer->session().changes().peerFlagsValue(
  50. peer,
  51. Data::PeerUpdate::Flag::ChatWallPaper
  52. ) | rpl::map([=]() -> rpl::producer<const Data::WallPaper*> {
  53. return WallPaperResolved(&peer->owner(), peer->wallPaper());
  54. }) | rpl::flatten_latest();
  55. }
  56. [[nodiscard]] rpl::producer<std::optional<ResolvedPaper>> PeerWallPaperValue(
  57. not_null<PeerData*> peer) {
  58. return PeerWallPaperMapped(
  59. peer
  60. ) | rpl::map([=](const Data::WallPaper *paper)
  61. -> rpl::producer<std::optional<ResolvedPaper>> {
  62. const auto single = [](std::optional<ResolvedPaper> value) {
  63. return rpl::single(std::move(value));
  64. };
  65. if (!paper) {
  66. return single({});
  67. }
  68. const auto document = paper->document();
  69. auto value = ResolvedPaper{
  70. *paper,
  71. document ? document->createMediaView() : nullptr,
  72. };
  73. if (!value.media || value.media->loaded(true)) {
  74. return single(std::move(value));
  75. }
  76. paper->loadDocument();
  77. return single(
  78. value
  79. ) | rpl::then(document->session().downloaderTaskFinished(
  80. ) | rpl::filter([=] {
  81. return value.media->loaded(true);
  82. }) | rpl::take(1) | rpl::map_to(
  83. std::optional<ResolvedPaper>(value)
  84. ));
  85. }) | rpl::flatten_latest();
  86. }
  87. [[nodiscard]] auto MaybeChatThemeDataValueFromPeer(
  88. not_null<PeerData*> peer)
  89. -> rpl::producer<std::optional<Data::CloudTheme>> {
  90. return PeerThemeEmojiValue(
  91. peer
  92. ) | rpl::map([=](const QString &emoji)
  93. -> rpl::producer<std::optional<Data::CloudTheme>> {
  94. return peer->owner().cloudThemes().themeForEmojiValue(emoji);
  95. }) | rpl::flatten_latest();
  96. }
  97. [[nodiscard]] rpl::producer<> DebouncedPaletteValue() {
  98. return [=](auto consumer) {
  99. auto lifetime = rpl::lifetime();
  100. struct State {
  101. base::has_weak_ptr guard;
  102. bool scheduled = false;
  103. };
  104. const auto state = lifetime.make_state<State>();
  105. consumer.put_next_copy(rpl::empty);
  106. style::PaletteChanged(
  107. ) | rpl::start_with_next([=] {
  108. if (state->scheduled) {
  109. return;
  110. }
  111. state->scheduled = true;
  112. Ui::PostponeCall(&state->guard, [=] {
  113. state->scheduled = false;
  114. consumer.put_next_copy(rpl::empty);
  115. });
  116. }, lifetime);
  117. return lifetime;
  118. };
  119. }
  120. struct ResolvedTheme {
  121. std::optional<Data::CloudTheme> theme;
  122. std::optional<ResolvedPaper> paper;
  123. bool dark = false;
  124. };
  125. [[nodiscard]] auto MaybeCloudThemeValueFromPeer(
  126. not_null<PeerData*> peer)
  127. -> rpl::producer<ResolvedTheme> {
  128. return rpl::combine(
  129. MaybeChatThemeDataValueFromPeer(peer),
  130. PeerWallPaperValue(peer),
  131. Theme::IsThemeDarkValue() | rpl::distinct_until_changed()
  132. ) | rpl::map([](
  133. std::optional<Data::CloudTheme> theme,
  134. std::optional<ResolvedPaper> paper,
  135. bool night) -> rpl::producer<ResolvedTheme> {
  136. if (theme || !paper) {
  137. return rpl::single<ResolvedTheme>({
  138. std::move(theme),
  139. std::move(paper),
  140. night,
  141. });
  142. }
  143. return DebouncedPaletteValue(
  144. ) | rpl::map([=] {
  145. return ResolvedTheme{
  146. .paper = paper,
  147. .dark = night,
  148. };
  149. });
  150. }) | rpl::flatten_latest();
  151. }
  152. } // namespace
  153. rpl::producer<const Data::WallPaper*> WallPaperResolved(
  154. not_null<Data::Session*> owner,
  155. const Data::WallPaper *paper) {
  156. const auto id = paper ? paper->emojiId() : QString();
  157. if (id.isEmpty()) {
  158. return rpl::single(paper);
  159. }
  160. const auto themes = &owner->cloudThemes();
  161. auto fromThemes = [=](bool force)
  162. -> rpl::producer<const Data::WallPaper*> {
  163. if (themes->chatThemes().empty() && !force) {
  164. return nullptr;
  165. }
  166. return Window::Theme::IsNightModeValue(
  167. ) | rpl::map([=](bool dark) -> const Data::WallPaper* {
  168. const auto &list = themes->chatThemes();
  169. const auto i = ranges::find(
  170. list,
  171. id,
  172. &Data::CloudTheme::emoticon);
  173. if (i != end(list)) {
  174. using Type = Data::CloudThemeType;
  175. const auto type = dark ? Type::Dark : Type::Light;
  176. const auto j = i->settings.find(type);
  177. if (j != end(i->settings) && j->second.paper) {
  178. return &*j->second.paper;
  179. }
  180. }
  181. return nullptr;
  182. });
  183. };
  184. if (auto result = fromThemes(false)) {
  185. return result;
  186. }
  187. themes->refreshChatThemes();
  188. return rpl::single<const Data::WallPaper*>(
  189. nullptr
  190. ) | rpl::then(themes->chatThemesUpdated(
  191. ) | rpl::take(1) | rpl::map([=] {
  192. return fromThemes(true);
  193. }) | rpl::flatten_latest());
  194. }
  195. AbstractSectionWidget::AbstractSectionWidget(
  196. QWidget *parent,
  197. not_null<SessionController*> controller,
  198. rpl::producer<PeerData*> peerForBackground)
  199. : RpWidget(parent)
  200. , _controller(controller) {
  201. std::move(
  202. peerForBackground
  203. ) | rpl::map([=](PeerData *peer) -> rpl::producer<> {
  204. if (!peer) {
  205. return rpl::single(rpl::empty) | rpl::then(
  206. controller->defaultChatTheme()->repaintBackgroundRequests()
  207. );
  208. }
  209. return ChatThemeValueFromPeer(
  210. controller,
  211. peer
  212. ) | rpl::map([](const std::shared_ptr<Ui::ChatTheme> &theme) {
  213. return rpl::single(rpl::empty) | rpl::then(
  214. theme->repaintBackgroundRequests()
  215. );
  216. }) | rpl::flatten_latest();
  217. }) | rpl::flatten_latest() | rpl::start_with_next([=] {
  218. update();
  219. }, lifetime());
  220. }
  221. Main::Session &AbstractSectionWidget::session() const {
  222. return _controller->session();
  223. }
  224. SectionWidget::SectionWidget(
  225. QWidget *parent,
  226. not_null<Window::SessionController*> controller,
  227. rpl::producer<PeerData*> peerForBackground)
  228. : AbstractSectionWidget(parent, controller, std::move(peerForBackground)) {
  229. }
  230. SectionWidget::SectionWidget(
  231. QWidget *parent,
  232. not_null<Window::SessionController*> controller,
  233. not_null<PeerData*> peerForBackground)
  234. : AbstractSectionWidget(
  235. parent,
  236. controller,
  237. rpl::single(peerForBackground.get())) {
  238. }
  239. void SectionWidget::setGeometryWithTopMoved(
  240. const QRect &newGeometry,
  241. int topDelta) {
  242. _topDelta = topDelta;
  243. bool willBeResized = (size() != newGeometry.size());
  244. if (geometry() != newGeometry) {
  245. auto weak = Ui::MakeWeak(this);
  246. setGeometry(newGeometry);
  247. if (!weak) {
  248. return;
  249. }
  250. }
  251. if (!willBeResized) {
  252. resizeEvent(nullptr);
  253. }
  254. _topDelta = 0;
  255. }
  256. void SectionWidget::showAnimated(
  257. SlideDirection direction,
  258. const SectionSlideParams &params) {
  259. if (_showAnimation) {
  260. return;
  261. }
  262. showChildren();
  263. auto myContentCache = grabForShowAnimation(params);
  264. hideChildren();
  265. showAnimatedHook(params);
  266. _showAnimation = std::make_unique<SlideAnimation>();
  267. _showAnimation->setDirection(direction);
  268. _showAnimation->setRepaintCallback([this] { update(); });
  269. _showAnimation->setFinishedCallback([this] { showFinished(); });
  270. _showAnimation->setPixmaps(
  271. params.oldContentCache,
  272. myContentCache);
  273. _showAnimation->setTopBarShadow(params.withTopBarShadow);
  274. _showAnimation->setWithFade(params.withFade);
  275. _showAnimation->setTopSkip(params.topSkip);
  276. _showAnimation->setTopBarMask(params.topMask);
  277. _showAnimation->start();
  278. show();
  279. }
  280. std::shared_ptr<SectionMemento> SectionWidget::createMemento() {
  281. return nullptr;
  282. }
  283. void SectionWidget::showFast() {
  284. show();
  285. showFinished();
  286. }
  287. QPixmap SectionWidget::grabForShowAnimation(
  288. const SectionSlideParams &params) {
  289. return Ui::GrabWidget(this);
  290. }
  291. void SectionWidget::PaintBackground(
  292. not_null<Window::SessionController*> controller,
  293. not_null<Ui::ChatTheme*> theme,
  294. not_null<QWidget*> widget,
  295. QRect clip) {
  296. PaintBackground(
  297. theme,
  298. widget,
  299. controller->content()->height(),
  300. controller->content()->backgroundFromY(),
  301. clip);
  302. }
  303. void SectionWidget::PaintBackground(
  304. not_null<Ui::ChatTheme*> theme,
  305. not_null<QWidget*> widget,
  306. int fillHeight,
  307. int fromy,
  308. QRect clip) {
  309. auto p = QPainter(widget);
  310. if (fromy) {
  311. p.translate(0, fromy);
  312. clip = clip.translated(0, -fromy);
  313. }
  314. PaintBackground(p, theme, QSize(widget->width(), fillHeight), clip);
  315. }
  316. void SectionWidget::PaintBackground(
  317. QPainter &p,
  318. not_null<Ui::ChatTheme*> theme,
  319. QSize fill,
  320. QRect clip) {
  321. const auto &background = theme->background();
  322. if (background.colorForFill) {
  323. p.fillRect(clip, *background.colorForFill);
  324. return;
  325. }
  326. const auto &gradient = background.gradientForFill;
  327. auto state = theme->backgroundState(fill);
  328. const auto paintCache = [&](const Ui::CachedBackground &cache) {
  329. const auto to = QRect(
  330. QPoint(cache.x, cache.y),
  331. cache.pixmap.size() / style::DevicePixelRatio());
  332. if (cache.waitingForNegativePattern) {
  333. // While we wait for pattern being loaded we paint just gradient.
  334. // But in case of negative patter opacity we just fill-black.
  335. p.fillRect(to, Qt::black);
  336. } else if (cache.area == fill) {
  337. p.drawPixmap(to, cache.pixmap);
  338. } else {
  339. const auto sx = fill.width() / float64(cache.area.width());
  340. const auto sy = fill.height() / float64(cache.area.height());
  341. const auto round = [](float64 value) -> int {
  342. return (value >= 0.)
  343. ? int(std::ceil(value))
  344. : int(std::floor(value));
  345. };
  346. const auto sto = QPoint(round(to.x() * sx), round(to.y() * sy));
  347. p.drawPixmap(
  348. sto.x(),
  349. sto.y(),
  350. round((to.x() + to.width()) * sx) - sto.x(),
  351. round((to.y() + to.height()) * sy) - sto.y(),
  352. cache.pixmap);
  353. }
  354. };
  355. const auto hasNow = !state.now.pixmap.isNull();
  356. const auto goodNow = hasNow && (state.now.area == fill);
  357. const auto useCache = goodNow || !gradient.isNull();
  358. if (useCache) {
  359. const auto fade = (state.shown < 1. && !gradient.isNull());
  360. if (fade) {
  361. paintCache(state.was);
  362. p.setOpacity(state.shown);
  363. }
  364. paintCache(state.now);
  365. if (fade) {
  366. p.setOpacity(1.);
  367. }
  368. return;
  369. }
  370. const auto &prepared = background.prepared;
  371. if (prepared.isNull()) {
  372. return;
  373. } else if (background.isPattern) {
  374. const auto w = prepared.width() * fill.height() / prepared.height();
  375. const auto cx = qCeil(fill.width() / float64(w));
  376. const auto cols = (cx / 2) * 2 + 1;
  377. const auto xshift = (fill.width() - w * cols) / 2;
  378. for (auto i = 0; i != cols; ++i) {
  379. p.drawImage(
  380. QRect(xshift + i * w, 0, w, fill.height()),
  381. prepared,
  382. QRect(QPoint(), prepared.size()));
  383. }
  384. } else if (background.tile) {
  385. const auto &tiled = background.preparedForTiled;
  386. const auto left = clip.left();
  387. const auto top = clip.top();
  388. const auto right = clip.left() + clip.width();
  389. const auto bottom = clip.top() + clip.height();
  390. const auto w = tiled.width() / float64(style::DevicePixelRatio());
  391. const auto h = tiled.height() / float64(style::DevicePixelRatio());
  392. const auto sx = qFloor(left / w);
  393. const auto sy = qFloor(top / h);
  394. const auto cx = qCeil(right / w);
  395. const auto cy = qCeil(bottom / h);
  396. for (auto i = sx; i < cx; ++i) {
  397. for (auto j = sy; j < cy; ++j) {
  398. p.drawImage(QPointF(i * w, j * h), tiled);
  399. }
  400. }
  401. } else {
  402. const auto hq = PainterHighQualityEnabler(p);
  403. const auto rects = Ui::ComputeChatBackgroundRects(
  404. fill,
  405. prepared.size());
  406. p.drawImage(rects.to, prepared, rects.from);
  407. }
  408. }
  409. void SectionWidget::paintEvent(QPaintEvent *e) {
  410. if (_showAnimation) {
  411. auto p = QPainter(this);
  412. _showAnimation->paintContents(p);
  413. }
  414. }
  415. bool SectionWidget::animatingShow() const {
  416. return (_showAnimation != nullptr);
  417. }
  418. void SectionWidget::showFinished() {
  419. _showAnimation.reset();
  420. if (isHidden()) return;
  421. showChildren();
  422. showFinishedHook();
  423. if (isAncestorOf(window()->focusWidget())) {
  424. setInnerFocus();
  425. } else {
  426. controller()->widget()->setInnerFocus();
  427. }
  428. }
  429. rpl::producer<int> SectionWidget::desiredHeight() const {
  430. return rpl::single(height());
  431. }
  432. SectionWidget::~SectionWidget() = default;
  433. auto ChatThemeValueFromPeer(
  434. not_null<SessionController*> controller,
  435. not_null<PeerData*> peer)
  436. -> rpl::producer<std::shared_ptr<Ui::ChatTheme>> {
  437. auto cloud = MaybeCloudThemeValueFromPeer(
  438. peer
  439. ) | rpl::map([=](ResolvedTheme resolved)
  440. -> rpl::producer<std::shared_ptr<Ui::ChatTheme>> {
  441. if (!resolved.theme && !resolved.paper) {
  442. return rpl::single(controller->defaultChatTheme());
  443. }
  444. const auto theme = resolved.theme.value_or(Data::CloudTheme());
  445. const auto paper = resolved.paper
  446. ? resolved.paper->paper
  447. : Data::WallPaper(0);
  448. const auto type = resolved.dark
  449. ? Data::CloudThemeType::Dark
  450. : Data::CloudThemeType::Light;
  451. if (paper.document()
  452. && resolved.paper->media
  453. && !resolved.paper->media->loaded()
  454. && !controller->chatThemeAlreadyCached(theme, paper, type)) {
  455. return rpl::single(controller->defaultChatTheme());
  456. }
  457. return controller->cachedChatThemeValue(theme, paper, type);
  458. }) | rpl::flatten_latest(
  459. ) | rpl::distinct_until_changed();
  460. return rpl::combine(
  461. std::move(cloud),
  462. controller->peerThemeOverrideValue()
  463. ) | rpl::map([=](
  464. std::shared_ptr<Ui::ChatTheme> &&cloud,
  465. PeerThemeOverride &&overriden) {
  466. return (overriden.peer == peer.get()
  467. && Ui::Emoji::Find(peer->themeEmoji()) != overriden.emoji)
  468. ? std::move(overriden.theme)
  469. : std::move(cloud);
  470. });
  471. }
  472. bool ShowSendPremiumError(
  473. not_null<SessionController*> controller,
  474. not_null<DocumentData*> document) {
  475. return ShowSendPremiumError(controller->uiShow(), document);
  476. }
  477. bool ShowSendPremiumError(
  478. std::shared_ptr<ChatHelpers::Show> show,
  479. not_null<DocumentData*> document) {
  480. if (!document->isPremiumSticker()
  481. || document->session().premium()) {
  482. return false;
  483. }
  484. ShowStickerPreviewBox(std::move(show), document);
  485. return true;
  486. }
  487. bool ShowReactPremiumError(
  488. not_null<SessionController*> controller,
  489. not_null<HistoryItem*> item,
  490. const Data::ReactionId &id) {
  491. if (item->reactionsAreTags()) {
  492. if (controller->session().premium()) {
  493. return false;
  494. }
  495. ShowPremiumPreviewBox(controller, PremiumFeature::TagsForMessages);
  496. return true;
  497. } else if (controller->session().premium()
  498. || ranges::contains(item->chosenReactions(), id)
  499. || item->history()->peer->isBroadcast()) {
  500. return false;
  501. } else if (!id.custom()) {
  502. return false;
  503. }
  504. ShowPremiumPreviewBox(controller, PremiumFeature::InfiniteReactions);
  505. return true;
  506. }
  507. } // namespace Window