checkbox.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993
  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/checkbox.h"
  8. #include "ui/effects/ripple_animation.h"
  9. #include "ui/basic_click_handlers.h"
  10. #include "ui/qt_weak_factory.h"
  11. #include "ui/ui_utility.h"
  12. #include "ui/painter.h"
  13. #include "styles/palette.h"
  14. #include <QtGui/QtEvents>
  15. #include <QtCore/QtMath>
  16. namespace Ui {
  17. namespace {
  18. TextParseOptions _checkboxOptions = {
  19. TextParseMultiline, // flags
  20. 0, // maxw
  21. 0, // maxh
  22. Qt::LayoutDirectionAuto, // dir
  23. };
  24. TextParseOptions _checkboxRichOptions = {
  25. TextParseMultiline, // flags
  26. 0, // maxw
  27. 0, // maxh
  28. Qt::LayoutDirectionAuto, // dir
  29. };
  30. } // namespace
  31. AbstractCheckView::AbstractCheckView(int duration, bool checked, Fn<void()> updateCallback)
  32. : _duration(duration)
  33. , _checked(checked)
  34. , _updateCallback(std::move(updateCallback)) {
  35. }
  36. void AbstractCheckView::setChecked(bool checked, anim::type animated) {
  37. const auto changed = (_checked != checked);
  38. _checked = checked;
  39. if (animated == anim::type::instant) {
  40. finishAnimating();
  41. if (_updateCallback) {
  42. _updateCallback();
  43. }
  44. } else if (changed) {
  45. _toggleAnimation.start(
  46. [=] { if (_updateCallback) _updateCallback(); },
  47. _checked ? 0. : 1.,
  48. _checked ? 1. : 0.,
  49. _duration);
  50. }
  51. checkedChangedHook(animated);
  52. if (changed) {
  53. _checks.fire_copy(_checked);
  54. }
  55. }
  56. void AbstractCheckView::setUpdateCallback(Fn<void()> updateCallback) {
  57. _updateCallback = std::move(updateCallback);
  58. }
  59. void AbstractCheckView::update() {
  60. if (_updateCallback) {
  61. _updateCallback();
  62. }
  63. }
  64. void AbstractCheckView::finishAnimating() {
  65. _toggleAnimation.stop();
  66. }
  67. float64 AbstractCheckView::currentAnimationValue() {
  68. return _toggleAnimation.value(_checked ? 1. : 0.);
  69. }
  70. bool AbstractCheckView::animating() const {
  71. return _toggleAnimation.animating();
  72. }
  73. ToggleView::ToggleView(
  74. const style::Toggle &st,
  75. bool checked,
  76. Fn<void()> updateCallback)
  77. : AbstractCheckView(st.duration, checked, std::move(updateCallback))
  78. , _st(&st) {
  79. }
  80. QSize ToggleView::getSize() const {
  81. return QSize(2 * _st->border + _st->diameter + _st->width, 2 * _st->border + _st->diameter);
  82. }
  83. void ToggleView::setStyle(const style::Toggle &st) {
  84. _st = &st;
  85. }
  86. void ToggleView::paint(QPainter &p, int left, int top, int outerWidth) {
  87. left += _st->border;
  88. top += _st->border;
  89. PainterHighQualityEnabler hq(p);
  90. auto toggled = currentAnimationValue();
  91. auto fullWidth = _st->diameter + _st->width;
  92. auto innerDiameter = _st->diameter - 2 * _st->shift;
  93. auto innerRadius = float64(innerDiameter) / 2.;
  94. auto toggleLeft = left + anim::interpolate(0, fullWidth - _st->diameter, toggled);
  95. auto bgRect = style::rtlrect(left + _st->shift, top + _st->shift, fullWidth - 2 * _st->shift, innerDiameter, outerWidth);
  96. auto fgRect = style::rtlrect(toggleLeft, top, _st->diameter, _st->diameter, outerWidth);
  97. auto fgBrush = anim::brush(_st->untoggledFg, _st->toggledFg, toggled);
  98. p.setPen(Qt::NoPen);
  99. p.setBrush(fgBrush);
  100. p.drawRoundedRect(bgRect, innerRadius, innerRadius);
  101. auto pen = anim::pen(_st->untoggledFg, _st->toggledFg, toggled);
  102. pen.setWidth(_st->border);
  103. p.setPen(pen);
  104. p.setBrush(anim::brush(_st->untoggledBg, _st->toggledBg, toggled));
  105. p.drawEllipse(fgRect);
  106. if (_locked || _st->xsize > 0) {
  107. p.setPen(Qt::NoPen);
  108. p.setBrush(fgBrush);
  109. if (_locked) {
  110. const auto color = anim::color(_st->untoggledFg, _st->toggledFg, toggled);
  111. _st->lockIcon.paint(p, toggleLeft, top, outerWidth, color);
  112. } else {
  113. paintXV(p, toggleLeft, top, outerWidth, toggled, fgBrush);
  114. }
  115. }
  116. }
  117. void ToggleView::paintXV(QPainter &p, int left, int top, int outerWidth, float64 toggled, const QBrush &brush) {
  118. Expects(_st->vsize > 0);
  119. Expects(_st->stroke > 0);
  120. const auto stroke = (0. + _st->stroke) / M_SQRT2;
  121. if (toggled < 1) {
  122. // Just X or X->V.
  123. const auto xSize = 0. + _st->xsize;
  124. const auto xLeft = left + (_st->diameter - xSize) / 2.;
  125. const auto xTop = top + (_st->diameter - xSize) / 2.;
  126. QPointF pathX[] = {
  127. { xLeft, xTop + stroke },
  128. { xLeft + stroke, xTop },
  129. { xLeft + (xSize / 2.), xTop + (xSize / 2.) - stroke },
  130. { xLeft + xSize - stroke, xTop },
  131. { xLeft + xSize, xTop + stroke },
  132. { xLeft + (xSize / 2.) + stroke, xTop + (xSize / 2.) },
  133. { xLeft + xSize, xTop + xSize - stroke },
  134. { xLeft + xSize - stroke, xTop + xSize },
  135. { xLeft + (xSize / 2.), xTop + (xSize / 2.) + stroke },
  136. { xLeft + stroke, xTop + xSize },
  137. { xLeft, xTop + xSize - stroke },
  138. { xLeft + (xSize / 2.) - stroke, xTop + (xSize / 2.) },
  139. };
  140. for (auto &point : pathX) {
  141. point = style::rtlpoint(point, outerWidth);
  142. }
  143. if (toggled > 0) {
  144. // X->V.
  145. const auto vSize = 0. + _st->vsize;
  146. const auto fSize = (xSize + vSize - 2. * stroke);
  147. const auto vLeft = left + (_st->diameter - fSize) / 2.;
  148. const auto vTop = 0. + xTop + _st->vshift;
  149. QPointF pathV[] = {
  150. { vLeft, vTop + xSize - vSize + stroke },
  151. { vLeft + stroke, vTop + xSize - vSize },
  152. { vLeft + vSize - stroke, vTop + xSize - 2 * stroke },
  153. { vLeft + fSize - stroke, vTop },
  154. { vLeft + fSize, vTop + stroke },
  155. { vLeft + vSize, vTop + xSize - stroke },
  156. { vLeft + vSize, vTop + xSize - stroke },
  157. { vLeft + vSize - stroke, vTop + xSize },
  158. { vLeft + vSize - stroke, vTop + xSize },
  159. { vLeft + vSize - stroke, vTop + xSize },
  160. { vLeft + vSize - 2 * stroke, vTop + xSize - stroke },
  161. { vLeft + vSize - 2 * stroke, vTop + xSize - stroke },
  162. };
  163. for (auto &point : pathV) {
  164. point = style::rtlpoint(point, outerWidth);
  165. }
  166. p.fillPath(anim::interpolate(pathX, pathV, toggled), brush);
  167. } else {
  168. // Just X.
  169. p.fillPath(anim::path(pathX), brush);
  170. }
  171. } else {
  172. // Just V.
  173. const auto xSize = 0. + _st->xsize;
  174. const auto xTop = top + (_st->diameter - xSize) / 2.;
  175. const auto vSize = 0. + _st->vsize;
  176. const auto fSize = (xSize + vSize - 2. * stroke);
  177. const auto vLeft = left + (_st->diameter - (_st->xsize + _st->vsize - 2. * stroke)) / 2.;
  178. const auto vTop = 0. + xTop + _st->vshift;
  179. QPointF pathV[] = {
  180. { vLeft, vTop + xSize - vSize + stroke },
  181. { vLeft + stroke, vTop + xSize - vSize },
  182. { vLeft + vSize - stroke, vTop + xSize - 2 * stroke },
  183. { vLeft + fSize - stroke, vTop },
  184. { vLeft + fSize, vTop + stroke },
  185. { vLeft + vSize, vTop + xSize - stroke },
  186. { vLeft + vSize, vTop + xSize - stroke },
  187. { vLeft + vSize - stroke, vTop + xSize },
  188. { vLeft + vSize - stroke, vTop + xSize },
  189. { vLeft + vSize - stroke, vTop + xSize },
  190. { vLeft + vSize - 2 * stroke, vTop + xSize - stroke },
  191. { vLeft + vSize - 2 * stroke, vTop + xSize - stroke },
  192. };
  193. p.fillPath(anim::path(pathV), brush);
  194. }
  195. }
  196. QSize ToggleView::rippleSize() const {
  197. return getSize() + 2 * QSize(_st->rippleAreaPadding, _st->rippleAreaPadding);
  198. }
  199. QImage ToggleView::prepareRippleMask() const {
  200. auto size = rippleSize();
  201. return RippleAnimation::RoundRectMask(size, size.height() / 2);
  202. }
  203. bool ToggleView::checkRippleStartPosition(QPoint position) const {
  204. return QRect(QPoint(0, 0), rippleSize()).contains(position);
  205. }
  206. void ToggleView::setLocked(bool locked) {
  207. if (_locked != locked) {
  208. _locked = locked;
  209. update();
  210. }
  211. }
  212. CheckView::CheckView(const style::Check &st, bool checked, Fn<void()> updateCallback) : AbstractCheckView(st.duration, checked, std::move(updateCallback))
  213. , _st(&st) {
  214. }
  215. QSize CheckView::getSize() const {
  216. return QSize(_st->diameter, _st->diameter);
  217. }
  218. void CheckView::setStyle(const style::Check &st) {
  219. _st = &st;
  220. }
  221. void CheckView::paint(QPainter &p, int left, int top, int outerWidth) {
  222. auto toggled = currentAnimationValue();
  223. auto pen = _untoggledOverride
  224. ? anim::pen(*_untoggledOverride, _st->toggledFg, toggled)
  225. : anim::pen(_st->untoggledFg, _st->toggledFg, toggled);
  226. pen.setWidth(_st->thickness);
  227. p.setPen(pen);
  228. p.setBrush(anim::brush(
  229. _st->bg,
  230. (_untoggledOverride
  231. ? anim::color(*_untoggledOverride, _st->toggledFg, toggled)
  232. : anim::color(_st->untoggledFg, _st->toggledFg, toggled)),
  233. toggled));
  234. {
  235. PainterHighQualityEnabler hq(p);
  236. p.drawRoundedRect(style::rtlrect(QRectF(left, top, _st->diameter, _st->diameter).marginsRemoved(QMarginsF(_st->thickness / 2., _st->thickness / 2., _st->thickness / 2., _st->thickness / 2.)), outerWidth), st::roundRadiusSmall - (_st->thickness / 2.), st::roundRadiusSmall - (_st->thickness / 2.));
  237. }
  238. if (toggled > 0) {
  239. _st->icon.paint(p, QPoint(left, top), outerWidth);
  240. }
  241. }
  242. QSize CheckView::rippleSize() const {
  243. return getSize() + 2 * QSize(_st->rippleAreaPadding, _st->rippleAreaPadding);
  244. }
  245. QImage CheckView::prepareRippleMask() const {
  246. return RippleAnimation::EllipseMask(rippleSize());
  247. }
  248. bool CheckView::checkRippleStartPosition(QPoint position) const {
  249. return QRect(QPoint(0, 0), rippleSize()).contains(position);
  250. }
  251. void CheckView::setUntoggledOverride(
  252. std::optional<QColor> untoggledOverride) {
  253. _untoggledOverride = untoggledOverride;
  254. update();
  255. }
  256. Fn<void()> CheckView::PrepareNonToggledError(
  257. not_null<CheckView*> view,
  258. rpl::lifetime &lifetime) {
  259. struct State {
  260. bool error = false;
  261. Ui::Animations::Simple errorAnimation;
  262. };
  263. const auto state = lifetime.make_state<State>();
  264. view->checkedChanges(
  265. ) | rpl::filter([=](bool checked) {
  266. return checked;
  267. }) | rpl::start_with_next([=] {
  268. state->error = false;
  269. view->setUntoggledOverride(std::nullopt);
  270. }, lifetime);
  271. return [=] {
  272. const auto callback = [=] {
  273. const auto error = state->errorAnimation.value(
  274. state->error ? 1. : 0.);
  275. if (error == 0.) {
  276. view->setUntoggledOverride(std::nullopt);
  277. } else {
  278. const auto color = anim::color(
  279. st::defaultCheck.untoggledFg,
  280. st::boxTextFgError,
  281. error);
  282. view->setUntoggledOverride(color);
  283. }
  284. };
  285. state->error = true;
  286. state->errorAnimation.stop();
  287. state->errorAnimation.start(
  288. callback,
  289. 0.,
  290. 1.,
  291. st::defaultCheck.duration);
  292. };
  293. }
  294. RadioView::RadioView(
  295. const style::Radio &st,
  296. bool checked,
  297. Fn<void()> updateCallback)
  298. : AbstractCheckView(st.duration, checked, std::move(updateCallback))
  299. , _st(&st) {
  300. }
  301. QSize RadioView::getSize() const {
  302. return QSize(_st->diameter, _st->diameter);
  303. }
  304. void RadioView::setStyle(const style::Radio &st) {
  305. _st = &st;
  306. }
  307. void RadioView::paint(QPainter &p, int left, int top, int outerWidth) {
  308. PainterHighQualityEnabler hq(p);
  309. auto toggled = currentAnimationValue();
  310. auto pen = _toggledOverride
  311. ? (_untoggledOverride
  312. ? anim::pen(*_untoggledOverride, *_toggledOverride, toggled)
  313. : anim::pen(_st->untoggledFg, *_toggledOverride, toggled))
  314. : (_untoggledOverride
  315. ? anim::pen(*_untoggledOverride, _st->toggledFg, toggled)
  316. : anim::pen(_st->untoggledFg, _st->toggledFg, toggled));
  317. pen.setWidth(_st->thickness);
  318. p.setPen(pen);
  319. p.setBrush(_st->bg);
  320. //int32 skip = qCeil(_st->thickness / 2.);
  321. //p.drawEllipse(_checkRect.marginsRemoved(QMargins(skip, skip, skip, skip)));
  322. const auto skip = (_st->outerSkip / 10.) + (_st->thickness / 2);
  323. p.drawEllipse(style::rtlrect(QRectF(left, top, _st->diameter, _st->diameter).marginsRemoved(QMarginsF(skip, skip, skip, skip)), outerWidth));
  324. if (toggled > 0) {
  325. p.setPen(Qt::NoPen);
  326. p.setBrush(_toggledOverride
  327. ? (_untoggledOverride
  328. ? anim::brush(*_untoggledOverride, *_toggledOverride, toggled)
  329. : anim::brush(_st->untoggledFg, *_toggledOverride, toggled))
  330. : (_untoggledOverride
  331. ? anim::brush(*_untoggledOverride, _st->toggledFg, toggled)
  332. : anim::brush(_st->untoggledFg, _st->toggledFg, toggled)));
  333. const auto skip0 = _st->diameter / 2., skip1 = _st->skip / 10., checkSkip = skip0 * (1. - toggled) + skip1 * toggled;
  334. p.drawEllipse(style::rtlrect(QRectF(left, top, _st->diameter, _st->diameter).marginsRemoved(QMarginsF(checkSkip, checkSkip, checkSkip, checkSkip)), outerWidth));
  335. //int32 fskip = qFloor(checkSkip), cskip = qCeil(checkSkip);
  336. //if (2 * fskip < _checkRect.width()) {
  337. // if (fskip != cskip) {
  338. // p.setOpacity(float64(cskip) - checkSkip);
  339. // p.drawEllipse(_checkRect.marginsRemoved(QMargins(fskip, fskip, fskip, fskip)));
  340. // p.setOpacity(1.);
  341. // }
  342. // if (2 * cskip < _checkRect.width()) {
  343. // p.drawEllipse(_checkRect.marginsRemoved(QMargins(cskip, cskip, cskip, cskip)));
  344. // }
  345. //}
  346. }
  347. }
  348. QSize RadioView::rippleSize() const {
  349. return getSize() + 2 * QSize(_st->rippleAreaPadding, _st->rippleAreaPadding);
  350. }
  351. QImage RadioView::prepareRippleMask() const {
  352. return RippleAnimation::EllipseMask(rippleSize());
  353. }
  354. bool RadioView::checkRippleStartPosition(QPoint position) const {
  355. return QRect(QPoint(0, 0), rippleSize()).contains(position);
  356. }
  357. void RadioView::setToggledOverride(std::optional<QColor> toggledOverride) {
  358. _toggledOverride = toggledOverride;
  359. update();
  360. }
  361. void RadioView::setUntoggledOverride(
  362. std::optional<QColor> untoggledOverride) {
  363. _untoggledOverride = untoggledOverride;
  364. update();
  365. }
  366. Checkbox::Checkbox(
  367. QWidget *parent,
  368. const QString &text,
  369. bool checked,
  370. const style::Checkbox &st,
  371. const style::Check &checkSt)
  372. : Checkbox(
  373. parent,
  374. rpl::single(text) | rpl::map(TextWithEntities::Simple),
  375. st,
  376. std::make_unique<CheckView>(
  377. checkSt,
  378. checked)) {
  379. }
  380. Checkbox::Checkbox(
  381. QWidget *parent,
  382. const TextWithEntities &text,
  383. bool checked,
  384. const style::Checkbox &st,
  385. const style::Check &checkSt)
  386. : Checkbox(
  387. parent,
  388. rpl::single(text),
  389. st,
  390. std::make_unique<CheckView>(
  391. checkSt,
  392. checked)) {
  393. }
  394. Checkbox::Checkbox(
  395. QWidget *parent,
  396. const QString &text,
  397. bool checked,
  398. const style::Checkbox &st,
  399. const style::Toggle &toggleSt)
  400. : Checkbox(
  401. parent,
  402. rpl::single(text) | rpl::map(TextWithEntities::Simple),
  403. st,
  404. std::make_unique<ToggleView>(
  405. toggleSt,
  406. checked)) {
  407. }
  408. Checkbox::Checkbox(
  409. QWidget *parent,
  410. rpl::producer<QString> &&text,
  411. bool checked,
  412. const style::Checkbox &st,
  413. const style::Check &checkSt)
  414. : Checkbox(
  415. parent,
  416. std::move(text) | rpl::map(TextWithEntities::Simple),
  417. st,
  418. std::make_unique<CheckView>(
  419. checkSt,
  420. checked)) {
  421. }
  422. Checkbox::Checkbox(
  423. QWidget *parent,
  424. rpl::producer<QString> &&text,
  425. bool checked,
  426. const style::Checkbox &st,
  427. const style::Toggle &toggleSt)
  428. : Checkbox(
  429. parent,
  430. std::move(text) | rpl::map(TextWithEntities::Simple),
  431. st,
  432. std::make_unique<ToggleView>(
  433. toggleSt,
  434. checked)) {
  435. }
  436. Checkbox::Checkbox(
  437. QWidget *parent,
  438. const QString &text,
  439. const style::Checkbox &st,
  440. std::unique_ptr<AbstractCheckView> check)
  441. : Checkbox(
  442. parent,
  443. rpl::single(text) | rpl::map(TextWithEntities::Simple),
  444. st,
  445. std::move(check)) {
  446. }
  447. Checkbox::Checkbox(
  448. QWidget *parent,
  449. rpl::producer<TextWithEntities> &&text,
  450. const style::Checkbox &st,
  451. std::unique_ptr<AbstractCheckView> check)
  452. : RippleButton(parent, st.ripple)
  453. , _st(st)
  454. , _check(std::move(check))
  455. , _text(
  456. _st.style,
  457. QString(),
  458. _checkboxOptions,
  459. _st.style.font->elidew) {
  460. _check->setUpdateCallback([=] { update(); });
  461. resizeToText();
  462. setCursor(style::cur_pointer);
  463. std::move(
  464. text
  465. ) | rpl::start_with_next([=](TextWithEntities &&value) {
  466. if (value.entities.empty()) {
  467. setText(base::take(value.text));
  468. } else {
  469. _text.setMarkedText(
  470. _st.style,
  471. std::move(value),
  472. _checkboxRichOptions);
  473. resizeToText();
  474. if (_text.hasLinks()) {
  475. setMouseTracking(true);
  476. }
  477. update();
  478. }
  479. }, lifetime());
  480. }
  481. int Checkbox::countTextMinWidth() const {
  482. const auto leftSkip = _st.checkPosition.x()
  483. + checkRect().width()
  484. + _st.textPosition.x();
  485. return (_st.width > 0)
  486. ? std::max(_st.width - leftSkip, 1)
  487. : kQFixedMax;
  488. }
  489. QRect Checkbox::checkRect() const {
  490. auto size = _check->getSize();
  491. return QRect({
  492. (_checkAlignment & Qt::AlignHCenter)
  493. ? (width() - size.width()) / 2
  494. : (_checkAlignment & Qt::AlignRight)
  495. ? (width() - _st.checkPosition.x() - size.width())
  496. : _st.checkPosition.x(),
  497. (_checkAlignment & Qt::AlignVCenter)
  498. ? (height() - size.height()) / 2
  499. : (_checkAlignment & Qt::AlignBottom)
  500. ? (height() - _st.checkPosition.y() - size.height())
  501. : _st.checkPosition.y()
  502. }, size);
  503. }
  504. void Checkbox::setText(const QString &text, bool rich) {
  505. _text.setText(_st.style, text, rich ? _checkboxRichOptions : _checkboxOptions);
  506. resizeToText();
  507. update();
  508. }
  509. void Checkbox::setCheckAlignment(style::align alignment) {
  510. if (_checkAlignment != alignment) {
  511. _checkAlignment = alignment;
  512. resizeToText();
  513. update();
  514. }
  515. }
  516. void Checkbox::setAllowTextLines(int lines) {
  517. _allowTextLines = lines;
  518. resizeToText();
  519. update();
  520. }
  521. void Checkbox::setTextBreakEverywhere(bool allow) {
  522. _textBreakEverywhere = allow;
  523. }
  524. void Checkbox::setLink(uint16 index, const ClickHandlerPtr &lnk) {
  525. _text.setLink(index, lnk);
  526. }
  527. void Checkbox::setLinksTrusted() {
  528. static const auto TrustedLinksFilter = [](
  529. const ClickHandlerPtr &link,
  530. Qt::MouseButton button) {
  531. if (const auto url = dynamic_cast<UrlClickHandler*>(link.get())) {
  532. url->UrlClickHandler::onClick({ button });
  533. return false;
  534. }
  535. return true;
  536. };
  537. setClickHandlerFilter(TrustedLinksFilter);
  538. }
  539. void Checkbox::setClickHandlerFilter(ClickHandlerFilter &&filter) {
  540. _clickHandlerFilter = std::move(filter);
  541. }
  542. bool Checkbox::checked() const {
  543. return _check->checked();
  544. }
  545. rpl::producer<bool> Checkbox::checkedChanges() const {
  546. return _checkedChanges.events();
  547. }
  548. rpl::producer<bool> Checkbox::checkedValue() const {
  549. return _checkedChanges.events_starting_with(checked());
  550. }
  551. void Checkbox::resizeToText() {
  552. if (_st.width <= 0) {
  553. resizeToWidth(_text.maxWidth() - _st.width);
  554. } else {
  555. resizeToWidth(_st.width);
  556. }
  557. }
  558. void Checkbox::setChecked(bool checked, NotifyAboutChange notify) {
  559. if (_check->checked() != checked) {
  560. _check->setChecked(checked, anim::type::normal);
  561. if (notify == NotifyAboutChange::Notify) {
  562. _checkedChanges.fire_copy(checked);
  563. }
  564. }
  565. }
  566. void Checkbox::finishAnimating() {
  567. _check->finishAnimating();
  568. }
  569. int Checkbox::naturalWidth() const {
  570. if (_st.width > 0) {
  571. return _st.width;
  572. }
  573. auto result = _st.checkPosition.x() + _check->getSize().width();
  574. if (!_text.isEmpty()) {
  575. result += _st.textPosition.x() + _text.maxWidth();
  576. }
  577. return result - _st.width;
  578. }
  579. void Checkbox::paintEvent(QPaintEvent *e) {
  580. Painter p(this);
  581. auto check = checkRect();
  582. auto active = _check->currentAnimationValue();
  583. if (isDisabled()) {
  584. p.setOpacity(_st.disabledOpacity);
  585. } else {
  586. auto color = anim::color(_st.rippleBg, _st.rippleBgActive, active);
  587. paintRipple(p, check.topLeft() + _st.rippleAreaPosition, &color);
  588. }
  589. auto realCheckRect = myrtlrect(check);
  590. if (realCheckRect.intersects(e->rect())) {
  591. if (isDisabled()) {
  592. p.drawPixmapLeft(check.left(), check.top(), width(), _checkCache);
  593. } else {
  594. _check->paint(p, check.left(), check.top(), width());
  595. }
  596. }
  597. if (realCheckRect.contains(e->rect()) || _text.isEmpty()) {
  598. return;
  599. }
  600. const auto alignLeft = (_checkAlignment & Qt::AlignLeft);
  601. const auto alignRight = (_checkAlignment & Qt::AlignRight);
  602. const auto centered = ((_checkAlignment & Qt::AlignHCenter) != 0);
  603. const auto textSkip = _st.checkPosition.x()
  604. + check.width()
  605. + _st.textPosition.x();
  606. const auto availableTextWidth = centered
  607. ? std::max(width() - _st.margin.left() - _st.margin.right(), 1)
  608. : std::max(width() - textSkip, 1);
  609. const auto textTop = _st.margin.top() + _st.textPosition.y();
  610. p.setPen(anim::pen(_st.textFg, _st.textFgActive, active));
  611. if (alignLeft) {
  612. if (!_allowTextLines) {
  613. _text.drawLeft(
  614. p,
  615. textSkip,
  616. textTop,
  617. availableTextWidth,
  618. width());
  619. } else {
  620. _text.drawLeftElided(
  621. p,
  622. textSkip,
  623. textTop,
  624. availableTextWidth,
  625. width(),
  626. _allowTextLines,
  627. style::al_left,
  628. 0,
  629. -1,
  630. 0,
  631. _textBreakEverywhere);
  632. }
  633. } else if (alignRight) {
  634. if (!_allowTextLines) {
  635. _text.drawRight(
  636. p,
  637. textSkip,
  638. textTop,
  639. availableTextWidth,
  640. width());
  641. } else {
  642. _text.drawRightElided(
  643. p,
  644. textSkip,
  645. textTop,
  646. availableTextWidth,
  647. width(),
  648. _allowTextLines,
  649. style::al_left,
  650. 0,
  651. -1,
  652. 0,
  653. _textBreakEverywhere);
  654. }
  655. } else if (!_allowTextLines
  656. || (_text.countHeight(availableTextWidth)
  657. < (_allowTextLines + 1) * _st.style.font->height)) {
  658. _text.drawLeft(
  659. p,
  660. _st.margin.left(),
  661. textTop,
  662. width() - _st.margin.left() - _st.margin.right(),
  663. width(),
  664. style::al_top);
  665. } else {
  666. _text.drawLeftElided(
  667. p,
  668. _st.margin.left(),
  669. textTop,
  670. width() - _st.margin.left() - _st.margin.right(),
  671. width(),
  672. _allowTextLines,
  673. style::al_top,
  674. 0,
  675. -1,
  676. 0,
  677. _textBreakEverywhere);
  678. }
  679. }
  680. void Checkbox::mousePressEvent(QMouseEvent *e) {
  681. RippleButton::mousePressEvent(e);
  682. ClickHandler::pressed();
  683. }
  684. void Checkbox::mouseMoveEvent(QMouseEvent *e) {
  685. RippleButton::mouseMoveEvent(e);
  686. const auto state = getTextState(e->pos());
  687. if (state.link != ClickHandler::getActive()) {
  688. ClickHandler::setActive(state.link, this);
  689. update();
  690. }
  691. }
  692. void Checkbox::mouseReleaseEvent(QMouseEvent *e) {
  693. const auto weak = Ui::MakeWeak(this);
  694. if (auto activated = _activatingHandler = ClickHandler::unpressed()) {
  695. // _clickHandlerFilter may delete `this`. In that case we don't want
  696. // to try to show a context menu or smth like that.
  697. const auto button = e->button();
  698. crl::on_main(this, [=] {
  699. const auto guard = window();
  700. if (!_clickHandlerFilter
  701. || _clickHandlerFilter(activated, button)) {
  702. ActivateClickHandler(guard, activated, button);
  703. }
  704. });
  705. }
  706. RippleButton::mouseReleaseEvent(e);
  707. if (weak) {
  708. _activatingHandler = nullptr;
  709. }
  710. }
  711. void Checkbox::leaveEventHook(QEvent *e) {
  712. RippleButton::leaveEventHook(e);
  713. ClickHandler::clearActive(this);
  714. }
  715. Text::StateResult Checkbox::getTextState(const QPoint &m) const {
  716. if (!(_checkAlignment & Qt::AlignLeft)) {
  717. return {};
  718. }
  719. const auto check = checkRect();
  720. const auto textSkip = _st.checkPosition.x()
  721. + check.width()
  722. + _st.textPosition.x();
  723. const auto availableTextWidth = std::max(width() - textSkip, 1);
  724. const auto textTop = _st.margin.top() + _st.textPosition.y();
  725. auto request = Ui::Text::StateRequestElided();
  726. request.lines = _allowTextLines;
  727. return !_allowTextLines
  728. ? _text.getState(
  729. m - QPoint(textSkip, textTop),
  730. availableTextWidth,
  731. {})
  732. : _text.getStateElidedLeft(
  733. m - QPoint(textSkip, textTop),
  734. availableTextWidth,
  735. width(),
  736. request);
  737. }
  738. QPixmap Checkbox::grabCheckCache() const {
  739. auto checkSize = _check->getSize();
  740. auto image = QImage(
  741. checkSize * style::DevicePixelRatio(),
  742. QImage::Format_ARGB32_Premultiplied);
  743. image.fill(Qt::transparent);
  744. image.setDevicePixelRatio(style::DevicePixelRatio());
  745. {
  746. Painter p(&image);
  747. _check->paint(p, 0, 0, checkSize.width());
  748. }
  749. return PixmapFromImage(std::move(image));
  750. }
  751. void Checkbox::onStateChanged(State was, StateChangeSource source) {
  752. RippleButton::onStateChanged(was, source);
  753. if (isDisabled() && !(was & StateFlag::Disabled)) {
  754. setCursor(style::cur_default);
  755. finishAnimating();
  756. _checkCache = grabCheckCache();
  757. } else if (!isDisabled() && (was & StateFlag::Disabled)) {
  758. setCursor(style::cur_pointer);
  759. _checkCache = QPixmap();
  760. }
  761. auto now = state();
  762. if (!isDisabled() && (was & StateFlag::Over) && (now & StateFlag::Over)) {
  763. if ((was & StateFlag::Down) && !(now & StateFlag::Down)) {
  764. handlePress();
  765. }
  766. }
  767. }
  768. void Checkbox::handlePress() {
  769. if (!_activatingHandler) {
  770. setChecked(!checked());
  771. }
  772. }
  773. int Checkbox::resizeGetHeight(int newWidth) {
  774. const auto result = _check->getSize().height();
  775. const auto centered = ((_checkAlignment & Qt::AlignHCenter) != 0);
  776. if (!centered && _allowTextLines == 1) {
  777. return result;
  778. }
  779. const auto textSkip = _st.checkPosition.x()
  780. + checkRect().width()
  781. + _st.textPosition.x();
  782. const auto fullWidth = _st.margin.left() + newWidth + _st.margin.right();
  783. const auto availableTextWidth = centered
  784. ? std::max(newWidth, 1)
  785. : std::max(fullWidth - textSkip, 1);
  786. const auto textHeight = _text.countHeight(availableTextWidth);
  787. const auto textBottom = _st.textPosition.y()
  788. + (_allowTextLines
  789. ? std::min(textHeight, _allowTextLines * _st.style.font->height)
  790. : textHeight);
  791. return std::max(result, textBottom);
  792. }
  793. QImage Checkbox::prepareRippleMask() const {
  794. return _check->prepareRippleMask();
  795. }
  796. QPoint Checkbox::prepareRippleStartPosition() const {
  797. if (isDisabled()) {
  798. return DisabledRippleStartPosition();
  799. }
  800. auto position = myrtlpoint(mapFromGlobal(QCursor::pos()))
  801. - checkRect().topLeft()
  802. - _st.rippleAreaPosition;
  803. return _check->checkRippleStartPosition(position)
  804. ? position
  805. : DisabledRippleStartPosition();
  806. }
  807. void RadiobuttonGroup::setValue(int value) {
  808. if (_hasValue && _value == value) {
  809. return;
  810. }
  811. _hasValue = true;
  812. _value = value;
  813. for (const auto button : _buttons) {
  814. button->handleNewGroupValue(_value);
  815. }
  816. const auto guard = weak_from_this();
  817. _changes.fire_copy(value);
  818. if (guard.lock()) {
  819. if (const auto callback = _changedCallback) {
  820. callback(_value);
  821. }
  822. }
  823. }
  824. void RadiobuttonGroup::registerButton(Radiobutton *button) {
  825. if (!base::contains(_buttons, button)) {
  826. _buttons.push_back(button);
  827. }
  828. }
  829. void RadiobuttonGroup::unregisterButton(Radiobutton *button) {
  830. _buttons.erase(ranges::remove(_buttons, button), _buttons.end());
  831. }
  832. Radiobutton::Radiobutton(
  833. QWidget *parent,
  834. const std::shared_ptr<RadiobuttonGroup> &group,
  835. int value,
  836. const QString &text,
  837. const style::Checkbox &st,
  838. const style::Radio &radioSt)
  839. : Radiobutton(
  840. parent,
  841. group,
  842. value,
  843. text,
  844. st,
  845. std::make_unique<RadioView>(
  846. radioSt,
  847. (group->hasValue() && group->current() == value))) {
  848. }
  849. Radiobutton::Radiobutton(
  850. QWidget *parent,
  851. const std::shared_ptr<RadiobuttonGroup> &group,
  852. int value,
  853. const QString &text,
  854. const style::Checkbox &st,
  855. std::unique_ptr<AbstractCheckView> check)
  856. : Checkbox(
  857. parent,
  858. text,
  859. st,
  860. std::move(check))
  861. , _group(group)
  862. , _value(value) {
  863. using namespace rpl::mappers;
  864. checkbox()->setChecked(group->hasValue() && group->current() == value);
  865. _group->registerButton(this);
  866. checkbox()->checkedChanges(
  867. ) | rpl::filter(
  868. _1
  869. ) | rpl::start_with_next([=] {
  870. _group->setValue(_value);
  871. }, lifetime());
  872. }
  873. void Radiobutton::handleNewGroupValue(int value) {
  874. auto checked = (value == _value);
  875. if (checkbox()->checked() != checked) {
  876. checkbox()->setChecked(
  877. checked,
  878. Ui::Checkbox::NotifyAboutChange::DontNotify);
  879. }
  880. }
  881. void Radiobutton::handlePress() {
  882. if (!checkbox()->checked()) {
  883. checkbox()->setChecked(true);
  884. }
  885. }
  886. Radiobutton::~Radiobutton() {
  887. _group->unregisterButton(this);
  888. }
  889. } // namespace Ui