settings_scale_preview.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811
  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 "settings/settings_scale_preview.h"
  8. #include "base/platform/base_platform_info.h"
  9. #include "base/event_filter.h"
  10. #include "data/data_user.h"
  11. #include "data/data_peer_values.h"
  12. #include "history/history_item_components.h"
  13. #include "main/main_session.h"
  14. #include "ui/chat/chat_style.h"
  15. #include "ui/chat/chat_theme.h"
  16. #include "ui/image/image_prepare.h"
  17. #include "ui/platform/ui_platform_utility.h"
  18. #include "ui/text/text_options.h"
  19. #include "ui/widgets/shadow.h"
  20. #include "ui/cached_round_corners.h"
  21. #include "ui/painter.h"
  22. #include "ui/ui_utility.h"
  23. #include "window/themes/window_theme.h"
  24. #include "window/section_widget.h"
  25. #include "window/window_controller.h"
  26. #include "window/window_session_controller.h"
  27. #include "styles/style_chat.h"
  28. #include <QtGui/QGuiApplication>
  29. #include <QtGui/QScreen>
  30. namespace Settings {
  31. namespace {
  32. constexpr auto kMinTextWidth = 120;
  33. constexpr auto kMaxTextWidth = 320;
  34. constexpr auto kMaxTextLines = 3;
  35. class Preview final {
  36. public:
  37. Preview(QWidget *slider, rpl::producer<QImage> userpic);
  38. void toggle(ScalePreviewShow show, int scale, int globalX);
  39. private:
  40. void init();
  41. void initAsWindow();
  42. void watchParent();
  43. void reparent();
  44. void updateToScale(int scale);
  45. void updateGlobalPosition(int globalX);
  46. void updateGlobalPosition();
  47. void updateWindowGlobalPosition(QPoint global);
  48. void updateOuterPosition(int globalX);
  49. [[nodiscard]] QRect adjustByScreenGeometry(QRect geometry) const;
  50. void toggleShown(bool shown);
  51. void toggleFilter();
  52. void update();
  53. void paint(Painter &p, QRect clip);
  54. void paintLayer(Painter &p, QRect clip);
  55. void paintInner(Painter &p, QRect clip);
  56. void paintUserpic(Painter &p, QRect clip);
  57. void paintBubble(Painter &p, QRect clip);
  58. void paintContent(Painter &p, QRect clip);
  59. void paintReply(Painter &p, QRect clip);
  60. void paintMessage(Painter &p, QRect clip);
  61. void validateUserpicCache();
  62. void validateBubbleCache();
  63. void validateShadowCache();
  64. [[nodiscard]] int scaled(int value) const;
  65. [[nodiscard]] QPoint scaled(QPoint value) const;
  66. [[nodiscard]] QMargins scaled(QMargins value) const;
  67. [[nodiscard]] style::font scaled(
  68. const style::font &value,
  69. int size) const;
  70. [[nodiscard]] style::QuoteStyle scaled(
  71. const style::QuoteStyle &value) const;
  72. [[nodiscard]] style::TextStyle scaled(
  73. const style::TextStyle &value,
  74. int fontSize) const;
  75. [[nodiscard]] QImage scaled(
  76. const style::icon &icon,
  77. const QColor &color) const;
  78. Ui::RpWidget _widget;
  79. not_null<QWidget*> _slider;
  80. Ui::ChatTheme _theme;
  81. style::TextStyle _nameStyle = st::fwdTextStyle;
  82. Ui::Text::String _nameText = { kMaxTextWidth / 3 };
  83. style::TextStyle _textStyle = st::messageTextStyle;
  84. Ui::Text::String _replyText = { kMaxTextWidth / 3 };
  85. Ui::Text::String _messageText = { kMaxTextWidth / 3 };
  86. style::Shadow _shadow = st::callShadow;
  87. std::array<QImage, 4> _shadowSides;
  88. std::array<QImage, 4> _shadowCorners;
  89. Ui::CornersPixmaps _bubbleCorners;
  90. QPixmap _bubbleShadowBottomRight;
  91. int _bubbleShadow = 0;
  92. int _localShiftLeft = 0;
  93. QImage _bubbleTail;
  94. QRect _replyRect;
  95. QRect _name;
  96. QRect _reply;
  97. QRect _message;
  98. QRect _content;
  99. QRect _bubble;
  100. QRect _userpic;
  101. QRect _inner;
  102. QRect _outer;
  103. QSize _minOuterSize;
  104. QSize _maxOuterSize;
  105. QImage _layer, _canvas;
  106. QPoint _cursor;
  107. std::array<QImage, 4> _canvasCornerMasks;
  108. QImage _userpicOriginal;
  109. QImage _userpicImage;
  110. int _scale = 0;
  111. int _ratio = 0;
  112. bool _window = false;
  113. Ui::Animations::Simple _shownAnimation;
  114. bool _shown = false;
  115. std::unique_ptr<QObject> _filter;
  116. std::unique_ptr<QObject> _parentWatcher;
  117. };
  118. Preview::Preview(QWidget *slider, rpl::producer<QImage> userpic)
  119. : _widget(slider->window())
  120. , _slider(slider)
  121. , _ratio(style::DevicePixelRatio())
  122. , _window(Ui::Platform::TranslucentWindowsSupported()) {
  123. std::move(userpic) | rpl::start_with_next([=](QImage &&userpic) {
  124. _userpicOriginal = std::move(userpic);
  125. if (!_userpicImage.isNull()) {
  126. _userpicImage = {};
  127. update();
  128. }
  129. }, _widget.lifetime());
  130. watchParent();
  131. init();
  132. }
  133. void Preview::watchParent() {
  134. const auto parent = _widget.parentWidget();
  135. _parentWatcher.reset(base::install_event_filter(parent, [=](
  136. not_null<QEvent*> e) {
  137. if (e->type() == QEvent::ParentChange) {
  138. if (_widget.window() != parent) {
  139. reparent();
  140. }
  141. }
  142. return base::EventFilterResult::Continue;
  143. }));
  144. }
  145. void Preview::reparent() {
  146. const auto parent = _widget.parentWidget();
  147. if (!parent) {
  148. // macOS just removes parenting for a _window.
  149. _parentWatcher = nullptr;
  150. return;
  151. }
  152. _widget.setParent(parent->window(), _widget.windowFlags());
  153. if (_shown) {
  154. _widget.show();
  155. updateGlobalPosition();
  156. }
  157. watchParent();
  158. }
  159. void Preview::toggle(ScalePreviewShow show, int scale, int sliderX) {
  160. if (show == ScalePreviewShow::Hide) {
  161. toggleShown(false);
  162. return;
  163. } else if (show == ScalePreviewShow::Update && !_shown) {
  164. return;
  165. }
  166. updateToScale(scale);
  167. updateGlobalPosition(sliderX);
  168. if (_widget.isHidden()) {
  169. Ui::ForceFullRepaintSync(&_widget);
  170. }
  171. toggleShown(true);
  172. }
  173. void Preview::toggleShown(bool shown) {
  174. if (_shown == shown) {
  175. return;
  176. }
  177. _shown = shown;
  178. toggleFilter();
  179. if (_shown) {
  180. _widget.show();
  181. } else if (_widget.isHidden()) {
  182. _shownAnimation.stop();
  183. return;
  184. }
  185. const auto callback = [=] {
  186. update();
  187. if (!_shown && !_shownAnimation.animating()) {
  188. _widget.hide();
  189. }
  190. };
  191. _shownAnimation.start(
  192. callback,
  193. shown ? 0. : 1.,
  194. shown ? 1. : 0.,
  195. st::slideWrapDuration);
  196. }
  197. void Preview::toggleFilter() {
  198. if (!_shown) {
  199. _filter = nullptr;
  200. return;
  201. } else if (_filter) {
  202. return;
  203. }
  204. _filter = std::make_unique<QObject>();
  205. const auto watch = [&](QWidget *widget, const auto &self) -> void {
  206. if (!widget) {
  207. return;
  208. }
  209. base::install_event_filter(_filter.get(), widget, [=](
  210. not_null<QEvent*> e) {
  211. if (e->type() == QEvent::Move
  212. || e->type() == QEvent::Resize
  213. || e->type() == QEvent::Show
  214. || e->type() == QEvent::ShowToParent
  215. || e->type() == QEvent::ZOrderChange) {
  216. updateGlobalPosition();
  217. }
  218. return base::EventFilterResult::Continue;
  219. });
  220. if (!_window && widget == _widget.window()) {
  221. return;
  222. }
  223. self(widget->parentWidget(), self);
  224. };
  225. watch(_slider, watch);
  226. const auto checkDeactivation = [=](Qt::ApplicationState state) {
  227. if (state != Qt::ApplicationActive) {
  228. toggle(ScalePreviewShow::Hide, 0, 0);
  229. }
  230. };
  231. QObject::connect(
  232. qApp,
  233. &QGuiApplication::applicationStateChanged,
  234. _filter.get(),
  235. checkDeactivation,
  236. Qt::QueuedConnection);
  237. }
  238. void Preview::update() {
  239. _widget.update(_outer);
  240. }
  241. void Preview::init() {
  242. const auto background = Window::Theme::Background();
  243. const auto &paper = background->paper();
  244. _theme.setBackground({
  245. .prepared = background->prepared(),
  246. .preparedForTiled = background->preparedForTiled(),
  247. .gradientForFill = background->gradientForFill(),
  248. .colorForFill = background->colorForFill(),
  249. .colors = paper.backgroundColors(),
  250. .patternOpacity = paper.patternOpacity(),
  251. .gradientRotation = paper.gradientRotation(),
  252. .isPattern = paper.isPattern(),
  253. .tile = background->tile(),
  254. });
  255. _widget.paintRequest(
  256. ) | rpl::start_with_next([=](QRect clip) {
  257. auto p = Painter(&_widget);
  258. paint(p, clip);
  259. }, _widget.lifetime());
  260. style::PaletteChanged(
  261. ) | rpl::start_with_next([=] {
  262. _bubbleCorners = {};
  263. _bubbleTail = {};
  264. _bubbleShadowBottomRight = {};
  265. update();
  266. }, _widget.lifetime());
  267. if (_window) {
  268. initAsWindow();
  269. updateToScale(style::kScaleMin);
  270. _minOuterSize = _outer.size();
  271. updateToScale(style::MaxScaleForRatio(_ratio));
  272. _maxOuterSize = _outer.size();
  273. }
  274. }
  275. int Preview::scaled(int value) const {
  276. return style::ConvertScale(value, _scale);
  277. }
  278. QPoint Preview::scaled(QPoint value) const {
  279. return { scaled(value.x()), scaled(value.y()) };
  280. }
  281. QMargins Preview::scaled(QMargins value) const {
  282. return {
  283. scaled(value.left()),
  284. scaled(value.top()),
  285. scaled(value.right()),
  286. scaled(value.bottom()),
  287. };
  288. }
  289. style::font Preview::scaled(const style::font &font, int size) const {
  290. return style::font(scaled(size), font->flags(), font->family());
  291. }
  292. style::QuoteStyle Preview::scaled(const style::QuoteStyle &value) const {
  293. return {
  294. .icon = value.icon,
  295. .scrollable = value.scrollable,
  296. };
  297. }
  298. style::TextStyle Preview::scaled(
  299. const style::TextStyle &value,
  300. int fontSize) const {
  301. return {
  302. .font = scaled(value.font, fontSize),
  303. .linkUnderline = value.linkUnderline,
  304. .blockquote = scaled(value.blockquote),
  305. .pre = scaled(value.pre),
  306. };
  307. }
  308. QImage Preview::scaled(
  309. const style::icon &icon,
  310. const QColor &color) const {
  311. return icon.instance(color, _scale);
  312. }
  313. void Preview::updateToScale(int scale) {
  314. using style::ConvertScale;
  315. if (_scale == scale) {
  316. return;
  317. }
  318. _scale = scale;
  319. _nameStyle = scaled(st::fwdTextStyle, 13);
  320. _textStyle = scaled(st::messageTextStyle, 13);
  321. _textStyle.blockquote.verticalSkip = scaled(4);
  322. _textStyle.blockquote.outline = scaled(3);
  323. _textStyle.blockquote.outlineShift = scaled(2);
  324. _textStyle.blockquote.radius = scaled(5);
  325. _textStyle.blockquote.padding = scaled(QMargins{ 10, 2, 20, 2 });
  326. _textStyle.blockquote.iconPosition = scaled(QPoint{ 4, 4 });
  327. _textStyle.pre.verticalSkip = scaled(4);
  328. _textStyle.pre.outline = scaled(3);
  329. _textStyle.pre.outlineShift = scaled(2);
  330. _textStyle.pre.radius = scaled(5);
  331. _textStyle.pre.header = scaled(20);
  332. _textStyle.pre.headerPosition = scaled(QPoint{ 10, 2 });
  333. _textStyle.pre.padding = scaled(QMargins{ 10, 2, 4, 2 });
  334. _textStyle.pre.iconPosition = scaled(QPoint{ 4, 2 });
  335. _nameText.setText(
  336. _nameStyle,
  337. u"Bob Harris"_q,
  338. Ui::NameTextOptions());
  339. _replyText.setText(
  340. _textStyle,
  341. u"Good morning!"_q,
  342. Ui::ItemTextDefaultOptions());
  343. _messageText.setText(
  344. _textStyle,
  345. u"Do you know what time it is?"_q,
  346. Ui::ItemTextDefaultOptions());
  347. const auto namePosition = QPoint(
  348. scaled(11), // st::historyReplyPadding.left()
  349. scaled(2)); // st::historyReplyPadding.top()
  350. const auto replyPosition = QPoint(
  351. scaled(11), // st::historyReplyPadding.left()
  352. (scaled(2) // st::historyReplyPadding.top()
  353. + _nameStyle.font->height)); // + st::msgServiceNameFont->height
  354. const auto paddingRight = scaled(6); // st::historyReplyPadding.right()
  355. const auto wantedWidth = std::max({
  356. namePosition.x() + _nameText.maxWidth() + paddingRight,
  357. replyPosition.x() + _replyText.maxWidth() + paddingRight,
  358. _messageText.maxWidth(),
  359. });
  360. const auto minTextWidth = scaled(kMinTextWidth);
  361. const auto maxTextWidth = scaled(kMaxTextWidth);
  362. const auto messageWidth = std::clamp(
  363. wantedWidth,
  364. minTextWidth,
  365. maxTextWidth);
  366. const auto messageHeight = std::min(
  367. _messageText.countHeight(maxTextWidth),
  368. kMaxTextLines * _textStyle.font->height);
  369. _replyRect = QRect(
  370. 0, // st::msgReplyBarPos.x(),
  371. scaled(2),// st::historyReplyTop
  372. messageWidth,
  373. (scaled(2) // st::historyReplyPadding.top()
  374. + _nameStyle.font->height // + st::msgServiceNameFont->height
  375. + _textStyle.font->height // + st::normalFont->height
  376. + scaled(2))); // + st::historyReplyPadding.bottom()
  377. _name = QRect(
  378. _replyRect.topLeft() + namePosition,
  379. QSize(messageWidth - namePosition.x(), _nameStyle.font->height));
  380. _reply = QRect(
  381. _replyRect.topLeft() + replyPosition,
  382. QSize(messageWidth - replyPosition.x(), _textStyle.font->height));
  383. _message = QRect(0, 0, messageWidth, messageHeight);
  384. // replyRect.bottom + st::historyReplyBottom;
  385. const auto replySkip = _replyRect.y() + _replyRect.height() + scaled(2);
  386. _message.moveTop(replySkip);
  387. _content = QRect(0, 0, messageWidth, replySkip + messageHeight);
  388. const auto msgPadding = scaled(QMargins(13, 7, 13, 8)); // st::msgPadding
  389. _bubble = _content.marginsAdded(msgPadding);
  390. _content.moveTopLeft(-_bubble.topLeft());
  391. _bubble.moveTopLeft({});
  392. _bubbleShadow = scaled(2); // st::msgShadow
  393. _bubbleCorners = {};
  394. _bubbleTail = {};
  395. _bubbleShadowBottomRight = {};
  396. const auto hasUserpic = !_userpicOriginal.isNull();
  397. const auto bubbleMargin = scaled(QMargins(20, 16, 20, 16));
  398. const auto userpicSkip = hasUserpic ? scaled(40) : 0; // st::msgPhotoSkip
  399. _inner = _bubble.marginsAdded(
  400. bubbleMargin + QMargins(userpicSkip, 0, 0, 0));
  401. _bubble.moveTopLeft(-_inner.topLeft());
  402. _inner.moveTopLeft({});
  403. if (hasUserpic) {
  404. const auto userpicSize = scaled(33); // st::msgPhotoSize
  405. _userpic = QRect(
  406. bubbleMargin.left(),
  407. _bubble.y() + _bubble.height() - userpicSize,
  408. userpicSize,
  409. userpicSize);
  410. _userpicImage = {};
  411. }
  412. _shadow.extend = scaled(QMargins(9, 8, 9, 10)); // st::callShadow.extend
  413. _shadowSides = {};
  414. _shadowCorners = {};
  415. update();
  416. _outer = _inner.marginsAdded(_shadow.extend);
  417. _inner.moveTopLeft(-_outer.topLeft());
  418. _outer.moveTopLeft({});
  419. _layer = QImage(
  420. _outer.size() * _ratio,
  421. QImage::Format_ARGB32_Premultiplied);
  422. _layer.setDevicePixelRatio(_ratio);
  423. _canvas = QImage(
  424. _inner.size() * _ratio,
  425. QImage::Format_ARGB32_Premultiplied);
  426. _canvas.setDevicePixelRatio(_ratio);
  427. _canvas.fill(Qt::transparent);
  428. _canvasCornerMasks = Images::CornersMask(scaled(6)); // st::callRadius
  429. }
  430. void Preview::updateGlobalPosition(int sliderX) {
  431. _localShiftLeft = sliderX;
  432. if (_window) {
  433. updateWindowGlobalPosition(_slider->mapToGlobal(QPoint()));
  434. } else {
  435. updateGlobalPosition();
  436. }
  437. }
  438. void Preview::updateGlobalPosition() {
  439. if (_window) {
  440. const auto global = _slider->mapToGlobal(QPoint());
  441. updateWindowGlobalPosition(global);
  442. } else {
  443. const auto parent = _widget.parentWidget();
  444. const auto global = Ui::MapFrom(parent, _slider, QPoint());
  445. const auto desiredLeft = global.x()
  446. + _localShiftLeft
  447. - (_outer.width() / 2);
  448. const auto desiredTop = global.y() - _outer.height();
  449. const auto requiredRight = std::min(
  450. desiredLeft + _outer.width(),
  451. parent->width());
  452. const auto left = std::max(
  453. std::min(desiredLeft, requiredRight - _outer.width()),
  454. 0);
  455. _widget.setGeometry(QRect(QPoint(left, desiredTop), _outer.size()));
  456. }
  457. _widget.raise();
  458. }
  459. void Preview::updateWindowGlobalPosition(QPoint global) {
  460. const auto desiredLeft = global.x() - (_minOuterSize.width() / 2);
  461. const auto desiredRight = global.x()
  462. + _slider->width()
  463. + (_maxOuterSize.width() / 2);
  464. const auto requiredLeft = desiredRight - _maxOuterSize.width();
  465. const auto left = std::min(desiredLeft, requiredLeft);
  466. const auto requiredRight = left + _maxOuterSize.width();
  467. const auto right = std::max(desiredRight, requiredRight);
  468. const auto top = global.y() - _maxOuterSize.height();
  469. auto result = QRect(left, top, right - left, _maxOuterSize.height());
  470. _widget.setGeometry(adjustByScreenGeometry(result));
  471. updateOuterPosition(global.x() + _localShiftLeft);
  472. }
  473. QRect Preview::adjustByScreenGeometry(QRect geometry) const {
  474. const auto screen = _slider->screen();
  475. if (!screen) {
  476. return geometry;
  477. }
  478. const auto screenGeometry = screen->availableGeometry();
  479. if (!screenGeometry.intersects(geometry)
  480. || screenGeometry.width() < _maxOuterSize.width()
  481. || screenGeometry.height() < _maxOuterSize.height()) {
  482. return geometry;
  483. }
  484. const auto edgeLeft = screenGeometry.x();
  485. const auto edgeRight = screenGeometry.x() + screenGeometry.width();
  486. const auto edgedRight = std::min(
  487. edgeRight,
  488. geometry.x() + geometry.width());
  489. const auto left = std::max(
  490. std::min(geometry.x(), edgedRight - _maxOuterSize.width()),
  491. edgeLeft);
  492. const auto right = std::max(edgedRight, left + _maxOuterSize.width());
  493. return { left, geometry.y(), right - left, geometry.height() };
  494. }
  495. void Preview::updateOuterPosition(int globalX) {
  496. if (_window) {
  497. update();
  498. const auto global = _widget.geometry();
  499. const auto desiredLeft = globalX
  500. - (_outer.width() / 2)
  501. - global.x();
  502. _outer.moveLeft(std::max(
  503. std::min(desiredLeft, global.width() - _outer.width()),
  504. 0));
  505. _outer.moveTop(_maxOuterSize.height() - _outer.height());
  506. update();
  507. }
  508. }
  509. void Preview::paint(Painter &p, QRect clip) {
  510. //p.setCompositionMode(QPainter::CompositionMode_Source);
  511. //p.fillRect(clip, Qt::transparent);
  512. //p.setCompositionMode(QPainter::CompositionMode_SourceOver);
  513. const auto outer = clip.intersected(_outer);
  514. if (outer.isEmpty()) {
  515. return;
  516. }
  517. const auto local = outer.translated(-_outer.topLeft());
  518. auto q = Painter(&_layer);
  519. q.setClipRect(local);
  520. paintLayer(q, local);
  521. q.end();
  522. const auto shown = _shownAnimation.value(_shown ? 1. : 0.);
  523. p.setClipRect(clip);
  524. p.setOpacity(shown);
  525. auto hq = std::optional<PainterHighQualityEnabler>();
  526. if (shown < 1.) {
  527. const auto middle = _outer.x() + (_outer.width() / 2);
  528. const auto bottom = _outer.y() + _outer.height();
  529. const auto scale = 0.3 + shown * 0.7;
  530. p.translate(middle, bottom);
  531. p.scale(scale, scale);
  532. p.translate(-middle, -bottom);
  533. hq.emplace(p);
  534. }
  535. p.drawImage(_outer.topLeft(), _layer);
  536. }
  537. void Preview::paintLayer(Painter &p, QRect clip) {
  538. p.setCompositionMode(QPainter::CompositionMode_Source);
  539. validateShadowCache();
  540. Ui::Shadow::paint(
  541. p,
  542. _inner,
  543. _outer.width(),
  544. _shadow,
  545. _shadowSides,
  546. _shadowCorners);
  547. const auto inner = clip.intersected(_inner);
  548. if (inner.isEmpty()) {
  549. return;
  550. }
  551. const auto local = inner.translated(-_inner.topLeft());
  552. auto q = Painter(&_canvas);
  553. q.setClipRect(local);
  554. paintInner(q, local);
  555. q.end();
  556. _canvas = Images::Round(std::move(_canvas), _canvasCornerMasks);
  557. p.setCompositionMode(QPainter::CompositionMode_SourceOver);
  558. p.drawImage(_inner.topLeft(), _canvas);
  559. }
  560. void Preview::paintInner(Painter &p, QRect clip) {
  561. Window::SectionWidget::PaintBackground(
  562. p,
  563. &_theme,
  564. QSize(_inner.width(), _inner.width() * 3),
  565. clip);
  566. paintUserpic(p, clip);
  567. p.translate(_bubble.topLeft());
  568. paintBubble(p, clip.translated(-_bubble.topLeft()));
  569. }
  570. void Preview::paintUserpic(Painter &p, QRect clip) {
  571. if (clip.intersected(_userpic).isEmpty()) {
  572. return;
  573. }
  574. validateUserpicCache();
  575. p.drawImage(_userpic.topLeft(), _userpicImage);
  576. }
  577. void Preview::paintBubble(Painter &p, QRect clip) {
  578. validateBubbleCache();
  579. const auto bubble = QRect(QPoint(), _bubble.size());
  580. const auto cornerShadow = _bubbleShadowBottomRight.size()
  581. / _bubbleShadowBottomRight.devicePixelRatio();
  582. p.drawPixmap(
  583. bubble.width() - cornerShadow.width(),
  584. bubble.height() + _bubbleShadow - cornerShadow.height(),
  585. _bubbleShadowBottomRight);
  586. Ui::FillRoundRect(p, bubble, st::msgInBg, _bubbleCorners);
  587. const auto tail = _bubbleTail.size() / _bubbleTail.devicePixelRatio();
  588. p.drawImage(-tail.width(), bubble.height() - tail.height(), _bubbleTail);
  589. p.fillRect(
  590. -tail.width(),
  591. bubble.height(),
  592. tail.width() + bubble.width() - cornerShadow.width(),
  593. _bubbleShadow,
  594. st::msgInShadow);
  595. const auto content = clip.intersected(_content);
  596. if (content.isEmpty()) {
  597. return;
  598. }
  599. p.translate(_content.topLeft());
  600. const auto local = content.translated(-_content.topLeft());
  601. p.setClipRect(local);
  602. paintContent(p, local);
  603. }
  604. void Preview::paintContent(Painter &p, QRect clip) {
  605. paintReply(p, clip);
  606. const auto message = clip.intersected(_message);
  607. if (message.isEmpty()) {
  608. return;
  609. }
  610. p.translate(_message.topLeft());
  611. const auto local = message.translated(-_message.topLeft());
  612. p.setClipRect(local);
  613. paintMessage(p, local);
  614. }
  615. void Preview::paintReply(Painter &p, QRect clip) {
  616. {
  617. auto hq = PainterHighQualityEnabler(p);
  618. p.setPen(Qt::NoPen);
  619. p.setBrush(st::msgInReplyBarColor);
  620. const auto outline = _textStyle.blockquote.outline;
  621. const auto radius = _textStyle.blockquote.radius;
  622. p.setOpacity(Ui::kDefaultOutline1Opacity);
  623. p.setClipRect(
  624. _replyRect.x(),
  625. _replyRect.y(),
  626. outline,
  627. _replyRect.height());
  628. p.drawRoundedRect(_replyRect, radius, radius);
  629. p.setOpacity(Ui::kDefaultBgOpacity);
  630. p.setClipRect(
  631. _replyRect.x() + outline,
  632. _replyRect.y(),
  633. _replyRect.width() - outline,
  634. _replyRect.height());
  635. p.drawRoundedRect(_replyRect, radius, radius);
  636. }
  637. p.setOpacity(1.);
  638. p.setClipping(false);
  639. p.setPen(st::msgInServiceFg);
  640. _nameText.drawLeftElided(
  641. p,
  642. _name.x(),
  643. _name.y(),
  644. _name.width(),
  645. _content.width());
  646. p.setPen(st::historyTextInFg);
  647. _replyText.drawLeftElided(
  648. p,
  649. _reply.x(),
  650. _reply.y(),
  651. _reply.width(),
  652. _content.width());
  653. }
  654. void Preview::paintMessage(Painter &p, QRect clip) {
  655. p.setPen(st::historyTextInFg);
  656. _messageText.drawLeftElided(
  657. p,
  658. 0,
  659. 0,
  660. _message.width(),
  661. _message.width(),
  662. kMaxTextLines);
  663. }
  664. void Preview::validateUserpicCache() {
  665. if (!_userpicImage.isNull()
  666. || _userpicOriginal.isNull()
  667. || _userpic.isEmpty()) {
  668. return;
  669. }
  670. _userpicImage = Images::Circle(_userpicOriginal.scaled(
  671. _userpic.size() * _ratio,
  672. Qt::IgnoreAspectRatio,
  673. Qt::SmoothTransformation));
  674. _userpicImage.setDevicePixelRatio(_ratio);
  675. }
  676. void Preview::validateBubbleCache() {
  677. if (!_bubbleCorners.p[0].isNull()) {
  678. return;
  679. }
  680. const auto radius = scaled(16); // st::bubbleRadiusLarge
  681. _bubbleCorners = Ui::PrepareCornerPixmaps(radius, st::msgInBg);
  682. _bubbleCorners.p[2] = {};
  683. _bubbleTail = scaled(st::historyBubbleTailInLeft, st::msgInBg->c);
  684. _bubbleShadowBottomRight
  685. = Ui::PrepareCornerPixmaps(radius, st::msgInShadow).p[3];
  686. }
  687. void Preview::validateShadowCache() {
  688. if (!_shadowSides[0].isNull()) {
  689. return;
  690. }
  691. const auto &shadowColor = st::windowShadowFg->c;
  692. _shadowSides[0] = scaled(st::callShadow.left, shadowColor);
  693. _shadowSides[1] = scaled(st::callShadow.top, shadowColor);
  694. _shadowSides[2] = scaled(st::callShadow.right, shadowColor);
  695. _shadowSides[3] = scaled(st::callShadow.bottom, shadowColor);
  696. _shadowCorners[0] = scaled(st::callShadow.topLeft, shadowColor);
  697. _shadowCorners[1] = scaled(st::callShadow.bottomLeft, shadowColor);
  698. _shadowCorners[2] = scaled(st::callShadow.topRight, shadowColor);
  699. _shadowCorners[3] = scaled(st::callShadow.bottomRight, shadowColor);
  700. }
  701. void Preview::initAsWindow() {
  702. _widget.setWindowFlags(Qt::WindowFlags(Qt::FramelessWindowHint)
  703. | Qt::BypassWindowManagerHint
  704. | Qt::NoDropShadowWindowHint
  705. | Qt::ToolTip);
  706. _widget.setAttribute(Qt::WA_TransparentForMouseEvents);
  707. _widget.hide();
  708. _widget.setAttribute(Qt::WA_NoSystemBackground);
  709. _widget.setAttribute(Qt::WA_TranslucentBackground);
  710. }
  711. } // namespace
  712. [[nodiscard]] Fn<void(ScalePreviewShow, int, int)> SetupScalePreview(
  713. not_null<Window::Controller*> window,
  714. not_null<Ui::RpWidget*> slider) {
  715. const auto controller = window->sessionController();
  716. const auto user = controller
  717. ? controller->session().user().get()
  718. : nullptr;
  719. const auto preview = slider->lifetime().make_state<Preview>(
  720. slider.get(),
  721. user ? Data::PeerUserpicImageValue(user, 160, 0) : nullptr);
  722. return [=](ScalePreviewShow show, int scale, int globalX) {
  723. preview->toggle(show, scale, globalX);
  724. };
  725. }
  726. } // namespace Settings