message_bar.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669
  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 "ui/chat/message_bar.h"
  8. #include "ui/effects/spoiler_mess.h"
  9. #include "ui/image/image_prepare.h"
  10. #include "ui/painter.h"
  11. #include "ui/power_saving.h"
  12. #include "ui/text/text_options.h"
  13. #include "ui/ui_utility.h"
  14. #include "styles/style_chat.h"
  15. #include "styles/style_chat_helpers.h"
  16. #include "styles/palette.h"
  17. namespace Ui {
  18. namespace {
  19. [[nodiscard]] int SameFirstPartLength(const QString &a, const QString &b) {
  20. const auto &[i, j] = ranges::mismatch(a, b);
  21. return (i - a.begin());
  22. }
  23. [[nodiscard]] bool MuchDifferent(
  24. int same,
  25. const QString &a,
  26. const QString &b) {
  27. return (same * 2 < a.size()) || (same * 2 < b.size());
  28. }
  29. [[nodiscard]] bool MuchDifferent(const QString &a, const QString &b) {
  30. return MuchDifferent(SameFirstPartLength(a, b), a, b);
  31. }
  32. [[nodiscard]] bool ComplexTitleAnimation(
  33. int same,
  34. const QString &a,
  35. const QString &b) {
  36. return !MuchDifferent(same, a, b)
  37. && (same != a.size() || same != b.size());
  38. }
  39. } // namespace
  40. MessageBar::MessageBar(
  41. not_null<QWidget*> parent,
  42. const style::MessageBar &st,
  43. Fn<bool()> customEmojiPaused)
  44. : _st(st)
  45. , _widget(parent)
  46. , _customEmojiPaused(std::move(customEmojiPaused)) {
  47. setup();
  48. style::PaletteChanged(
  49. ) | rpl::start_with_next([=] {
  50. _topBarGradient = _bottomBarGradient = QPixmap();
  51. }, _widget.lifetime());
  52. }
  53. void MessageBar::customEmojiRepaint() {
  54. if (_customEmojiRepaintScheduled) {
  55. return;
  56. }
  57. _customEmojiRepaintScheduled = true;
  58. _widget.update();
  59. }
  60. void MessageBar::setup() {
  61. _widget.resize(0, st::historyReplyHeight);
  62. _widget.paintRequest(
  63. ) | rpl::start_with_next([=](QRect rect) {
  64. auto p = Painter(&_widget);
  65. p.setInactive(_customEmojiPaused());
  66. _customEmojiRepaintScheduled = false;
  67. paint(p);
  68. }, _widget.lifetime());
  69. }
  70. void MessageBar::set(MessageBarContent &&content) {
  71. _contentLifetime.destroy();
  72. tweenTo(std::move(content));
  73. }
  74. void MessageBar::set(rpl::producer<MessageBarContent> content) {
  75. _contentLifetime.destroy();
  76. std::move(
  77. content
  78. ) | rpl::start_with_next([=](MessageBarContent &&content) {
  79. tweenTo(std::move(content));
  80. }, _contentLifetime);
  81. }
  82. MessageBar::BodyAnimation MessageBar::DetectBodyAnimationType(
  83. Animation *currentAnimation,
  84. const MessageBarContent &currentContent,
  85. const MessageBarContent &nextContent) {
  86. const auto now = currentAnimation
  87. ? currentAnimation->bodyAnimation
  88. : BodyAnimation::None;
  89. const auto somethingChanged = (currentContent.text != nextContent.text)
  90. || (currentContent.title != nextContent.title)
  91. || (currentContent.index != nextContent.index)
  92. || (currentContent.count != nextContent.count);
  93. return (now == BodyAnimation::Full
  94. || MuchDifferent(currentContent.title, nextContent.title)
  95. || (currentContent.title.isEmpty() && somethingChanged))
  96. ? BodyAnimation::Full
  97. : (now == BodyAnimation::Text || somethingChanged)
  98. ? BodyAnimation::Text
  99. : BodyAnimation::None;
  100. }
  101. void MessageBar::tweenTo(MessageBarContent &&content) {
  102. Expects(content.count > 0);
  103. Expects(content.index >= 0 && content.index < content.count);
  104. _widget.update();
  105. if (!_st.duration || anim::Disabled() || _widget.size().isEmpty()) {
  106. updateFromContent(std::move(content));
  107. return;
  108. }
  109. const auto hasImageChanged = (_content.preview.isNull()
  110. != content.preview.isNull());
  111. const auto bodyChanged = (_content.index != content.index
  112. || _content.count != content.count
  113. || _content.title != content.title
  114. || _content.text != content.text
  115. || _content.preview.constBits() != content.preview.constBits());
  116. const auto barCountChanged = (_content.count != content.count);
  117. const auto barFrom = _content.index;
  118. const auto barTo = content.index;
  119. auto animation = Animation();
  120. animation.bodyAnimation = DetectBodyAnimationType(
  121. _animation.get(),
  122. _content,
  123. content);
  124. animation.movingTo = (content.index > _content.index)
  125. ? RectPart::Top
  126. : (content.index < _content.index)
  127. ? RectPart::Bottom
  128. : RectPart::None;
  129. animation.imageFrom = grabImagePart();
  130. animation.spoilerFrom = std::move(_spoiler);
  131. animation.bodyOrTextFrom = grabBodyOrTextPart(animation.bodyAnimation);
  132. const auto sameLength = SameFirstPartLength(
  133. _content.title,
  134. content.title);
  135. if (animation.bodyAnimation == BodyAnimation::Text
  136. && ComplexTitleAnimation(sameLength, _content.title, content.title)) {
  137. animation.titleSame = grabTitleBase(sameLength);
  138. animation.titleFrom = grabTitlePart(sameLength);
  139. }
  140. auto was = std::move(_animation);
  141. updateFromContent(std::move(content));
  142. animation.imageTo = grabImagePart();
  143. animation.bodyOrTextTo = grabBodyOrTextPart(animation.bodyAnimation);
  144. if (!animation.titleSame.isNull()) {
  145. animation.titleTo = grabTitlePart(sameLength);
  146. }
  147. if (was) {
  148. _animation = std::move(was);
  149. std::swap(*_animation, animation);
  150. _animation->imageShown = std::move(animation.imageShown);
  151. _animation->barScroll = std::move(animation.barScroll);
  152. _animation->barTop = std::move(animation.barTop);
  153. } else {
  154. _animation = std::make_unique<Animation>(std::move(animation));
  155. }
  156. if (hasImageChanged) {
  157. _animation->imageShown.start(
  158. [=] { _widget.update(); },
  159. _image.isNull() ? 1. : 0.,
  160. _image.isNull() ? 0. : 1.,
  161. _st.duration);
  162. }
  163. if (bodyChanged) {
  164. _animation->bodyMoved.start(
  165. [=] { _widget.update(); },
  166. 0.,
  167. 1.,
  168. _st.duration);
  169. }
  170. if (barCountChanged) {
  171. _animation->barScroll.stop();
  172. _animation->barTop.stop();
  173. } else if (barFrom != barTo) {
  174. const auto wasState = countBarState(barFrom);
  175. const auto nowState = countBarState(barTo);
  176. _animation->barScroll.start(
  177. [=] { _widget.update(); },
  178. wasState.scroll,
  179. nowState.scroll,
  180. _st.duration);
  181. _animation->barTop.start(
  182. [] {},
  183. wasState.offset,
  184. nowState.offset,
  185. _st.duration);
  186. }
  187. }
  188. void MessageBar::updateFromContent(MessageBarContent &&content) {
  189. _content = std::move(content);
  190. _title.setText(_st.title, _content.title);
  191. _text.setMarkedText(
  192. _st.text,
  193. _content.text,
  194. Ui::DialogTextOptions(),
  195. _content.context);
  196. _image = prepareImage(_content.preview);
  197. if (!_content.spoilerRepaint) {
  198. _spoiler = nullptr;
  199. } else if (!_spoiler) {
  200. _spoiler = std::make_unique<SpoilerAnimation>(
  201. _content.spoilerRepaint);
  202. }
  203. }
  204. QRect MessageBar::imageRect() const {
  205. const auto left = st::msgReplyBarSkip + st::msgReplyBarSkip;
  206. const auto top = (st::historyReplyHeight - st::historyReplyPreview) / 2;
  207. const auto size = st::historyReplyPreview;
  208. return QRect(left, top, size, size);
  209. }
  210. QRect MessageBar::titleRangeRect(int from, int till) const {
  211. auto result = bodyRect();
  212. result.setHeight(st::msgServiceNameFont->height);
  213. const auto left = from
  214. ? st::msgServiceNameFont->width(_content.title.mid(0, from))
  215. : 0;
  216. const auto right = (till <= _content.title.size())
  217. ? st::msgServiceNameFont->width(_content.title.mid(0, till))
  218. : result.width();
  219. result.setLeft(result.left() + left);
  220. result.setWidth(right - left);
  221. return result;
  222. }
  223. QRect MessageBar::bodyRect(bool withImage) const {
  224. const auto innerLeft = st::msgReplyBarSkip + st::msgReplyBarSkip;
  225. const auto imageSkip = st::historyReplyPreview + st::msgReplyBarSkip;
  226. const auto left = innerLeft + (withImage ? imageSkip : 0);
  227. const auto top = st::msgReplyPadding.top();
  228. const auto width = _widget.width() - left - st::msgReplyPadding.right();
  229. const auto height = (st::historyReplyHeight - 2 * top);
  230. return QRect(left, top, width, height) - _content.margins;
  231. }
  232. QRect MessageBar::bodyRect() const {
  233. return bodyRect(!_image.isNull());
  234. }
  235. QRect MessageBar::textRect() const {
  236. auto result = bodyRect();
  237. result.setTop(result.top() + st::msgServiceNameFont->height);
  238. return result;
  239. }
  240. auto MessageBar::makeGrabGuard() {
  241. auto imageShown = _animation
  242. ? std::move(_animation->imageShown)
  243. : Ui::Animations::Simple();
  244. auto spoiler = std::move(_spoiler);
  245. auto fromSpoiler = _animation
  246. ? std::move(_animation->spoilerFrom)
  247. : nullptr;
  248. return gsl::finally([
  249. &,
  250. shown = std::move(imageShown),
  251. spoiler = std::move(spoiler),
  252. fromSpoiler = std::move(fromSpoiler)
  253. ]() mutable {
  254. if (_animation) {
  255. _animation->imageShown = std::move(shown);
  256. _animation->spoilerFrom = std::move(fromSpoiler);
  257. }
  258. _spoiler = std::move(spoiler);
  259. });
  260. }
  261. QPixmap MessageBar::grabBodyOrTextPart(BodyAnimation type) {
  262. return (type == BodyAnimation::Full)
  263. ? grabBodyPart()
  264. : (type == BodyAnimation::Text)
  265. ? grabTextPart()
  266. : QPixmap();
  267. }
  268. QPixmap MessageBar::grabTitleBase(int till) {
  269. return grabTitleRange(0, till);
  270. }
  271. QPixmap MessageBar::grabTitlePart(int from) {
  272. Expects(from <= _content.title.size());
  273. return grabTitleRange(from, _content.title.size());
  274. }
  275. QPixmap MessageBar::grabTitleRange(int from, int till) {
  276. const auto guard = makeGrabGuard();
  277. return GrabWidget(widget(), titleRangeRect(from, till));
  278. }
  279. QPixmap MessageBar::grabBodyPart() {
  280. const auto guard = makeGrabGuard();
  281. return GrabWidget(widget(), bodyRect());
  282. }
  283. QPixmap MessageBar::grabTextPart() {
  284. const auto guard = makeGrabGuard();
  285. return GrabWidget(widget(), textRect());
  286. }
  287. QPixmap MessageBar::grabImagePart() {
  288. if (!_animation) {
  289. return _image;
  290. }
  291. const auto guard = makeGrabGuard();
  292. return (_animation->bodyMoved.animating()
  293. && !_animation->imageFrom.isNull()
  294. && !_animation->imageTo.isNull())
  295. ? GrabWidget(widget(), imageRect())
  296. : _animation->imageFrom;
  297. }
  298. void MessageBar::finishAnimating() {
  299. if (_animation) {
  300. _animation = nullptr;
  301. _widget.update();
  302. }
  303. }
  304. QPixmap MessageBar::prepareImage(const QImage &preview) {
  305. return QPixmap::fromImage(preview, Qt::ColorOnly);
  306. }
  307. void MessageBar::paint(Painter &p) {
  308. const auto progress = _animation ? _animation->bodyMoved.value(1.) : 1.;
  309. const auto imageFinal = _image.isNull() ? 0. : 1.;
  310. const auto imageShown = _animation
  311. ? _animation->imageShown.value(imageFinal)
  312. : imageFinal;
  313. if (progress == 1. && imageShown == imageFinal && _animation) {
  314. _animation = nullptr;
  315. }
  316. const auto body = [&] {
  317. if (!_animation || !_animation->imageShown.animating()) {
  318. return bodyRect();
  319. }
  320. const auto noImage = bodyRect(false);
  321. const auto withImage = bodyRect(true);
  322. return QRect(
  323. anim::interpolate(noImage.x(), withImage.x(), imageShown),
  324. noImage.y(),
  325. anim::interpolate(noImage.width(), withImage.width(), imageShown),
  326. noImage.height());
  327. }();
  328. const auto text = textRect();
  329. const auto image = imageRect();
  330. const auto width = _widget.width();
  331. const auto noShift = !_animation
  332. || (_animation->movingTo == RectPart::None);
  333. const auto shiftFull = st::msgReplyBarSkip;
  334. const auto shiftTo = noShift
  335. ? 0
  336. : (_animation->movingTo == RectPart::Top)
  337. ? anim::interpolate(shiftFull, 0, progress)
  338. : anim::interpolate(-shiftFull, 0, progress);
  339. const auto shiftFrom = noShift
  340. ? 0
  341. : (_animation->movingTo == RectPart::Top)
  342. ? (shiftTo - shiftFull)
  343. : (shiftTo + shiftFull);
  344. const auto now = crl::now();
  345. const auto paused = p.inactive();
  346. const auto pausedSpoiler = paused || On(PowerSaving::kChatSpoiler);
  347. paintLeftBar(p);
  348. if (!_animation) {
  349. if (!_image.isNull()) {
  350. paintImageWithSpoiler(
  351. p,
  352. image,
  353. _image,
  354. _spoiler.get(),
  355. now,
  356. pausedSpoiler);
  357. }
  358. } else if (!_animation->imageTo.isNull()
  359. || (!_animation->imageFrom.isNull()
  360. && _animation->imageShown.animating())) {
  361. const auto rect = [&] {
  362. if (!_animation->imageShown.animating()) {
  363. return image;
  364. }
  365. const auto size = anim::interpolate(0, image.width(), imageShown);
  366. return QRect(
  367. image.x(),
  368. image.y() + (image.height() - size) / 2,
  369. size,
  370. size);
  371. }();
  372. if (_animation->bodyMoved.animating()) {
  373. p.setOpacity(1. - progress);
  374. paintImageWithSpoiler(
  375. p,
  376. rect.translated(0, shiftFrom),
  377. _animation->imageFrom,
  378. _animation->spoilerFrom.get(),
  379. now,
  380. pausedSpoiler);
  381. p.setOpacity(progress);
  382. paintImageWithSpoiler(
  383. p,
  384. rect.translated(0, shiftTo),
  385. _animation->imageTo,
  386. _spoiler.get(),
  387. now,
  388. pausedSpoiler);
  389. p.setOpacity(1.);
  390. } else {
  391. paintImageWithSpoiler(
  392. p,
  393. rect,
  394. _image,
  395. _spoiler.get(),
  396. now,
  397. pausedSpoiler);
  398. }
  399. }
  400. if (!_animation || _animation->bodyAnimation == BodyAnimation::None) {
  401. if (_title.isEmpty()) {
  402. // "Loading..." state.
  403. p.setPen(st::historyComposeAreaFgService);
  404. _text.draw(p, {
  405. .position = {
  406. body.x(),
  407. body.y() + (body.height() - st::normalFont->height) / 2,
  408. },
  409. .outerWidth = width,
  410. .availableWidth = body.width(),
  411. .elisionLines = 1,
  412. });
  413. } else {
  414. p.setPen(_st.textFg);
  415. _text.draw(p, {
  416. .position = { body.x(), text.y() },
  417. .outerWidth = width,
  418. .availableWidth = body.width(),
  419. .palette = &_st.textPalette,
  420. .spoiler = Ui::Text::DefaultSpoilerCache(),
  421. .now = now,
  422. .pausedEmoji = paused || On(PowerSaving::kEmojiChat),
  423. .pausedSpoiler = pausedSpoiler,
  424. .elisionLines = 1,
  425. });
  426. }
  427. } else if (_animation->bodyAnimation == BodyAnimation::Text) {
  428. p.setOpacity(1. - progress);
  429. p.drawPixmap(
  430. body.x(),
  431. text.y() + shiftFrom,
  432. _animation->bodyOrTextFrom);
  433. p.setOpacity(progress);
  434. p.drawPixmap(body.x(), text.y() + shiftTo, _animation->bodyOrTextTo);
  435. p.setOpacity(1.);
  436. }
  437. if (!_animation || _animation->bodyAnimation != BodyAnimation::Full) {
  438. if (_animation && !_animation->titleSame.isNull()) {
  439. const auto factor = style::DevicePixelRatio();
  440. p.drawPixmap(body.x(), body.y(), _animation->titleSame);
  441. p.setOpacity(1. - progress);
  442. p.drawPixmap(
  443. body.x() + (_animation->titleSame.width() / factor),
  444. body.y() + shiftFrom,
  445. _animation->titleFrom);
  446. p.setOpacity(progress);
  447. p.drawPixmap(
  448. body.x() + (_animation->titleSame.width() / factor),
  449. body.y() + shiftTo,
  450. _animation->titleTo);
  451. p.setOpacity(1.);
  452. } else {
  453. p.setPen(_st.titleFg);
  454. _title.drawLeftElided(p, body.x(), body.y(), body.width(), width);
  455. }
  456. } else {
  457. p.setOpacity(1. - progress);
  458. p.drawPixmap(
  459. body.x(),
  460. body.y() + shiftFrom,
  461. _animation->bodyOrTextFrom);
  462. p.setOpacity(progress);
  463. p.drawPixmap(body.x(), body.y() + shiftTo, _animation->bodyOrTextTo);
  464. p.setOpacity(1.);
  465. }
  466. }
  467. auto MessageBar::countBarState(int index) const -> BarState {
  468. Expects(index >= 0 && index < _content.count);
  469. auto result = BarState();
  470. const auto line = st::msgReplyBarSize.width();
  471. const auto height = st::msgReplyBarSize.height();
  472. const auto count = _content.count;
  473. const auto shownCount = std::min(count, 4);
  474. const auto dividers = (shownCount - 1) * line;
  475. const auto size = float64(st::msgReplyBarSize.height() - dividers)
  476. / shownCount;
  477. const auto fullHeight = count * size + (count - 1) * line;
  478. const auto topByIndex = [&](int index) {
  479. return index * (size + line);
  480. };
  481. result.scroll = (count < 5 || index < 2)
  482. ? 0
  483. : (index >= count - 2)
  484. ? (fullHeight - height)
  485. : (topByIndex(index) - (height - size) / 2);
  486. result.size = size;
  487. result.skip = line;
  488. result.offset = topByIndex(index);
  489. return result;
  490. }
  491. auto MessageBar::countBarState() const -> BarState {
  492. return countBarState(_content.index);
  493. }
  494. void MessageBar::ensureGradientsCreated(int size) {
  495. if (!_topBarGradient.isNull()) {
  496. return;
  497. }
  498. const auto rows = size * style::DevicePixelRatio() - 2;
  499. auto bottomMask = QImage(
  500. QSize(1, size) * style::DevicePixelRatio(),
  501. QImage::Format_ARGB32_Premultiplied);
  502. const auto step = ((1ULL << 24) - 1) / rows;
  503. const auto limit = step * rows;
  504. auto bits = bottomMask.bits();
  505. const auto perLine = bottomMask.bytesPerLine();
  506. for (auto counter = uint32(0); counter != limit; counter += step) {
  507. const auto value = (counter >> 16);
  508. memset(bits, int(value), perLine);
  509. bits += perLine;
  510. }
  511. memset(bits, 255, perLine * 2);
  512. auto bottom = style::colorizeImage(bottomMask, st::historyPinnedBg);
  513. bottom.setDevicePixelRatio(style::DevicePixelRatio());
  514. auto top = bottom.mirrored();
  515. _bottomBarGradient = Images::PixmapFast(std::move(bottom));
  516. _topBarGradient = Images::PixmapFast(std::move(top));
  517. }
  518. void MessageBar::paintImageWithSpoiler(
  519. QPainter &p,
  520. QRect rect,
  521. const QPixmap &image,
  522. SpoilerAnimation *spoiler,
  523. crl::time now,
  524. bool paused) const {
  525. p.drawPixmap(rect, image);
  526. if (spoiler) {
  527. const auto frame = DefaultImageSpoiler().frame(
  528. spoiler->index(now, paused));
  529. FillSpoilerRect(p, rect, frame);
  530. }
  531. }
  532. void MessageBar::paintLeftBar(Painter &p) {
  533. const auto state = countBarState();
  534. const auto gradientSize = int(std::ceil(state.size * 2.5));
  535. if (_content.count > 4) {
  536. ensureGradientsCreated(gradientSize);
  537. }
  538. const auto scroll = _animation
  539. ? _animation->barScroll.value(state.scroll)
  540. : state.scroll;
  541. const auto offset = _animation
  542. ? _animation->barTop.value(state.offset)
  543. : state.offset;
  544. const auto line = st::msgReplyBarSize.width();
  545. const auto height = st::msgReplyBarSize.height();
  546. const auto activeFrom = offset - scroll;
  547. const auto activeTill = activeFrom + state.size;
  548. const auto single = state.size + state.skip;
  549. const auto barSkip = st::msgReplyPadding.top() + st::msgReplyBarPos.y();
  550. const auto fullHeight = barSkip + height + barSkip;
  551. const auto bar = QRect(
  552. st::msgReplyBarSkip + st::msgReplyBarPos.x(),
  553. barSkip,
  554. line,
  555. state.size);
  556. const auto paintFromScroll = std::max(scroll - barSkip, 0.);
  557. const auto paintFrom = int(std::floor(paintFromScroll / single));
  558. const auto paintTillScroll = (scroll + height + barSkip);
  559. const auto paintTill = std::min(
  560. int(std::floor(paintTillScroll / single)) + 1,
  561. _content.count);
  562. p.setPen(Qt::NoPen);
  563. const auto activeBrush = QBrush(st::msgInReplyBarColor);
  564. const auto inactiveBrush = QBrush(QColor(
  565. st::msgInReplyBarColor->c.red(),
  566. st::msgInReplyBarColor->c.green(),
  567. st::msgInReplyBarColor->c.blue(),
  568. st::msgInReplyBarColor->c.alpha() / 3));
  569. const auto radius = line / 2.;
  570. auto hq = PainterHighQualityEnabler(p);
  571. p.setClipRect(bar.x(), 0, bar.width(), fullHeight);
  572. for (auto i = paintFrom; i != paintTill; ++i) {
  573. const auto top = i * single - scroll;
  574. const auto bottom = top + state.size;
  575. const auto active = (top == activeFrom);
  576. p.setBrush(active ? activeBrush : inactiveBrush);
  577. p.drawRoundedRect(bar.translated(0, top), radius, radius);
  578. if (active
  579. || bottom - line <= activeFrom
  580. || top + line >= activeTill) {
  581. continue;
  582. }
  583. const auto partFrom = std::max(top, activeFrom);
  584. const auto partTill = std::min(bottom, activeTill);
  585. p.setBrush(activeBrush);
  586. p.drawRoundedRect(
  587. QRect(bar.x(), bar.y() + partFrom, line, partTill - partFrom),
  588. radius,
  589. radius);
  590. }
  591. p.setClipping(false);
  592. if (_content.count > 4) {
  593. const auto firstScroll = countBarState(2).scroll;
  594. const auto gradientTop = (scroll >= firstScroll)
  595. ? 0
  596. : anim::interpolate(-gradientSize, 0, scroll / firstScroll);
  597. const auto lastScroll = countBarState(_content.count - 3).scroll;
  598. const auto largestScroll = countBarState(_content.count - 1).scroll;
  599. const auto gradientBottom = (scroll <= lastScroll)
  600. ? fullHeight
  601. : anim::interpolate(
  602. fullHeight,
  603. fullHeight + gradientSize,
  604. (scroll - lastScroll) / (largestScroll - lastScroll));
  605. if (gradientTop > -gradientSize) {
  606. p.drawPixmap(
  607. QRect(bar.x(), gradientTop, bar.width(), gradientSize),
  608. _topBarGradient);
  609. }
  610. if (gradientBottom < fullHeight + gradientSize) {
  611. p.drawPixmap(
  612. QRect(
  613. bar.x(),
  614. gradientBottom - gradientSize,
  615. bar.width(),
  616. gradientSize),
  617. _bottomBarGradient);
  618. }
  619. }
  620. }
  621. } // namespace Ui