qtmaterialflatbutton.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742
  1. #include "qtmaterialflatbutton.h"
  2. #include "qtmaterialflatbutton_p.h"
  3. #include <QPainter>
  4. #include <QMouseEvent>
  5. #include <QResizeEvent>
  6. #include <QBitmap>
  7. #include <QPainterPath>
  8. #include <QIcon>
  9. #include "lib/qtmaterialrippleoverlay.h"
  10. #include "lib/qtmaterialripple.h"
  11. #include "lib/qtmaterialstyle.h"
  12. #include "qtmaterialflatbutton_internal.h"
  13. /*!
  14. * \class QtMaterialFlatButtonPrivate
  15. * \internal
  16. */
  17. /*!
  18. * \internal
  19. */
  20. QtMaterialFlatButtonPrivate::QtMaterialFlatButtonPrivate(QtMaterialFlatButton *q)
  21. : q_ptr(q)
  22. {
  23. }
  24. /*!
  25. * \internal
  26. */
  27. QtMaterialFlatButtonPrivate::~QtMaterialFlatButtonPrivate()
  28. {
  29. }
  30. /*!
  31. * \internal
  32. */
  33. void QtMaterialFlatButtonPrivate::init()
  34. {
  35. Q_Q(QtMaterialFlatButton);
  36. rippleOverlay = new QtMaterialRippleOverlay(q);
  37. stateMachine = new QtMaterialFlatButtonStateMachine(q);
  38. role = Material::Default;
  39. rippleStyle = Material::PositionedRipple;
  40. iconPlacement = Material::LeftIcon;
  41. overlayStyle = Material::GrayOverlay;
  42. bgMode = Qt::TransparentMode;
  43. textAlignment = Qt::AlignHCenter;
  44. fixedRippleRadius = 64;
  45. cornerRadius = 3;
  46. baseOpacity = 0.13;
  47. fontSize = 10; // 10.5;
  48. useThemeColors = true;
  49. useFixedRippleRadius = false;
  50. haloVisible = true;
  51. q->setStyle(&QtMaterialStyle::instance());
  52. q->setAttribute(Qt::WA_Hover);
  53. q->setMouseTracking(true);
  54. QFont font("Roboto", fontSize, QFont::Medium);
  55. font.setCapitalization(QFont::AllUppercase);
  56. q->setFont(font);
  57. QPainterPath path;
  58. path.addRoundedRect(q->rect(), cornerRadius, cornerRadius);
  59. rippleOverlay->setClipPath(path);
  60. rippleOverlay->setClipping(true);
  61. stateMachine->setupProperties();
  62. stateMachine->startAnimations();
  63. }
  64. /*!
  65. * \class QtMaterialFlatButton
  66. */
  67. QtMaterialFlatButton::QtMaterialFlatButton(QWidget *parent, Material::ButtonPreset preset)
  68. : QPushButton(parent),
  69. d_ptr(new QtMaterialFlatButtonPrivate(this))
  70. {
  71. d_func()->init();
  72. applyPreset(preset);
  73. }
  74. QtMaterialFlatButton::QtMaterialFlatButton(const QString &text, QWidget *parent, Material::ButtonPreset preset)
  75. : QPushButton(text, parent),
  76. d_ptr(new QtMaterialFlatButtonPrivate(this))
  77. {
  78. d_func()->init();
  79. applyPreset(preset);
  80. }
  81. QtMaterialFlatButton::QtMaterialFlatButton(const QString &text, Material::Role role, QWidget *parent, Material::ButtonPreset preset)
  82. : QPushButton(text, parent),
  83. d_ptr(new QtMaterialFlatButtonPrivate(this))
  84. {
  85. d_func()->init();
  86. applyPreset(preset);
  87. setRole(role);
  88. }
  89. QtMaterialFlatButton::~QtMaterialFlatButton()
  90. {
  91. }
  92. void QtMaterialFlatButton::applyPreset(Material::ButtonPreset preset)
  93. {
  94. switch (preset)
  95. {
  96. case Material::FlatPreset:
  97. setOverlayStyle(Material::NoOverlay);
  98. break;
  99. case Material::CheckablePreset:
  100. setOverlayStyle(Material::NoOverlay);
  101. setCheckable(true);
  102. setHaloVisible(false);
  103. break;
  104. default:
  105. break;
  106. }
  107. }
  108. void QtMaterialFlatButton::setUseThemeColors(bool value)
  109. {
  110. Q_D(QtMaterialFlatButton);
  111. if (d->useThemeColors == value) {
  112. return;
  113. }
  114. d->useThemeColors = value;
  115. d->stateMachine->setupProperties();
  116. }
  117. bool QtMaterialFlatButton::useThemeColors() const
  118. {
  119. Q_D(const QtMaterialFlatButton);
  120. return d->useThemeColors;
  121. }
  122. void QtMaterialFlatButton::setRole(Material::Role role)
  123. {
  124. Q_D(QtMaterialFlatButton);
  125. d->role = role;
  126. d->stateMachine->setupProperties();
  127. }
  128. Material::Role QtMaterialFlatButton::role() const
  129. {
  130. Q_D(const QtMaterialFlatButton);
  131. return d->role;
  132. }
  133. void QtMaterialFlatButton::setForegroundColor(const QColor &color)
  134. {
  135. Q_D(QtMaterialFlatButton);
  136. d->foregroundColor = color;
  137. MATERIAL_DISABLE_THEME_COLORS
  138. update();
  139. }
  140. QColor QtMaterialFlatButton::foregroundColor() const
  141. {
  142. Q_D(const QtMaterialFlatButton);
  143. if (d->useThemeColors || !d->foregroundColor.isValid())
  144. {
  145. if (Qt::OpaqueMode == d->bgMode) {
  146. return QtMaterialStyle::instance().themeColor("canvas");
  147. }
  148. switch (d->role)
  149. {
  150. case Material::Primary:
  151. return QtMaterialStyle::instance().themeColor("primary1");
  152. case Material::Secondary:
  153. return QtMaterialStyle::instance().themeColor("accent1");
  154. case Material::Default:
  155. default:
  156. return QtMaterialStyle::instance().themeColor("text");
  157. }
  158. }
  159. return d->foregroundColor;
  160. }
  161. void QtMaterialFlatButton::setBackgroundColor(const QColor &color)
  162. {
  163. Q_D(QtMaterialFlatButton);
  164. d->backgroundColor = color;
  165. MATERIAL_DISABLE_THEME_COLORS
  166. update();
  167. }
  168. QColor QtMaterialFlatButton::backgroundColor() const
  169. {
  170. Q_D(const QtMaterialFlatButton);
  171. if (d->useThemeColors || !d->backgroundColor.isValid())
  172. {
  173. switch (d->role)
  174. {
  175. case Material::Primary:
  176. return QtMaterialStyle::instance().themeColor("primary1");
  177. case Material::Secondary:
  178. return QtMaterialStyle::instance().themeColor("accent1");
  179. case Material::Default:
  180. default:
  181. return QtMaterialStyle::instance().themeColor("text");
  182. }
  183. }
  184. return d->backgroundColor;
  185. }
  186. void QtMaterialFlatButton::setOverlayColor(const QColor &color)
  187. {
  188. Q_D(QtMaterialFlatButton);
  189. d->overlayColor = color;
  190. MATERIAL_DISABLE_THEME_COLORS
  191. setOverlayStyle(Material::TintedOverlay);
  192. update();
  193. }
  194. QColor QtMaterialFlatButton::overlayColor() const
  195. {
  196. Q_D(const QtMaterialFlatButton);
  197. if (d->useThemeColors || !d->overlayColor.isValid()) {
  198. return foregroundColor();
  199. }
  200. return d->overlayColor;
  201. }
  202. void QtMaterialFlatButton::setDisabledForegroundColor(const QColor &color)
  203. {
  204. Q_D(QtMaterialFlatButton);
  205. d->disabledColor = color;
  206. MATERIAL_DISABLE_THEME_COLORS
  207. update();
  208. }
  209. QColor QtMaterialFlatButton::disabledForegroundColor() const
  210. {
  211. Q_D(const QtMaterialFlatButton);
  212. if (d->useThemeColors || !d->disabledColor.isValid()) {
  213. return QtMaterialStyle::instance().themeColor("disabled");
  214. } else {
  215. return d->disabledColor;
  216. }
  217. }
  218. void QtMaterialFlatButton::setDisabledBackgroundColor(const QColor &color)
  219. {
  220. Q_D(QtMaterialFlatButton);
  221. d->disabledBackgroundColor = color;
  222. MATERIAL_DISABLE_THEME_COLORS
  223. update();
  224. }
  225. QColor QtMaterialFlatButton::disabledBackgroundColor() const
  226. {
  227. Q_D(const QtMaterialFlatButton);
  228. if (d->useThemeColors || !d->disabledBackgroundColor.isValid()) {
  229. return QtMaterialStyle::instance().themeColor("disabled3");
  230. } else {
  231. return d->disabledBackgroundColor;
  232. }
  233. }
  234. void QtMaterialFlatButton::setFontSize(qreal size)
  235. {
  236. Q_D(QtMaterialFlatButton);
  237. d->fontSize = size;
  238. QFont f(font());
  239. f.setPointSizeF(size);
  240. setFont(f);
  241. update();
  242. }
  243. qreal QtMaterialFlatButton::fontSize() const
  244. {
  245. Q_D(const QtMaterialFlatButton);
  246. return d->fontSize;
  247. }
  248. void QtMaterialFlatButton::setHaloVisible(bool visible)
  249. {
  250. Q_D(QtMaterialFlatButton);
  251. d->haloVisible = visible;
  252. update();
  253. }
  254. bool QtMaterialFlatButton::isHaloVisible() const
  255. {
  256. Q_D(const QtMaterialFlatButton);
  257. return d->haloVisible;
  258. }
  259. void QtMaterialFlatButton::setOverlayStyle(Material::OverlayStyle style)
  260. {
  261. Q_D(QtMaterialFlatButton);
  262. d->overlayStyle = style;
  263. update();
  264. }
  265. Material::OverlayStyle QtMaterialFlatButton::overlayStyle() const
  266. {
  267. Q_D(const QtMaterialFlatButton);
  268. return d->overlayStyle;
  269. }
  270. void QtMaterialFlatButton::setRippleStyle(Material::RippleStyle style)
  271. {
  272. Q_D(QtMaterialFlatButton);
  273. d->rippleStyle = style;
  274. }
  275. Material::RippleStyle QtMaterialFlatButton::rippleStyle() const
  276. {
  277. Q_D(const QtMaterialFlatButton);
  278. return d->rippleStyle;
  279. }
  280. void QtMaterialFlatButton::setIconPlacement(Material::ButtonIconPlacement placement)
  281. {
  282. Q_D(QtMaterialFlatButton);
  283. d->iconPlacement = placement;
  284. update();
  285. }
  286. Material::ButtonIconPlacement QtMaterialFlatButton::iconPlacement() const
  287. {
  288. Q_D(const QtMaterialFlatButton);
  289. return d->iconPlacement;
  290. }
  291. void QtMaterialFlatButton::setCornerRadius(qreal radius)
  292. {
  293. Q_D(QtMaterialFlatButton);
  294. d->cornerRadius = radius;
  295. updateClipPath();
  296. update();
  297. }
  298. qreal QtMaterialFlatButton::cornerRadius() const
  299. {
  300. Q_D(const QtMaterialFlatButton);
  301. return d->cornerRadius;
  302. }
  303. void QtMaterialFlatButton::setBackgroundMode(Qt::BGMode mode)
  304. {
  305. Q_D(QtMaterialFlatButton);
  306. d->bgMode = mode;
  307. d->stateMachine->setupProperties();
  308. }
  309. Qt::BGMode QtMaterialFlatButton::backgroundMode() const
  310. {
  311. Q_D(const QtMaterialFlatButton);
  312. return d->bgMode;
  313. }
  314. void QtMaterialFlatButton::setBaseOpacity(qreal opacity)
  315. {
  316. Q_D(QtMaterialFlatButton);
  317. d->baseOpacity = opacity;
  318. d->stateMachine->setupProperties();
  319. }
  320. qreal QtMaterialFlatButton::baseOpacity() const
  321. {
  322. Q_D(const QtMaterialFlatButton);
  323. return d->baseOpacity;
  324. }
  325. void QtMaterialFlatButton::setCheckable(bool value)
  326. {
  327. Q_D(QtMaterialFlatButton);
  328. d->stateMachine->updateCheckedStatus();
  329. QPushButton::setCheckable(value);
  330. }
  331. void QtMaterialFlatButton::setHasFixedRippleRadius(bool value)
  332. {
  333. Q_D(QtMaterialFlatButton);
  334. d->useFixedRippleRadius = value;
  335. }
  336. bool QtMaterialFlatButton::hasFixedRippleRadius() const
  337. {
  338. Q_D(const QtMaterialFlatButton);
  339. return d->useFixedRippleRadius;
  340. }
  341. void QtMaterialFlatButton::setFixedRippleRadius(qreal radius)
  342. {
  343. Q_D(QtMaterialFlatButton);
  344. d->fixedRippleRadius = radius;
  345. setHasFixedRippleRadius(true);
  346. }
  347. void QtMaterialFlatButton::setTextAlignment(Qt::Alignment alignment)
  348. {
  349. Q_D(QtMaterialFlatButton);
  350. d->textAlignment = alignment;
  351. }
  352. Qt::Alignment QtMaterialFlatButton::textAlignment() const
  353. {
  354. Q_D(const QtMaterialFlatButton);
  355. return d->textAlignment;
  356. }
  357. /*!
  358. * \reimp
  359. */
  360. QSize QtMaterialFlatButton::sizeHint() const
  361. {
  362. ensurePolished();
  363. QSize label(fontMetrics().size(Qt::TextSingleLine, text()));
  364. int w = 20 + label.width();
  365. int h = label.height();
  366. if (!icon().isNull()) {
  367. w += iconSize().width() + QtMaterialFlatButton::IconPadding;
  368. h = qMax(h, iconSize().height());
  369. }
  370. return QSize(w, 20 + h);
  371. }
  372. QtMaterialFlatButton::QtMaterialFlatButton(QtMaterialFlatButtonPrivate &d,QWidget *parent, Material::ButtonPreset preset)
  373. : QPushButton(parent),
  374. d_ptr(&d)
  375. {
  376. d_func()->init();
  377. applyPreset(preset);
  378. }
  379. /*!
  380. * \reimp
  381. */
  382. void QtMaterialFlatButton::checkStateSet()
  383. {
  384. Q_D(QtMaterialFlatButton);
  385. d->stateMachine->updateCheckedStatus();
  386. QPushButton::checkStateSet();
  387. }
  388. /*!
  389. * \reimp
  390. */
  391. void QtMaterialFlatButton::mousePressEvent(QMouseEvent *event)
  392. {
  393. Q_D(QtMaterialFlatButton);
  394. if (Material::NoRipple != d->rippleStyle)
  395. {
  396. QPoint pos;
  397. qreal radiusEndValue;
  398. if (Material::CenteredRipple == d->rippleStyle) {
  399. pos = rect().center();
  400. } else {
  401. pos = event->pos();
  402. }
  403. if (d->useFixedRippleRadius) {
  404. radiusEndValue = d->fixedRippleRadius;
  405. } else {
  406. radiusEndValue = static_cast<qreal>(width())/2;
  407. }
  408. QtMaterialRipple *ripple = new QtMaterialRipple(pos);
  409. ripple->setRadiusEndValue(radiusEndValue);
  410. ripple->setOpacityStartValue(0.35);
  411. ripple->setColor(foregroundColor());
  412. ripple->radiusAnimation()->setDuration(600);
  413. ripple->opacityAnimation()->setDuration(1300);
  414. d->rippleOverlay->addRipple(ripple);
  415. }
  416. QPushButton::mousePressEvent(event);
  417. }
  418. /*!
  419. * \reimp
  420. */
  421. void QtMaterialFlatButton::mouseReleaseEvent(QMouseEvent *event)
  422. {
  423. Q_D(QtMaterialFlatButton);
  424. QPushButton::mouseReleaseEvent(event);
  425. d->stateMachine->updateCheckedStatus();
  426. }
  427. void QtMaterialFlatButton::resizeEvent(QResizeEvent *event)
  428. {
  429. QPushButton::resizeEvent(event);
  430. updateClipPath();
  431. }
  432. /*!
  433. * \reimp
  434. */
  435. void QtMaterialFlatButton::paintEvent(QPaintEvent *event)
  436. {
  437. Q_UNUSED(event)
  438. Q_D(QtMaterialFlatButton);
  439. QPainter painter(this);
  440. painter.setRenderHint(QPainter::Antialiasing);
  441. const qreal cr = d->cornerRadius;
  442. if (cr > 0)
  443. {
  444. QPainterPath path;
  445. path.addRoundedRect(rect(), cr, cr);
  446. painter.setClipPath(path);
  447. painter.setClipping(true);
  448. }
  449. paintBackground(&painter);
  450. paintHalo(&painter);
  451. painter.setOpacity(1);
  452. painter.setClipping(false);
  453. paintForeground(&painter);
  454. }
  455. /*!
  456. * \internal
  457. */
  458. void QtMaterialFlatButton::paintBackground(QPainter *painter)
  459. {
  460. Q_D(QtMaterialFlatButton);
  461. const qreal overlayOpacity = d->stateMachine->overlayOpacity();
  462. const qreal checkedProgress = d->stateMachine->checkedOverlayProgress();
  463. if (Qt::OpaqueMode == d->bgMode) {
  464. QBrush brush;
  465. brush.setStyle(Qt::SolidPattern);
  466. if (isEnabled()) {
  467. brush.setColor(backgroundColor());
  468. } else {
  469. brush.setColor(disabledBackgroundColor());
  470. }
  471. painter->setOpacity(1);
  472. painter->setBrush(brush);
  473. painter->setPen(Qt::NoPen);
  474. painter->drawRect(rect());
  475. }
  476. QBrush brush;
  477. brush.setStyle(Qt::SolidPattern);
  478. painter->setPen(Qt::NoPen);
  479. if (!isEnabled()) {
  480. return;
  481. }
  482. if ((Material::NoOverlay != d->overlayStyle) && (overlayOpacity > 0)) {
  483. if (Material::TintedOverlay == d->overlayStyle) {
  484. brush.setColor(overlayColor());
  485. } else {
  486. brush.setColor(Qt::gray);
  487. }
  488. painter->setOpacity(overlayOpacity);
  489. painter->setBrush(brush);
  490. painter->drawRect(rect());
  491. }
  492. if (isCheckable() && checkedProgress > 0) {
  493. const qreal q = Qt::TransparentMode == d->bgMode ? 0.45 : 0.7;
  494. brush.setColor(foregroundColor());
  495. painter->setOpacity(q*checkedProgress);
  496. painter->setBrush(brush);
  497. QRect r(rect());
  498. r.setHeight(static_cast<qreal>(r.height())*checkedProgress);
  499. painter->drawRect(r);
  500. }
  501. }
  502. /*!
  503. * \internal
  504. */
  505. void QtMaterialFlatButton::paintHalo(QPainter *painter)
  506. {
  507. Q_D(QtMaterialFlatButton);
  508. if (!d->haloVisible) {
  509. return;
  510. }
  511. const qreal opacity = d->stateMachine->haloOpacity();
  512. const qreal s = d->stateMachine->haloScaleFactor()*d->stateMachine->haloSize();
  513. const qreal radius = static_cast<qreal>(width())*s;
  514. if (isEnabled() && opacity > 0) {
  515. QBrush brush;
  516. brush.setStyle(Qt::SolidPattern);
  517. brush.setColor(foregroundColor());
  518. painter->setOpacity(opacity);
  519. painter->setBrush(brush);
  520. painter->setPen(Qt::NoPen);
  521. const QPointF center = rect().center();
  522. painter->drawEllipse(center, radius, radius);
  523. }
  524. }
  525. #define COLOR_INTERPOLATE(CH) (1-progress)*source.CH() + progress*dest.CH()
  526. /*!
  527. * \internal
  528. */
  529. void QtMaterialFlatButton::paintForeground(QPainter *painter)
  530. {
  531. Q_D(QtMaterialFlatButton);
  532. if (isEnabled()) {
  533. painter->setPen(foregroundColor());
  534. const qreal progress = d->stateMachine->checkedOverlayProgress();
  535. if (isCheckable() && progress > 0) {
  536. QColor source = foregroundColor();
  537. QColor dest = Qt::TransparentMode == d->bgMode ? Qt::white
  538. : backgroundColor();
  539. if (qFuzzyCompare(1, progress)) {
  540. painter->setPen(dest);
  541. } else {
  542. painter->setPen(QColor(COLOR_INTERPOLATE(red),
  543. COLOR_INTERPOLATE(green),
  544. COLOR_INTERPOLATE(blue),
  545. COLOR_INTERPOLATE(alpha)));
  546. }
  547. }
  548. } else {
  549. painter->setPen(disabledForegroundColor());
  550. }
  551. if (icon().isNull()) {
  552. if (Qt::AlignLeft == d->textAlignment) {
  553. painter->drawText(rect().adjusted(12, 0, 0, 0), Qt::AlignLeft | Qt::AlignVCenter, text());
  554. } else {
  555. painter->drawText(rect(), Qt::AlignCenter, text());
  556. }
  557. return;
  558. }
  559. QSize textSize(fontMetrics().size(Qt::TextSingleLine, text()));
  560. QSize base(size()-textSize);
  561. const int iw = iconSize().width() + IconPadding;
  562. QPoint pos(Qt::AlignLeft == d->textAlignment ? 12 : (base.width()-iw)/2, 0);
  563. QRect textGeometry(pos + QPoint(0, base.height()/2), textSize);
  564. QRect iconGeometry(pos + QPoint(0, (height()-iconSize().height())/2), iconSize());
  565. if (Material::LeftIcon == d->iconPlacement) {
  566. textGeometry.translate(iw, 0);
  567. } else {
  568. iconGeometry.translate(textSize.width() + IconPadding, 0);
  569. }
  570. painter->drawText(textGeometry, Qt::AlignCenter, text());
  571. QPixmap pixmap = icon().pixmap(iconSize());
  572. QPainter icon(&pixmap);
  573. icon.setCompositionMode(QPainter::CompositionMode_SourceIn);
  574. icon.fillRect(pixmap.rect(), painter->pen().color());
  575. painter->drawPixmap(iconGeometry, pixmap);
  576. }
  577. /*!
  578. * \internal
  579. */
  580. void QtMaterialFlatButton::updateClipPath()
  581. {
  582. Q_D(QtMaterialFlatButton);
  583. const qreal radius = d->cornerRadius;
  584. QPainterPath path;
  585. path.addRoundedRect(rect(), radius, radius);
  586. d->rippleOverlay->setClipPath(path);
  587. }