buttons.cpp 25 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012
  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/buttons.h"
  8. #include "ui/effects/ripple_animation.h"
  9. #include "ui/effects/cross_animation.h"
  10. #include "ui/effects/numbers_animation.h"
  11. #include "ui/image/image_prepare.h"
  12. #include "ui/text/text_utilities.h"
  13. #include "ui/widgets/checkbox.h"
  14. #include "ui/painter.h"
  15. #include "ui/qt_object_factory.h"
  16. #include <QtGui/QtEvents>
  17. namespace Ui {
  18. namespace {
  19. class SimpleRippleButton : public RippleButton {
  20. public:
  21. using RippleButton::RippleButton;
  22. protected:
  23. QPoint prepareRippleStartPosition() const override final {
  24. const auto result = mapFromGlobal(QCursor::pos());
  25. return rect().contains(result)
  26. ? result
  27. : DisabledRippleStartPosition();
  28. }
  29. };
  30. class SimpleCircleButton final : public SimpleRippleButton {
  31. public:
  32. using SimpleRippleButton::SimpleRippleButton;
  33. protected:
  34. QImage prepareRippleMask() const override final {
  35. return RippleAnimation::EllipseMask(size());
  36. }
  37. };
  38. class SimpleRoundButton final : public SimpleRippleButton {
  39. public:
  40. using SimpleRippleButton::SimpleRippleButton;
  41. protected:
  42. QImage prepareRippleMask() const override final {
  43. return RippleAnimation::RoundRectMask(size(), st::buttonRadius);
  44. }
  45. };
  46. } // namespace
  47. LinkButton::LinkButton(
  48. QWidget *parent,
  49. const QString &text,
  50. const style::LinkButton &st)
  51. : AbstractButton(parent)
  52. , _st(st)
  53. , _text(text)
  54. , _textWidth(st.font->width(_text)) {
  55. resizeToText();
  56. setCursor(style::cur_pointer);
  57. }
  58. int LinkButton::naturalWidth() const {
  59. return _st.padding.left() + _textWidth + _st.padding.right();
  60. }
  61. void LinkButton::paintEvent(QPaintEvent *e) {
  62. Painter p(this);
  63. const auto &font = (isOver() ? _st.overFont : _st.font);
  64. const auto pen = _textFgOverride.has_value()
  65. ? QPen(*_textFgOverride)
  66. : isOver()
  67. ? _st.overColor
  68. : _st.color;
  69. p.setFont(font);
  70. p.setPen(pen);
  71. const auto left = _st.padding.left();
  72. const auto top = _st.padding.top() + font->ascent;
  73. if (width() < naturalWidth()) {
  74. const auto available = width() - left - _st.padding.right();
  75. p.drawText(left, top, font->elided(_text, available));
  76. } else {
  77. p.drawText(left, top, _text);
  78. }
  79. }
  80. void LinkButton::setText(const QString &text) {
  81. _text = text;
  82. _textWidth = _st.font->width(_text);
  83. resizeToText();
  84. update();
  85. }
  86. void LinkButton::resizeToText() {
  87. resize(
  88. naturalWidth(),
  89. _st.padding.top() + _st.font->height + _st.padding.bottom());
  90. }
  91. void LinkButton::setColorOverride(std::optional<QColor> textFg) {
  92. _textFgOverride = textFg;
  93. update();
  94. }
  95. void LinkButton::onStateChanged(State was, StateChangeSource source) {
  96. update();
  97. }
  98. RippleButton::RippleButton(QWidget *parent, const style::RippleAnimation &st)
  99. : AbstractButton(parent)
  100. , _st(st) {
  101. }
  102. void RippleButton::clearState() {
  103. AbstractButton::clearState();
  104. finishAnimating();
  105. }
  106. void RippleButton::finishAnimating() {
  107. if (_ripple) {
  108. _ripple.reset();
  109. update();
  110. }
  111. }
  112. void RippleButton::setForceRippled(
  113. bool rippled,
  114. anim::type animated) {
  115. if (_forceRippled != rippled) {
  116. _forceRippled = rippled;
  117. if (_forceRippled) {
  118. _forceRippledSubscription = style::PaletteChanged(
  119. ) | rpl::filter([=] {
  120. return _ripple != nullptr;
  121. }) | rpl::start_with_next([=] {
  122. _ripple->forceRepaint();
  123. });
  124. ensureRipple();
  125. if (_ripple->empty()) {
  126. _ripple->addFading();
  127. } else {
  128. _ripple->lastUnstop();
  129. }
  130. } else {
  131. if (_ripple) {
  132. _ripple->lastStop();
  133. }
  134. _forceRippledSubscription.destroy();
  135. }
  136. }
  137. if (animated == anim::type::instant && _ripple) {
  138. _ripple->lastFinish();
  139. }
  140. update();
  141. }
  142. void RippleButton::paintRipple(
  143. QPainter &p,
  144. const QPoint &point,
  145. const QColor *colorOverride) {
  146. paintRipple(p, point.x(), point.y(), colorOverride);
  147. }
  148. void RippleButton::paintRipple(QPainter &p, int x, int y, const QColor *colorOverride) {
  149. if (_ripple) {
  150. _ripple->paint(p, x, y, width(), colorOverride);
  151. if (_ripple->empty()) {
  152. _ripple.reset();
  153. }
  154. }
  155. }
  156. void RippleButton::onStateChanged(State was, StateChangeSource source) {
  157. update();
  158. auto wasDown = static_cast<bool>(was & StateFlag::Down);
  159. auto down = isDown();
  160. if (!_st.showDuration || down == wasDown || _forceRippled) {
  161. return;
  162. }
  163. if (down && (source == StateChangeSource::ByPress)) {
  164. // Start a ripple only from mouse press.
  165. auto position = prepareRippleStartPosition();
  166. if (position != DisabledRippleStartPosition()) {
  167. ensureRipple();
  168. _ripple->add(position);
  169. }
  170. } else if (!down && _ripple) {
  171. // Finish ripple anyway.
  172. _ripple->lastStop();
  173. }
  174. }
  175. void RippleButton::ensureRipple() {
  176. if (!_ripple) {
  177. _ripple = std::make_unique<RippleAnimation>(_st, prepareRippleMask(), [this] { update(); });
  178. }
  179. }
  180. QImage RippleButton::prepareRippleMask() const {
  181. return RippleAnimation::RectMask(size());
  182. }
  183. QPoint RippleButton::prepareRippleStartPosition() const {
  184. return mapFromGlobal(QCursor::pos());
  185. }
  186. RippleButton::~RippleButton() = default;
  187. FlatButton::FlatButton(
  188. QWidget *parent,
  189. const QString &text,
  190. const style::FlatButton &st)
  191. : RippleButton(parent, st.ripple)
  192. , _text(text)
  193. , _st(st) {
  194. if (_st.width < 0) {
  195. _width = textWidth() - _st.width;
  196. } else if (!_st.width) {
  197. _width = textWidth() + _st.height - _st.font->height;
  198. } else {
  199. _width = _st.width;
  200. }
  201. resize(_width, _st.height);
  202. }
  203. void FlatButton::setText(const QString &text) {
  204. _text = text;
  205. update();
  206. }
  207. void FlatButton::setWidth(int w) {
  208. _width = w;
  209. if (_width < 0) {
  210. _width = textWidth() - _st.width;
  211. } else if (!_width) {
  212. _width = textWidth() + _st.height - _st.font->height;
  213. }
  214. resize(_width, height());
  215. }
  216. void FlatButton::setColorOverride(std::optional<QColor> color) {
  217. _colorOverride = color;
  218. update();
  219. }
  220. int32 FlatButton::textWidth() const {
  221. return _st.font->width(_text);
  222. }
  223. void FlatButton::onStateChanged(State was, StateChangeSource source) {
  224. RippleButton::onStateChanged(was, source);
  225. update();
  226. }
  227. void FlatButton::paintEvent(QPaintEvent *e) {
  228. QPainter p(this);
  229. const auto inner = QRect(0, height() - _st.height, width(), _st.height);
  230. p.fillRect(inner, isOver() ? _st.overBgColor : _st.bgColor);
  231. paintRipple(p, 0, 0);
  232. p.setFont(isOver() ? _st.overFont : _st.font);
  233. p.setRenderHint(QPainter::TextAntialiasing);
  234. if (_colorOverride) {
  235. p.setPen(*_colorOverride);
  236. } else {
  237. p.setPen(isOver() ? _st.overColor : _st.color);
  238. }
  239. const auto textRect = inner.marginsRemoved(
  240. _textMargins
  241. ).marginsRemoved(
  242. { 0, _st.textTop, 0, 0 }
  243. );
  244. p.drawText(textRect, _text, style::al_top);
  245. }
  246. void FlatButton::setTextMargins(QMargins margins) {
  247. _textMargins = margins;
  248. update();
  249. }
  250. RoundButton::RoundButton(
  251. QWidget *parent,
  252. rpl::producer<QString> text,
  253. const style::RoundButton &st)
  254. : RippleButton(parent, st.ripple)
  255. , _textFull(std::move(text) | rpl::map(Text::WithEntities))
  256. , _st(st)
  257. , _roundRect(st.radius ? st.radius : st::buttonRadius, _st.textBg)
  258. , _roundRectOver(st.radius ? st.radius : st::buttonRadius, _st.textBgOver) {
  259. _textFull.value(
  260. ) | rpl::start_with_next([=](const TextWithEntities &text) {
  261. resizeToText(text);
  262. }, lifetime());
  263. }
  264. void RoundButton::setTextTransform(TextTransform transform) {
  265. _transform = transform;
  266. resizeToText(_textFull.current());
  267. }
  268. void RoundButton::setText(rpl::producer<QString> text) {
  269. _textFull = std::move(text) | rpl::map(Text::WithEntities);
  270. }
  271. void RoundButton::setText(rpl::producer<TextWithEntities> text) {
  272. _textFull = std::move(text);
  273. }
  274. void RoundButton::setNumbersText(const QString &numbersText, int numbers) {
  275. if (numbersText.isEmpty()) {
  276. _numbers.reset();
  277. } else {
  278. if (!_numbers) {
  279. const auto &font = _st.style.font;
  280. _numbers = std::make_unique<NumbersAnimation>(font, [this] {
  281. numbersAnimationCallback();
  282. });
  283. }
  284. _numbers->setText(numbersText, numbers);
  285. }
  286. resizeToText(_textFull.current());
  287. }
  288. void RoundButton::setWidthChangedCallback(Fn<void()> callback) {
  289. if (!_numbers) {
  290. const auto &font = _st.style.font;
  291. _numbers = std::make_unique<NumbersAnimation>(font, [this] {
  292. numbersAnimationCallback();
  293. });
  294. }
  295. _numbers->setWidthChangedCallback(std::move(callback));
  296. }
  297. void RoundButton::setBrushOverride(std::optional<QBrush> brush) {
  298. _brushOverride = std::move(brush);
  299. update();
  300. }
  301. void RoundButton::setPenOverride(std::optional<QPen> pen) {
  302. _penOverride = std::move(pen);
  303. update();
  304. }
  305. void RoundButton::finishNumbersAnimation() {
  306. if (_numbers) {
  307. _numbers->finishAnimating();
  308. }
  309. }
  310. void RoundButton::numbersAnimationCallback() {
  311. resizeToText(_textFull.current());
  312. }
  313. void RoundButton::setFullWidth(int newFullWidth) {
  314. _fullWidthOverride = newFullWidth;
  315. resizeToText(_textFull.current());
  316. }
  317. void RoundButton::setFullRadius(bool enabled) {
  318. _fullRadius = enabled;
  319. update();
  320. }
  321. void RoundButton::resizeToText(const TextWithEntities &text) {
  322. if (_transform == TextTransform::ToUpper) {
  323. _text.setMarkedText(
  324. _st.style,
  325. { text.text.toUpper(), text.entities },
  326. kMarkupTextOptions);
  327. } else {
  328. _text.setMarkedText(_st.style, text, kMarkupTextOptions);
  329. }
  330. int innerWidth = _text.maxWidth() + addedWidth();
  331. if (_fullWidthOverride > 0) {
  332. const auto padding = _fullRadius
  333. ? (_st.padding.left() + _st.padding.right())
  334. : 0;
  335. resize(
  336. _fullWidthOverride + padding,
  337. _st.height + _st.padding.top() + _st.padding.bottom());
  338. } else if (_fullWidthOverride < 0) {
  339. resize(
  340. innerWidth - _fullWidthOverride,
  341. _st.height + _st.padding.top() + _st.padding.bottom());
  342. } else if (_st.width <= 0) {
  343. resize(
  344. innerWidth - _st.width + _st.padding.left() + _st.padding.right(),
  345. _st.height + _st.padding.top() + _st.padding.bottom());
  346. } else {
  347. resize(
  348. _st.width + _st.padding.left() + _st.padding.right(),
  349. _st.height + _st.padding.top() + _st.padding.bottom());
  350. }
  351. update();
  352. }
  353. int RoundButton::addedWidth() const {
  354. auto result = 0;
  355. if (_numbers) {
  356. result += (result ? _st.numbersSkip : 0) + _numbers->countWidth();
  357. }
  358. if (!_st.icon.empty() && _st.iconPosition.x() < 0) {
  359. result += _st.icon.width() - _st.iconPosition.x();
  360. }
  361. return result;
  362. }
  363. int RoundButton::contentWidth() const {
  364. auto result = _text.maxWidth() + addedWidth();
  365. if (_fullWidthOverride < 0) {
  366. return result;
  367. } else if (_fullWidthOverride > 0) {
  368. const auto padding = _fullRadius
  369. ? (_st.padding.left() + _st.padding.right())
  370. : 0;
  371. const auto delta = _st.height - _st.style.font->height;
  372. if (_fullWidthOverride < result + delta) {
  373. return std::max(_fullWidthOverride - delta - padding, 1);
  374. }
  375. }
  376. return std::min(
  377. result,
  378. width() - _st.padding.left() - _st.padding.right());
  379. }
  380. void RoundButton::paintEvent(QPaintEvent *e) {
  381. Painter p(this);
  382. auto innerWidth = contentWidth();
  383. auto rounded = rect().marginsRemoved(_st.padding);
  384. if (_fullWidthOverride < 0) {
  385. rounded = QRect(0, rounded.top(), innerWidth - _fullWidthOverride, rounded.height());
  386. }
  387. const auto drawRect = [&](const RoundRect &rect) {
  388. const auto fill = myrtlrect(rounded);
  389. if (_fullRadius) {
  390. const auto radius = rounded.height() / 2;
  391. PainterHighQualityEnabler hq(p);
  392. p.setPen(_penOverride ? *_penOverride : Qt::NoPen);
  393. p.setBrush(_brushOverride ? *_brushOverride : rect.color()->b);
  394. p.drawRoundedRect(fill, radius, radius);
  395. } else if (_brushOverride) {
  396. PainterHighQualityEnabler hq(p);
  397. p.setPen(_penOverride ? *_penOverride : Qt::NoPen);
  398. p.setBrush(*_brushOverride);
  399. const auto radius = _st.radius ? _st.radius : st::buttonRadius;
  400. p.drawRoundedRect(fill, radius, radius);
  401. } else {
  402. rect.paint(p, fill);
  403. }
  404. };
  405. if (_penOverride) {
  406. paintRipple(p, rounded.topLeft());
  407. }
  408. drawRect(_roundRect);
  409. auto over = isOver();
  410. auto down = isDown();
  411. if (!_brushOverride && (over || down)) {
  412. drawRect(_roundRectOver);
  413. }
  414. if (!_penOverride) {
  415. paintRipple(p, rounded.topLeft());
  416. }
  417. const auto textTop = _st.padding.top() + _st.textTop;
  418. auto textLeft = _st.padding.left()
  419. + ((width()
  420. - innerWidth
  421. - _st.padding.left()
  422. - _st.padding.right()) / 2);
  423. if (_fullWidthOverride < 0) {
  424. textLeft = -_fullWidthOverride / 2;
  425. }
  426. if (!_st.icon.empty() && _st.iconPosition.x() < 0) {
  427. textLeft += _st.icon.width() - _st.iconPosition.x();
  428. }
  429. const auto iconLeft = (_st.iconPosition.x() >= 0)
  430. ? _st.iconPosition.x()
  431. : (textLeft + _st.iconPosition.x() - _st.icon.width());
  432. const auto iconTop = (_st.iconPosition.y() >= 0)
  433. ? _st.iconPosition.y()
  434. : (textTop + _st.iconPosition.y());
  435. const auto widthForText = std::max(innerWidth - addedWidth(), 0);
  436. if (!_text.isEmpty()) {
  437. p.setPen((over || down) ? _st.textFgOver : _st.textFg);
  438. _text.draw(p, {
  439. .position = { textLeft, textTop },
  440. .availableWidth = widthForText,
  441. .elisionLines = 1,
  442. });
  443. }
  444. if (_numbers) {
  445. textLeft += widthForText + (widthForText ? _st.numbersSkip : 0);
  446. p.setFont(_st.style.font);
  447. p.setPen((over || down) ? _st.numbersTextFgOver : _st.numbersTextFg);
  448. _numbers->paint(p, textLeft, textTop, width());
  449. }
  450. if (!_st.icon.empty()) {
  451. const auto &current = ((over || down) && !_st.iconOver.empty())
  452. ? _st.iconOver
  453. : _st.icon;
  454. current.paint(p, QPoint(iconLeft, iconTop), width());
  455. }
  456. }
  457. QImage RoundButton::prepareRippleMask() const {
  458. auto innerWidth = contentWidth();
  459. auto rounded = style::rtlrect(rect().marginsRemoved(_st.padding), width());
  460. if (_fullWidthOverride < 0) {
  461. rounded = QRect(0, rounded.top(), innerWidth - _fullWidthOverride, rounded.height());
  462. }
  463. return RippleAnimation::RoundRectMask(
  464. rounded.size(),
  465. (_fullRadius
  466. ? (rounded.height() / 2)
  467. : _st.radius
  468. ? _st.radius
  469. : st::buttonRadius));
  470. }
  471. QPoint RoundButton::prepareRippleStartPosition() const {
  472. return mapFromGlobal(QCursor::pos()) - QPoint(_st.padding.left(), _st.padding.top());
  473. }
  474. RoundButton::~RoundButton() = default;
  475. IconButton::IconButton(QWidget *parent, const style::IconButton &st) : RippleButton(parent, st.ripple)
  476. , _st(st) {
  477. resize(_st.width, _st.height);
  478. }
  479. const style::IconButton &IconButton::st() const {
  480. return _st;
  481. }
  482. void IconButton::setIconOverride(const style::icon *iconOverride, const style::icon *iconOverOverride) {
  483. _iconOverride = iconOverride;
  484. _iconOverrideOver = iconOverOverride;
  485. update();
  486. }
  487. void IconButton::setRippleColorOverride(const style::color *colorOverride) {
  488. _rippleColorOverride = colorOverride;
  489. }
  490. float64 IconButton::iconOverOpacity() const {
  491. return (isDown() || forceRippled())
  492. ? 1.
  493. : _a_over.value(isOver() ? 1. : 0.);
  494. }
  495. void IconButton::paintEvent(QPaintEvent *e) {
  496. Painter p(this);
  497. paintRipple(p, _st.rippleAreaPosition, _rippleColorOverride ? &(*_rippleColorOverride)->c : nullptr);
  498. const auto overIconOpacity = iconOverOpacity();
  499. const auto overIcon = [&] {
  500. if (_iconOverrideOver) {
  501. return _iconOverrideOver;
  502. } else if (!_st.iconOver.empty()) {
  503. return &_st.iconOver;
  504. } else if (_iconOverride) {
  505. return _iconOverride;
  506. }
  507. return &_st.icon;
  508. };
  509. const auto justIcon = [&] {
  510. if (_iconOverride) {
  511. return _iconOverride;
  512. }
  513. return &_st.icon;
  514. };
  515. const auto icon = (overIconOpacity == 1.) ? overIcon() : justIcon();
  516. auto position = _st.iconPosition;
  517. if (position.x() < 0) {
  518. position.setX((width() - icon->width()) / 2);
  519. }
  520. if (position.y() < 0) {
  521. position.setY((height() - icon->height()) / 2);
  522. }
  523. icon->paint(p, position, width());
  524. if (overIconOpacity > 0. && overIconOpacity < 1.) {
  525. const auto iconOver = overIcon();
  526. if (iconOver != icon) {
  527. p.setOpacity(overIconOpacity);
  528. iconOver->paint(p, position, width());
  529. }
  530. }
  531. }
  532. void IconButton::onStateChanged(State was, StateChangeSource source) {
  533. RippleButton::onStateChanged(was, source);
  534. auto over = isOver();
  535. auto wasOver = static_cast<bool>(was & StateFlag::Over);
  536. if (over != wasOver) {
  537. if (_st.duration) {
  538. auto from = over ? 0. : 1.;
  539. auto to = over ? 1. : 0.;
  540. _a_over.start([this] { update(); }, from, to, _st.duration);
  541. } else {
  542. update();
  543. }
  544. }
  545. }
  546. QPoint IconButton::prepareRippleStartPosition() const {
  547. auto result = mapFromGlobal(QCursor::pos())
  548. - _st.rippleAreaPosition;
  549. auto rect = QRect(0, 0, _st.rippleAreaSize, _st.rippleAreaSize);
  550. return rect.contains(result)
  551. ? result
  552. : DisabledRippleStartPosition();
  553. }
  554. QImage IconButton::prepareRippleMask() const {
  555. return RippleAnimation::EllipseMask(QSize(_st.rippleAreaSize, _st.rippleAreaSize));
  556. }
  557. CrossButton::CrossButton(QWidget *parent, const style::CrossButton &st) : RippleButton(parent, st.ripple)
  558. , _st(st)
  559. , _loadingAnimation([=](crl::time now) { return loadingCallback(now); }) {
  560. resize(_st.width, _st.height);
  561. setCursor(style::cur_pointer);
  562. setVisible(false);
  563. }
  564. bool CrossButton::loadingCallback(crl::time now) {
  565. const auto result = !stopLoadingAnimation(now);
  566. if (!result || !anim::Disabled()) {
  567. update();
  568. }
  569. return result;
  570. }
  571. void CrossButton::toggle(bool visible, anim::type animated) {
  572. if (_shown != visible) {
  573. _shown = visible;
  574. if (animated == anim::type::normal) {
  575. if (isHidden()) {
  576. setVisible(true);
  577. }
  578. _showAnimation.start(
  579. [=] { animationCallback(); },
  580. _shown ? 0. : 1.,
  581. _shown ? 1. : 0.,
  582. _st.duration);
  583. }
  584. }
  585. if (animated == anim::type::instant) {
  586. finishAnimating();
  587. }
  588. }
  589. void CrossButton::animationCallback() {
  590. update();
  591. if (!_showAnimation.animating()) {
  592. setVisible(_shown);
  593. }
  594. }
  595. void CrossButton::paintEvent(QPaintEvent *e) {
  596. auto p = QPainter(this);
  597. auto over = isOver();
  598. auto shown = _showAnimation.value(_shown ? 1. : 0.);
  599. p.setOpacity(shown);
  600. paintRipple(p, _st.crossPosition);
  601. auto loading = 0.;
  602. if (_loadingAnimation.animating()) {
  603. const auto now = crl::now();
  604. if (stopLoadingAnimation(now)) {
  605. _loadingAnimation.stop();
  606. } else if (anim::Disabled()) {
  607. CrossAnimation::paintStaticLoading(
  608. p,
  609. _st.cross,
  610. over ? _st.crossFgOver : _st.crossFg,
  611. _st.crossPosition.x(),
  612. _st.crossPosition.y(),
  613. width(),
  614. shown);
  615. return;
  616. } else {
  617. loading = ((now - _loadingAnimation.started())
  618. % _st.loadingPeriod) / float64(_st.loadingPeriod);
  619. }
  620. }
  621. CrossAnimation::paint(
  622. p,
  623. _st.cross,
  624. over ? _st.crossFgOver : _st.crossFg,
  625. _st.crossPosition.x(),
  626. _st.crossPosition.y(),
  627. width(),
  628. shown,
  629. loading);
  630. }
  631. bool CrossButton::stopLoadingAnimation(crl::time now) {
  632. if (!_loadingStopMs) {
  633. return false;
  634. }
  635. const auto stopPeriod = (_loadingStopMs - _loadingAnimation.started())
  636. / _st.loadingPeriod;
  637. const auto currentPeriod = (now - _loadingAnimation.started())
  638. / _st.loadingPeriod;
  639. if (currentPeriod != stopPeriod) {
  640. Assert(currentPeriod > stopPeriod);
  641. return true;
  642. }
  643. return false;
  644. }
  645. void CrossButton::setLoadingAnimation(bool enabled) {
  646. if (enabled) {
  647. _loadingStopMs = 0;
  648. if (!_loadingAnimation.animating()) {
  649. _loadingAnimation.start();
  650. }
  651. } else if (_loadingAnimation.animating()) {
  652. _loadingStopMs = crl::now();
  653. if (!((_loadingStopMs - _loadingAnimation.started())
  654. % _st.loadingPeriod)) {
  655. _loadingAnimation.stop();
  656. }
  657. }
  658. if (anim::Disabled()) {
  659. update();
  660. }
  661. }
  662. void CrossButton::onStateChanged(State was, StateChangeSource source) {
  663. RippleButton::onStateChanged(was, source);
  664. auto over = isOver();
  665. auto wasOver = static_cast<bool>(was & StateFlag::Over);
  666. if (over != wasOver) {
  667. update();
  668. }
  669. }
  670. QPoint CrossButton::prepareRippleStartPosition() const {
  671. return mapFromGlobal(QCursor::pos()) - _st.crossPosition;
  672. }
  673. QImage CrossButton::prepareRippleMask() const {
  674. return RippleAnimation::EllipseMask(QSize(_st.cross.size, _st.cross.size));
  675. }
  676. SettingsButton::SettingsButton(
  677. QWidget *parent,
  678. rpl::producer<QString> &&text,
  679. const style::SettingsButton &st)
  680. : SettingsButton(parent, std::move(text) | rpl::map([=](QString &&text) {
  681. return TextWithEntities{ std::move(text) };
  682. }), st) {
  683. }
  684. SettingsButton::SettingsButton(
  685. QWidget *parent,
  686. rpl::producer<TextWithEntities> &&text,
  687. const style::SettingsButton &st)
  688. : RippleButton(parent, st.ripple)
  689. , _st(st)
  690. , _padding(_st.padding) {
  691. std::move(
  692. text
  693. ) | rpl::start_with_next([this](TextWithEntities &&value) {
  694. setText(std::move(value));
  695. }, lifetime());
  696. }
  697. SettingsButton::SettingsButton(
  698. QWidget *parent,
  699. nullptr_t,
  700. const style::SettingsButton &st)
  701. : RippleButton(parent, st.ripple)
  702. , _st(st)
  703. , _padding(_st.padding) {
  704. }
  705. SettingsButton::~SettingsButton() = default;
  706. void SettingsButton::finishAnimating() {
  707. if (_toggle) {
  708. _toggle->finishAnimating();
  709. }
  710. Ui::RippleButton::finishAnimating();
  711. }
  712. SettingsButton *SettingsButton::toggleOn(
  713. rpl::producer<bool> &&toggled,
  714. bool ignoreClick) {
  715. Expects(_toggle == nullptr);
  716. _toggle = std::make_unique<Ui::ToggleView>(
  717. isOver() ? _st.toggleOver : _st.toggle,
  718. false,
  719. [this] { rtlupdate(toggleRect()); });
  720. if (!ignoreClick) {
  721. addClickHandler([this] {
  722. _toggle->setChecked(!_toggle->checked(), anim::type::normal);
  723. });
  724. }
  725. std::move(
  726. toggled
  727. ) | rpl::start_with_next([this](bool toggled) {
  728. _toggle->setChecked(toggled, anim::type::normal);
  729. }, lifetime());
  730. _toggle->finishAnimating();
  731. return this;
  732. }
  733. bool SettingsButton::toggled() const {
  734. return _toggle ? _toggle->checked() : false;
  735. }
  736. void SettingsButton::setToggleLocked(bool locked) {
  737. if (_toggle) {
  738. _toggle->setLocked(locked);
  739. }
  740. }
  741. rpl::producer<bool> SettingsButton::toggledChanges() const {
  742. if (_toggle) {
  743. return _toggle->checkedChanges();
  744. }
  745. return nullptr;
  746. }
  747. rpl::producer<bool> SettingsButton::toggledValue() const {
  748. if (_toggle) {
  749. return _toggle->checkedValue();
  750. }
  751. return nullptr;
  752. }
  753. void SettingsButton::setColorOverride(
  754. std::optional<QColor> textColorOverride) {
  755. _textColorOverride = textColorOverride;
  756. update();
  757. }
  758. void SettingsButton::setPaddingOverride(style::margins padding) {
  759. _padding = padding;
  760. resizeToWidth(widthNoMargins());
  761. }
  762. const style::SettingsButton &SettingsButton::st() const {
  763. return _st;
  764. }
  765. int SettingsButton::fullTextWidth() const {
  766. return _text.maxWidth();
  767. }
  768. void SettingsButton::paintEvent(QPaintEvent *e) {
  769. Painter p(this);
  770. const auto paintOver = (isOver() || isDown()) && !isDisabled();
  771. paintBg(p, e->rect(), paintOver);
  772. paintRipple(p, 0, 0);
  773. const auto outerw = width();
  774. paintText(p, paintOver, outerw);
  775. if (_toggle) {
  776. paintToggle(p, outerw);
  777. }
  778. }
  779. void SettingsButton::paintBg(Painter &p, const QRect &rect, bool over) const {
  780. p.fillRect(rect, over ? _st.textBgOver : _st.textBg);
  781. }
  782. void SettingsButton::paintText(Painter &p, bool over, int outerw) const {
  783. auto available = outerw - _padding.left() - _padding.right();
  784. if (_toggle) {
  785. available -= (width() - toggleRect().x());
  786. }
  787. if (available <= 0) {
  788. return;
  789. }
  790. p.setPen(_textColorOverride
  791. ? QPen(*_textColorOverride)
  792. : over
  793. ? _st.textFgOver
  794. : _st.textFg);
  795. _text.drawLeftElided(
  796. p,
  797. _padding.left(),
  798. _padding.top(),
  799. available,
  800. outerw);
  801. }
  802. void SettingsButton::paintToggle(Painter &p, int outerw) const {
  803. if (_toggle) {
  804. auto rect = toggleRect();
  805. _toggle->paint(p, rect.left(), rect.top(), outerw);
  806. }
  807. }
  808. QRect SettingsButton::toggleRect() const {
  809. Expects(_toggle != nullptr);
  810. auto size = _toggle->getSize();
  811. auto left = width() - _st.toggleSkip - size.width();
  812. auto top = (height() - size.height()) / 2;
  813. return { QPoint(left, top), size };
  814. }
  815. QRect SettingsButton::maybeToggleRect() const {
  816. return _toggle ? toggleRect() : QRect(0, 0, 0, 0);
  817. }
  818. int SettingsButton::resizeGetHeight(int newWidth) {
  819. return _padding.top() + _st.height + _padding.bottom();
  820. }
  821. void SettingsButton::onStateChanged(
  822. State was,
  823. StateChangeSource source) {
  824. if (!isDisabled() || !isDown()) {
  825. RippleButton::onStateChanged(was, source);
  826. }
  827. if (_toggle) {
  828. _toggle->setStyle(isOver() ? _st.toggleOver : _st.toggle);
  829. }
  830. setPointerCursor(!isDisabled());
  831. }
  832. void SettingsButton::setText(TextWithEntities &&text) {
  833. _text.setMarkedText(_st.style, text, kMarkupTextOptions);
  834. update();
  835. }
  836. not_null<RippleButton*> CreateSimpleRectButton(
  837. QWidget *parent,
  838. const style::RippleAnimation &st) {
  839. const auto result = CreateChild<SimpleRippleButton>(parent, st);
  840. result->paintRequest() | rpl::start_with_next([result] {
  841. auto p = QPainter(result);
  842. result->paintRipple(p, 0, 0);
  843. }, result->lifetime());
  844. return result;
  845. }
  846. not_null<RippleButton*> CreateSimpleSettingsButton(
  847. QWidget *parent,
  848. const style::RippleAnimation &st,
  849. const style::color &bg) {
  850. const auto result = CreateChild<SimpleRippleButton>(parent, st);
  851. result->paintRequest() | rpl::start_with_next([result, bg] {
  852. auto p = QPainter(result);
  853. const auto paintOver = (result->isOver() || result->isDown())
  854. && !result->isDisabled();
  855. if (paintOver) {
  856. p.fillRect(result->rect(), bg);
  857. }
  858. result->paintRipple(p, 0, 0);
  859. }, result->lifetime());
  860. return result;
  861. }
  862. not_null<RippleButton*> CreateSimpleCircleButton(
  863. QWidget *parent,
  864. const style::RippleAnimation &st) {
  865. const auto result = CreateChild<SimpleCircleButton>(parent, st);
  866. result->paintRequest() | rpl::start_with_next([result] {
  867. auto p = QPainter(result);
  868. result->paintRipple(p, 0, 0);
  869. }, result->lifetime());
  870. return result;
  871. }
  872. not_null<RippleButton*> CreateSimpleRoundButton(
  873. QWidget *parent,
  874. const style::RippleAnimation &st) {
  875. const auto result = CreateChild<SimpleRoundButton>(parent, st);
  876. result->paintRequest() | rpl::start_with_next([result] {
  877. auto p = QPainter(result);
  878. result->paintRipple(p, 0, 0);
  879. }, result->lifetime());
  880. return result;
  881. }
  882. } // namespace Ui