PickerBase.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. #include "PickerBase.h"
  2. #include "QFluentWidgets.h"
  3. #include "Widgets/CycleListWidget.h"
  4. #include "Widgets/Button.h"
  5. #include "Widgets/ScrollArea.h"
  6. #include <QPainter>
  7. #include <QApplication>
  8. #include <QScreen>
  9. #include <QDebug>
  10. ItemMaskWidget::ItemMaskWidget(const QList<CycleListWidget *> &list, QWidget *parent)
  11. : QWidget(parent), listWidgets(list)
  12. {
  13. setFixedHeight(37);
  14. FluentStyleSheet::apply("TIME_PICKER", this);
  15. }
  16. void ItemMaskWidget::paintEvent(QPaintEvent * /*event*/)
  17. {
  18. QPainter painter(this);
  19. painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
  20. // draw background
  21. painter.setPen(Qt::NoPen);
  22. painter.setBrush(themeColor());
  23. painter.drawRoundedRect(this->rect().adjusted(4, 0, -3, 0), 5, 5);
  24. // draw text
  25. if (QFWIns.isDarkTheme()) {
  26. painter.setPen(Qt::black);
  27. } else {
  28. painter.setPen(Qt::white);
  29. }
  30. painter.setFont(this->font());
  31. int w = 0;
  32. int h = this->height();
  33. for (auto p : listWidgets) {
  34. painter.save();
  35. // draw first item's text
  36. int x = p->itemSize().width() / 2 + 4 + this->x();
  37. QListWidgetItem *item1 = p->itemAt(QPoint(x, this->y() + 6));
  38. item1 = p->currentItem();
  39. if (!item1) {
  40. painter.restore();
  41. continue;
  42. }
  43. int iw = item1->sizeHint().width();
  44. int y = p->visualItemRect(item1).y();
  45. painter.translate(w, y - this->y() + 7);
  46. drawText(item1, &painter, 0);
  47. // draw second item's text
  48. QListWidgetItem *item2 = p->itemAt(this->pos() + QPoint(x, h - 6));
  49. drawText(item2, &painter, h);
  50. painter.restore();
  51. w += (iw + 8); // margin: 0 4px;
  52. }
  53. }
  54. void ItemMaskWidget::drawText(QListWidgetItem *item, QPainter *p, int y)
  55. {
  56. int align = item->textAlignment();
  57. int w = item->sizeHint().width();
  58. int h = item->sizeHint().height();
  59. QRectF rect;
  60. if (align & Qt::AlignLeft) {
  61. rect = QRectF(15, y, w, h); // padding-left: 11px
  62. } else if (align & Qt::AlignRight) {
  63. rect = QRectF(4, y, w - 15, h); // padding-right: 11px
  64. } else if (align & Qt::AlignCenter) {
  65. rect = QRectF(4, y, w, h);
  66. }
  67. p->drawText(rect, align, item->text());
  68. }
  69. PickerColumnButton::PickerColumnButton(const QString &name, const QVariantList &items, int width,
  70. Qt::AlignmentFlag align, PickerColumnFormatter *formatter, QWidget *parent)
  71. : QPushButton(parent), m_name(name), m_value("")
  72. {
  73. setItems(items);
  74. setAlignment(align);
  75. setFormatter(formatter);
  76. setFixedSize(width, 30);
  77. setObjectName("pickerButton");
  78. setProperty("hasBorder", false);
  79. setAttribute(Qt::WA_TransparentForMouseEvents);
  80. }
  81. QVariantList PickerColumnButton::items() const
  82. {
  83. QVariantList ret;
  84. for (const auto &item : m_items) {
  85. ret.append(m_formatter->encode(item));
  86. }
  87. return ret;
  88. }
  89. void PickerColumnButton::setItems(const QVariantList &items)
  90. {
  91. m_items = items;
  92. }
  93. QString PickerColumnButton::value() const
  94. {
  95. if (m_value.isEmpty()) {
  96. return m_value;
  97. }
  98. return m_formatter->encode(m_value);
  99. }
  100. void PickerColumnButton::setValue(const QString &value)
  101. {
  102. m_value = value;
  103. if (value.isEmpty()) {
  104. setText(this->name());
  105. setProperty("hasValue", false);
  106. } else {
  107. setText(this->value());
  108. setProperty("hasValue", true);
  109. }
  110. setStyle(QApplication::style());
  111. }
  112. Qt::AlignmentFlag PickerColumnButton::alignment() const
  113. {
  114. return m_align;
  115. }
  116. void PickerColumnButton::setAlignment(const Qt::AlignmentFlag &align)
  117. {
  118. if (align == Qt::AlignLeft) {
  119. setProperty("align", "left");
  120. } else if (align == Qt::AlignRight) {
  121. setProperty("align", "right");
  122. } else {
  123. setProperty("align", "center");
  124. }
  125. m_align = align;
  126. setStyle(QApplication::style());
  127. }
  128. PickerColumnFormatter *PickerColumnButton::formatter() const
  129. {
  130. return m_formatter;
  131. }
  132. void PickerColumnButton::setFormatter(PickerColumnFormatter *formatter)
  133. {
  134. if (!formatter) {
  135. m_formatter = new PickerColumnFormatter(this);
  136. } else {
  137. m_formatter = formatter;
  138. }
  139. }
  140. QString PickerColumnButton::name() const
  141. {
  142. return m_name;
  143. }
  144. void PickerColumnButton::setName(const QString &name)
  145. {
  146. if (this->text() == m_name) {
  147. this->setText(name);
  148. }
  149. m_name = name;
  150. }
  151. PickerBase::PickerBase(QWidget *parent) : QPushButton(parent)
  152. {
  153. hBoxLayout = new QHBoxLayout(this);
  154. hBoxLayout->setSpacing(0);
  155. hBoxLayout->setContentsMargins(0, 0, 0, 0);
  156. hBoxLayout->setSizeConstraint(QHBoxLayout::SetFixedSize);
  157. FluentStyleSheet::apply("TIME_PICKER", this);
  158. connect(this, &PickerBase::clicked, this, &PickerBase::showPanel);
  159. }
  160. void PickerBase::addColumn(const QString &name, const QVariantList &items, int width, Qt::AlignmentFlag align,
  161. PickerColumnFormatter *formatter)
  162. {
  163. PickerColumnButton *button = new PickerColumnButton(name, items, width, align, formatter, this);
  164. columns.append(button);
  165. hBoxLayout->addWidget(button, 0, Qt::AlignLeft);
  166. // update the style of buttons
  167. for (int i = 0; i < columns.count() - 1; ++i) {
  168. columns[i]->setProperty("hasBorder", true);
  169. columns[i]->setStyle(QApplication::style());
  170. }
  171. }
  172. #define checkColumnIndex(index) \
  173. do { \
  174. if (index < 0 || index >= this->columns.count()) \
  175. return; \
  176. } while (0)
  177. void PickerBase::setColumnAlignment(int index, Qt::AlignmentFlag align)
  178. {
  179. checkColumnIndex(index);
  180. columns[index]->setAlignment(align);
  181. }
  182. void PickerBase::setColumnWidth(int index, int width)
  183. {
  184. checkColumnIndex(index);
  185. columns[index]->setFixedWidth(width);
  186. }
  187. void PickerBase::setColumnTight(int index)
  188. {
  189. checkColumnIndex(index);
  190. QFontMetrics fm = fontMetrics();
  191. int w = -1;
  192. for (auto c : columns[index]->items()) {
  193. if (w < fm.width(c.toString())) {
  194. w = fm.width(c.toString());
  195. }
  196. }
  197. w += 30;
  198. setColumnWidth(index, w);
  199. }
  200. void PickerBase::setColumnVisible(int index, bool visible)
  201. {
  202. checkColumnIndex(index);
  203. columns[index]->setVisible(visible);
  204. }
  205. QStringList PickerBase::value() const
  206. {
  207. QStringList ret;
  208. for (auto c : columns) {
  209. if (c->isVisible()) {
  210. ret.append(c->value());
  211. }
  212. }
  213. return ret;
  214. }
  215. void PickerBase::setColumnValue(int index, const QString &value)
  216. {
  217. checkColumnIndex(index);
  218. columns[index]->setValue(value);
  219. }
  220. void PickerBase::setColumnFormatter(int index, PickerColumnFormatter *formatter)
  221. {
  222. checkColumnIndex(index);
  223. columns[index]->setFormatter(formatter);
  224. }
  225. void PickerBase::setColumnItems(int index, const QVariantList &items)
  226. {
  227. checkColumnIndex(index);
  228. columns[index]->setItems(items);
  229. }
  230. QVariant PickerBase::encodeValue(int index, const QVariant &value)
  231. {
  232. if (index < 0 || index >= this->columns.count())
  233. return QVariant();
  234. return columns.at(index)->formatter()->encode(value);
  235. }
  236. QVariant PickerBase::decodeValue(int index, const QVariant &value)
  237. {
  238. if (index < 0 || index >= this->columns.count())
  239. return QVariant();
  240. PickerColumnFormatter *f = columns.at(index)->formatter();
  241. QVariant v = f->decode(value);
  242. return columns.at(index)->formatter()->decode(value);
  243. }
  244. QStringList PickerBase::panelInitialValue()
  245. {
  246. return value();
  247. }
  248. void PickerBase::setColumn(int index, const QString &name, const QVariantList & /*items*/, int width,
  249. Qt::AlignmentFlag align)
  250. {
  251. checkColumnIndex(index);
  252. PickerColumnButton *button = columns.at(index);
  253. button->setText(name);
  254. button->setFixedWidth(width);
  255. button->setAlignment(align);
  256. }
  257. void PickerBase::clearColumns()
  258. {
  259. while (!columns.isEmpty()) {
  260. PickerColumnButton *btn = columns.takeFirst();
  261. hBoxLayout->removeWidget(btn);
  262. btn->deleteLater();
  263. }
  264. }
  265. void PickerBase::showPanel()
  266. {
  267. PickerPanel *panel = new PickerPanel(this);
  268. for (auto column : columns) {
  269. if (column->isVisible()) {
  270. panel->addColumn(column->items(), column->width(), column->alignment());
  271. }
  272. }
  273. panel->setValue(this->panelInitialValue());
  274. connect(panel, &PickerPanel::confirmed, this, &PickerBase::onConfirmed);
  275. connect(panel, &PickerPanel::columnValueChanged,
  276. [=](int index, const QString &value) { onColumnValueChanged(panel, index, value); });
  277. panel->exec(this->mapToGlobal(QPoint(0, -37 * 4)));
  278. }
  279. void PickerBase::onConfirmed(const QStringList &value)
  280. {
  281. for (int i = 0; i < value.count(); ++i) {
  282. setColumnValue(i, value.at(i));
  283. }
  284. }
  285. void PickerBase::onColumnValueChanged(PickerPanel *panel, int index, const QString &value)
  286. {
  287. Q_UNUSED(panel)
  288. Q_UNUSED(index)
  289. Q_UNUSED(value)
  290. }
  291. void PickerBase::mousePressEvent(QMouseEvent *event)
  292. {
  293. setButtonProperty("pressed", true);
  294. QPushButton::mousePressEvent(event);
  295. }
  296. void PickerBase::mouseReleaseEvent(QMouseEvent *event)
  297. {
  298. setButtonProperty("pressed", false);
  299. QPushButton::mouseReleaseEvent(event);
  300. }
  301. void PickerBase::enterEvent(QEvent * /*event*/)
  302. {
  303. setButtonProperty("enter", true);
  304. }
  305. void PickerBase::leaveEvent(QEvent * /*event*/)
  306. {
  307. setButtonProperty("enter", false);
  308. }
  309. void PickerBase::setButtonProperty(const char *name, const QVariant &value)
  310. {
  311. for (auto c : columns) {
  312. c->setProperty(name, value);
  313. c->setStyle(QApplication::style());
  314. }
  315. }
  316. PickerPanel::PickerPanel(QWidget *parent) : QWidget(parent)
  317. {
  318. m_itemHeight = 37;
  319. m_view = new QFrame(this);
  320. m_itemMaskWidget = new ItemMaskWidget(m_listWidgets, this);
  321. m_hSeparatorWidget = new SeparatorWidget(Qt::Horizontal, m_view);
  322. m_yesButton = new TransparentToolButton(NEWFLICON(FluentIcon, ACCEPT), m_view);
  323. m_cancelButton = new TransparentToolButton(NEWFLICON(FluentIcon, CLOSE), m_view);
  324. m_hBoxLayout = new QHBoxLayout(this);
  325. m_listLayout = new QHBoxLayout();
  326. m_buttonLayout = new QHBoxLayout();
  327. m_vBoxLayout = new QVBoxLayout(m_view);
  328. m_isExpanded = false;
  329. m_ani = nullptr;
  330. m_shadowEffect = nullptr;
  331. initWidget();
  332. }
  333. void PickerPanel::initWidget()
  334. {
  335. setWindowFlags(Qt::Popup | Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint);
  336. setAttribute(Qt::WA_TranslucentBackground);
  337. setShadowEffect();
  338. m_yesButton->setIconSize(QSize(16, 16));
  339. m_cancelButton->setIconSize(QSize(13, 13));
  340. m_yesButton->setFixedHeight(33);
  341. m_cancelButton->setFixedHeight(33);
  342. m_hBoxLayout->setContentsMargins(12, 8, 12, 20);
  343. m_hBoxLayout->addWidget(m_view, 1, Qt::AlignCenter);
  344. m_hBoxLayout->setSizeConstraint(QHBoxLayout::SetMinimumSize);
  345. m_vBoxLayout->setSpacing(0);
  346. m_vBoxLayout->setContentsMargins(0, 0, 0, 0);
  347. m_vBoxLayout->addLayout(m_listLayout, 1);
  348. m_vBoxLayout->addWidget(m_hSeparatorWidget);
  349. m_vBoxLayout->addLayout(m_buttonLayout, 1);
  350. m_vBoxLayout->setSizeConstraint(QVBoxLayout::SetMinimumSize);
  351. m_buttonLayout->setSpacing(6);
  352. m_buttonLayout->setContentsMargins(3, 3, 3, 3);
  353. m_buttonLayout->addWidget(m_yesButton);
  354. m_buttonLayout->addWidget(m_cancelButton);
  355. m_yesButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
  356. m_cancelButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
  357. connect(m_yesButton, &TransparentToolButton::clicked, this, &PickerPanel::fadeOut);
  358. connect(m_yesButton, &TransparentToolButton::clicked, [this]() { emit confirmed(this->value()); });
  359. connect(m_cancelButton, &TransparentToolButton::clicked, this, &PickerPanel::fadeOut);
  360. m_view->setObjectName("view");
  361. FluentStyleSheet::apply("TIME_PICKER", this);
  362. }
  363. void PickerPanel::setShadowEffect(int blurRadius, const QPointF &offset, const QColor &color)
  364. {
  365. m_shadowEffect = new QGraphicsDropShadowEffect(m_view);
  366. m_shadowEffect->setBlurRadius(blurRadius);
  367. m_shadowEffect->setOffset(offset);
  368. m_shadowEffect->setColor(color);
  369. m_view->setGraphicsEffect(nullptr);
  370. m_view->setGraphicsEffect(m_shadowEffect);
  371. }
  372. void PickerPanel::addColumn(const QVariantList &items, int width, Qt::AlignmentFlag align)
  373. {
  374. if (!m_listWidgets.isEmpty()) {
  375. m_listLayout->addWidget(new SeparatorWidget(Qt::Vertical, m_view));
  376. }
  377. CycleListWidget *w = new CycleListWidget(items, QSize(width, m_itemHeight), align, this);
  378. connect(w->vScrollBar, &SmoothScrollBar::valueChanged, m_itemMaskWidget, QOverload<>::of(&ItemMaskWidget::update));
  379. const int N = m_listWidgets.count();
  380. connect(w, &CycleListWidget::currentItemChanged,
  381. [N, this](QListWidgetItem *item) { emit columnValueChanged(N, item->text()); });
  382. m_listWidgets.append(w);
  383. m_itemMaskWidget->listWidgets = m_listWidgets;
  384. m_listLayout->addWidget(w);
  385. }
  386. /// return the value of columns
  387. QStringList PickerPanel::value() const
  388. {
  389. QStringList ret;
  390. for (auto w : m_listWidgets) {
  391. ret << w->currentItem()->text();
  392. }
  393. return ret;
  394. }
  395. /// set the value of columns
  396. void PickerPanel::setValue(const QStringList &value)
  397. {
  398. if (value.count() != m_listWidgets.count()) {
  399. return;
  400. }
  401. for (int i = 0; i < value.count(); ++i) {
  402. m_listWidgets.at(i)->setSelectedItem(value.at(i));
  403. }
  404. }
  405. QString PickerPanel::columnValue(int index) const
  406. {
  407. if (index < 0 || index >= m_listWidgets.count()) {
  408. return "";
  409. }
  410. CycleListWidget *w = m_listWidgets.at(index);
  411. QString s = w->currentItem()->text();
  412. return m_listWidgets.at(index)->currentItem()->text();
  413. }
  414. void PickerPanel::setColumnValue(int index, const QString &value)
  415. {
  416. if (index < 0 || index >= m_listWidgets.count()) {
  417. return;
  418. }
  419. m_listWidgets.at(index)->setSelectedItem(value);
  420. }
  421. CycleListWidget *PickerPanel::column(int index) const
  422. {
  423. if (index < 0 || index >= m_listWidgets.count()) {
  424. return nullptr;
  425. }
  426. return m_listWidgets.at(index);
  427. }
  428. void PickerPanel::exec(const QPoint &pos, bool ani)
  429. {
  430. if (isVisible()) {
  431. return;
  432. }
  433. // show before running animation, or the height calculation will be wrong
  434. this->show();
  435. QRect rect = QApplication::screenAt(QCursor::pos())->availableGeometry();
  436. int w = this->width() + 5;
  437. int h = this->height();
  438. QPoint newPos = pos;
  439. newPos.setX(qMin(pos.x() - this->layout()->contentsMargins().left(), rect.right() - w));
  440. newPos.setY(qMax(rect.top(), qMin(pos.y() - 4, rect.bottom() - h + 5)));
  441. this->move(newPos);
  442. if (!ani) {
  443. return;
  444. }
  445. m_isExpanded = false;
  446. m_ani = new QPropertyAnimation(m_view, "windowOpacity", this);
  447. connect(m_ani, &QPropertyAnimation::valueChanged, this, &PickerPanel::onAniValueChanged);
  448. m_ani->setStartValue(0);
  449. m_ani->setEndValue(1);
  450. m_ani->setDuration(150);
  451. m_ani->setEasingCurve(QEasingCurve::OutQuad);
  452. m_ani->start();
  453. }
  454. void PickerPanel::resizeEvent(QResizeEvent * /*event*/)
  455. {
  456. m_itemMaskWidget->resize(m_view->width() - 3, m_itemHeight);
  457. QMargins m = m_hBoxLayout->contentsMargins();
  458. m_itemMaskWidget->move(m.left() + 2, m.top() + 148);
  459. }
  460. void PickerPanel::fadeOut()
  461. {
  462. m_isExpanded = true;
  463. m_ani = new QPropertyAnimation(this, "windowOpacity", this);
  464. connect(m_ani, &QPropertyAnimation::valueChanged, this, &PickerPanel::onAniValueChanged);
  465. connect(m_ani, &QPropertyAnimation::finished, this, &PickerPanel::deleteLater);
  466. m_ani->setStartValue(1);
  467. m_ani->setEndValue(0);
  468. m_ani->setDuration(150);
  469. m_ani->setEasingCurve(QEasingCurve::OutQuad);
  470. m_ani->start();
  471. }
  472. void PickerPanel::onAniValueChanged(const QVariant &value)
  473. {
  474. QMargins m = this->layout()->contentsMargins();
  475. int w = m_view->width() + m.left() + m.right() + 120;
  476. int h = m_view->height() + m.top() + m.bottom() + 12;
  477. double opacity = value.toDouble();
  478. if (!m_isExpanded) {
  479. int y = int(h / 2 * (1 - opacity));
  480. setMask(QRegion(0, y, w, h - y * 2));
  481. } else {
  482. int y = int(h / 3 * (1 - opacity));
  483. setMask(QRegion(0, y, w, h - y * 2));
  484. }
  485. }