side_bar_button.cpp 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. // This file is part of Desktop App Toolkit,
  2. // a set of libraries for developing nice desktop applications.
  3. //
  4. // For license and copyright information please follow this link:
  5. // https://github.com/desktop-app/legal/blob/master/LEGAL
  6. //
  7. #include "ui/widgets/side_bar_button.h"
  8. #include "ui/effects/ripple_animation.h"
  9. #include "ui/painter.h"
  10. #include "styles/style_widgets.h"
  11. #include <QtGui/QtEvents>
  12. namespace Ui {
  13. namespace {
  14. constexpr auto kMaxLabelLines = 3;
  15. constexpr auto kPremiumLockedOpacity = 0.6;
  16. } // namespace
  17. SideBarButton::SideBarButton(
  18. not_null<QWidget*> parent,
  19. const TextWithEntities &title,
  20. const style::SideBarButton &st,
  21. Text::MarkedContext context,
  22. Fn<bool()> paused)
  23. : RippleButton(parent, st.ripple)
  24. , _st(st)
  25. , _text(_st.minTextWidth)
  26. , _paused(paused)
  27. , _context(std::move(context)) {
  28. _context.repaint = [this] { update(); };
  29. _text.setMarkedText(
  30. _st.style,
  31. title,
  32. kMarkupTextOptions,
  33. _context);
  34. setAttribute(Qt::WA_OpaquePaintEvent);
  35. style::PaletteChanged(
  36. ) | rpl::start_with_next([this] {
  37. _iconCache = _iconCacheActive = QImage();
  38. _lock.iconCache = _lock.iconCacheActive = QImage();
  39. update();
  40. }, lifetime());
  41. }
  42. void SideBarButton::setActive(bool active) {
  43. if (_active == active) {
  44. return;
  45. }
  46. _active = active;
  47. update();
  48. }
  49. void SideBarButton::setBadge(const QString &badge, bool muted) {
  50. if (_badge.toString() == badge && _badgeMuted == muted) {
  51. return;
  52. }
  53. _badge.setText(_st.badgeStyle, badge);
  54. _badgeMuted = muted;
  55. const auto width = badge.isEmpty()
  56. ? 0
  57. : std::max(_st.badgeHeight, _badge.maxWidth() + 2 * _st.badgeSkip);
  58. if (_iconCacheBadgeWidth != width) {
  59. _iconCacheBadgeWidth = width;
  60. _iconCache = _iconCacheActive = QImage();
  61. }
  62. update();
  63. }
  64. void SideBarButton::setIconOverride(
  65. const style::icon *iconOverride,
  66. const style::icon *iconOverrideActive) {
  67. _iconOverride = iconOverride;
  68. _iconOverrideActive = iconOverrideActive;
  69. update();
  70. }
  71. void SideBarButton::setLocked(bool locked) {
  72. if (_lock.locked == locked) {
  73. return;
  74. }
  75. _lock.locked = locked;
  76. const auto charFiller = QChar('l');
  77. const auto count = std::ceil(st::sideBarButtonLockSize.width()
  78. / float(_st.style.font->width(charFiller)));
  79. const auto filler = QString().fill(charFiller, count);
  80. auto result = TextWithEntities();
  81. if (_lock.locked) {
  82. result.append(filler);
  83. }
  84. const auto len = _text.length();
  85. result.append(_text.toTextWithEntities({
  86. ushort(_lock.locked ? 0 : count),
  87. ushort(len),
  88. }));
  89. _text.setMarkedText(_st.style, result, kMarkupTextOptions, _context);
  90. update();
  91. }
  92. bool SideBarButton::locked() const {
  93. return _lock.locked;
  94. }
  95. int SideBarButton::resizeGetHeight(int newWidth) {
  96. auto result = _st.minHeight;
  97. const auto text = std::min(
  98. _text.countHeight(newWidth - _st.textSkip * 2),
  99. _st.style.font->height * kMaxLabelLines);
  100. const auto add = text - _st.style.font->height;
  101. return result + std::max(add, 0);
  102. }
  103. void SideBarButton::paintEvent(QPaintEvent *e) {
  104. auto p = Painter(this);
  105. const auto clip = e->rect();
  106. const auto &bg = _active ? _st.textBgActive : _st.textBg;
  107. p.fillRect(clip, bg);
  108. RippleButton::paintRipple(p, 0, 0);
  109. if (_lock.locked) {
  110. p.setOpacity(kPremiumLockedOpacity);
  111. }
  112. const auto &icon = computeIcon();
  113. const auto x = (_st.iconPosition.x() < 0)
  114. ? (width() - icon.width()) / 2
  115. : _st.iconPosition.x();
  116. const auto y = (_st.iconPosition.y() < 0)
  117. ? (height() - icon.height()) / 2
  118. : _st.iconPosition.y();
  119. if (_iconCacheBadgeWidth) {
  120. validateIconCache();
  121. p.drawImage(x, y, _active ? _iconCacheActive : _iconCache);
  122. } else {
  123. icon.paint(p, x, y, width());
  124. }
  125. p.setPen(_active ? _st.textFgActive : _st.textFg);
  126. _text.draw(p, {
  127. .position = { _st.textSkip, _st.textTop },
  128. .availableWidth = (width() - 2 * _st.textSkip),
  129. .align = style::al_top,
  130. .pausedEmoji = _paused && _paused(),
  131. .elisionLines = kMaxLabelLines,
  132. });
  133. if (_iconCacheBadgeWidth) {
  134. const auto desiredLeft = width() / 2 + _st.badgePosition.x();
  135. const auto x = std::min(
  136. desiredLeft,
  137. width() - _iconCacheBadgeWidth - st::defaultScrollArea.width);
  138. const auto y = _st.badgePosition.y();
  139. auto hq = PainterHighQualityEnabler(p);
  140. p.setPen(Qt::NoPen);
  141. p.setBrush((_badgeMuted && !_active)
  142. ? _st.badgeBgMuted
  143. : _st.badgeBg);
  144. const auto r = _st.badgeHeight / 2;
  145. p.drawRoundedRect(x, y, _iconCacheBadgeWidth, _st.badgeHeight, r, r);
  146. p.setPen(_st.badgeFg);
  147. _badge.draw(
  148. p,
  149. x + (_iconCacheBadgeWidth - _badge.maxWidth()) / 2,
  150. y + (_st.badgeHeight - _st.badgeStyle.font->height) / 2,
  151. width());
  152. }
  153. if (_lock.locked) {
  154. const auto lineWidths = _text.countLineWidths(
  155. width() - 2 * _st.textSkip,
  156. { .reserve = kMaxLabelLines });
  157. if (lineWidths.empty()) {
  158. return;
  159. }
  160. validateLockIconCache();
  161. const auto &icon = _active ? _lock.iconCacheActive : _lock.iconCache;
  162. const auto size = icon.size() / style::DevicePixelRatio();
  163. p.translate(
  164. (width() - lineWidths.front()) / 2.,
  165. _st.textTop + (_st.style.font->height - size.height()) / 2.);
  166. p.setOpacity(1.);
  167. p.fillRect(QRect(QPoint(), size), bg);
  168. p.setOpacity(kPremiumLockedOpacity);
  169. p.translate(-_st.style.font->spacew / 2., 0);
  170. p.drawImage(0, 0, icon);
  171. }
  172. }
  173. const style::icon &SideBarButton::computeIcon() const {
  174. return _active
  175. ? (_iconOverrideActive
  176. ? *_iconOverrideActive
  177. : !_st.iconActive.empty()
  178. ? _st.iconActive
  179. : _iconOverride
  180. ? *_iconOverride
  181. : _st.icon)
  182. : _iconOverride
  183. ? *_iconOverride
  184. : _st.icon;
  185. }
  186. void SideBarButton::validateIconCache() {
  187. Expects(_st.iconPosition.x() < 0);
  188. if (!(_active ? _iconCacheActive : _iconCache).isNull()) {
  189. return;
  190. }
  191. const auto &icon = computeIcon();
  192. auto image = QImage(
  193. icon.size() * style::DevicePixelRatio(),
  194. QImage::Format_ARGB32_Premultiplied);
  195. image.setDevicePixelRatio(style::DevicePixelRatio());
  196. image.fill(Qt::transparent);
  197. {
  198. auto p = QPainter(&image);
  199. icon.paint(p, 0, 0, icon.width());
  200. p.setCompositionMode(QPainter::CompositionMode_Source);
  201. p.setBrush(Qt::transparent);
  202. auto pen = QPen(Qt::transparent);
  203. pen.setWidth(2 * _st.badgeStroke);
  204. p.setPen(pen);
  205. auto hq = PainterHighQualityEnabler(p);
  206. const auto desiredLeft = (icon.width() / 2) + _st.badgePosition.x();
  207. const auto x = std::min(
  208. desiredLeft,
  209. (width()
  210. - _iconCacheBadgeWidth
  211. - st::defaultScrollArea.width
  212. - (width() / 2)
  213. + (icon.width() / 2)));
  214. const auto top = (_st.iconPosition.y() >= 0)
  215. ? _st.iconPosition.y()
  216. : (height() - icon.height()) / 2;
  217. const auto y = _st.badgePosition.y() - top;
  218. const auto r = _st.badgeHeight / 2.;
  219. p.drawRoundedRect(x, y, _iconCacheBadgeWidth, _st.badgeHeight, r, r);
  220. }
  221. (_active ? _iconCacheActive : _iconCache) = std::move(image);
  222. }
  223. void SideBarButton::validateLockIconCache() {
  224. if (!(_active ? _lock.iconCacheActive : _lock.iconCache).isNull()) {
  225. return;
  226. }
  227. (_active ? _lock.iconCacheActive : _lock.iconCache)
  228. = SideBarLockIcon(_st.textFg);
  229. }
  230. QImage SideBarLockIcon(const style::color &fg) {
  231. const auto &size = st::sideBarButtonLockSize;
  232. const auto arcPen = QPen(
  233. fg,
  234. // Use a divider to get 1.5.
  235. st::sideBarButtonLockPenWidth
  236. / float64(st::sideBarButtonLockPenWidthDivider),
  237. Qt::SolidLine,
  238. Qt::SquareCap,
  239. Qt::RoundJoin);
  240. auto image = QImage(
  241. size * style::DevicePixelRatio(),
  242. QImage::Format_ARGB32_Premultiplied);
  243. image.setDevicePixelRatio(style::DevicePixelRatio());
  244. image.fill(Qt::transparent);
  245. {
  246. auto p = QPainter(&image);
  247. auto hq = PainterHighQualityEnabler(p);
  248. const auto &arcOffset = st::sideBarButtonLockArcOffset;
  249. const auto arcWidth = size.width() - arcOffset * 2;
  250. const auto &arcHeight = st::sideBarButtonLockArcHeight;
  251. const auto blockRectWidth = size.width();
  252. const auto blockRectHeight = st::sideBarButtonLockBlockHeight;
  253. const auto blockRectTop = size.height() - blockRectHeight;
  254. const auto blockRect = QRectF(
  255. (size.width() - blockRectWidth) / 2,
  256. blockRectTop,
  257. blockRectWidth,
  258. blockRectHeight);
  259. const auto lineHeight = -(blockRect.y() - arcHeight)
  260. + arcPen.width() / 2.;
  261. p.setPen(Qt::NoPen);
  262. p.setBrush(fg);
  263. {
  264. p.drawRoundedRect(blockRect, 2, 2);
  265. }
  266. p.translate(size.width() - arcOffset, blockRect.y());
  267. p.setPen(arcPen);
  268. const auto rLine = QLineF(0, 0, 0, lineHeight);
  269. const auto lLine = rLine.translated(-arcWidth, 0);
  270. p.drawLine(rLine);
  271. p.drawLine(lLine);
  272. p.drawArc(
  273. -arcWidth,
  274. -arcHeight - arcPen.width() / 2.,
  275. arcWidth,
  276. arcHeight * 2,
  277. 0,
  278. 180 * 16);
  279. }
  280. return image;
  281. }
  282. } // namespace Ui