#include "PickerBase.h" #include "QFluentWidgets.h" #include "Widgets/CycleListWidget.h" #include "Widgets/Button.h" #include "Widgets/ScrollArea.h" #include #include #include #include ItemMaskWidget::ItemMaskWidget(const QList &list, QWidget *parent) : QWidget(parent), listWidgets(list) { setFixedHeight(37); FluentStyleSheet::apply("TIME_PICKER", this); } void ItemMaskWidget::paintEvent(QPaintEvent * /*event*/) { QPainter painter(this); painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing); // draw background painter.setPen(Qt::NoPen); painter.setBrush(themeColor()); painter.drawRoundedRect(this->rect().adjusted(4, 0, -3, 0), 5, 5); // draw text if (QFWIns.isDarkTheme()) { painter.setPen(Qt::black); } else { painter.setPen(Qt::white); } painter.setFont(this->font()); int w = 0; int h = this->height(); for (auto p : listWidgets) { painter.save(); // draw first item's text int x = p->itemSize().width() / 2 + 4 + this->x(); QListWidgetItem *item1 = p->itemAt(QPoint(x, this->y() + 6)); item1 = p->currentItem(); if (!item1) { painter.restore(); continue; } int iw = item1->sizeHint().width(); int y = p->visualItemRect(item1).y(); painter.translate(w, y - this->y() + 7); drawText(item1, &painter, 0); // draw second item's text QListWidgetItem *item2 = p->itemAt(this->pos() + QPoint(x, h - 6)); drawText(item2, &painter, h); painter.restore(); w += (iw + 8); // margin: 0 4px; } } void ItemMaskWidget::drawText(QListWidgetItem *item, QPainter *p, int y) { int align = item->textAlignment(); int w = item->sizeHint().width(); int h = item->sizeHint().height(); QRectF rect; if (align & Qt::AlignLeft) { rect = QRectF(15, y, w, h); // padding-left: 11px } else if (align & Qt::AlignRight) { rect = QRectF(4, y, w - 15, h); // padding-right: 11px } else if (align & Qt::AlignCenter) { rect = QRectF(4, y, w, h); } p->drawText(rect, align, item->text()); } PickerColumnButton::PickerColumnButton(const QString &name, const QVariantList &items, int width, Qt::AlignmentFlag align, PickerColumnFormatter *formatter, QWidget *parent) : QPushButton(parent), m_name(name), m_value("") { setItems(items); setAlignment(align); setFormatter(formatter); setFixedSize(width, 30); setObjectName("pickerButton"); setProperty("hasBorder", false); setAttribute(Qt::WA_TransparentForMouseEvents); } QVariantList PickerColumnButton::items() const { QVariantList ret; for (const auto &item : m_items) { ret.append(m_formatter->encode(item)); } return ret; } void PickerColumnButton::setItems(const QVariantList &items) { m_items = items; } QString PickerColumnButton::value() const { if (m_value.isEmpty()) { return m_value; } return m_formatter->encode(m_value); } void PickerColumnButton::setValue(const QString &value) { m_value = value; if (value.isEmpty()) { setText(this->name()); setProperty("hasValue", false); } else { setText(this->value()); setProperty("hasValue", true); } setStyle(QApplication::style()); } Qt::AlignmentFlag PickerColumnButton::alignment() const { return m_align; } void PickerColumnButton::setAlignment(const Qt::AlignmentFlag &align) { if (align == Qt::AlignLeft) { setProperty("align", "left"); } else if (align == Qt::AlignRight) { setProperty("align", "right"); } else { setProperty("align", "center"); } m_align = align; setStyle(QApplication::style()); } PickerColumnFormatter *PickerColumnButton::formatter() const { return m_formatter; } void PickerColumnButton::setFormatter(PickerColumnFormatter *formatter) { if (!formatter) { m_formatter = new PickerColumnFormatter(this); } else { m_formatter = formatter; } } QString PickerColumnButton::name() const { return m_name; } void PickerColumnButton::setName(const QString &name) { if (this->text() == m_name) { this->setText(name); } m_name = name; } PickerBase::PickerBase(QWidget *parent) : QPushButton(parent) { hBoxLayout = new QHBoxLayout(this); hBoxLayout->setSpacing(0); hBoxLayout->setContentsMargins(0, 0, 0, 0); hBoxLayout->setSizeConstraint(QHBoxLayout::SetFixedSize); FluentStyleSheet::apply("TIME_PICKER", this); connect(this, &PickerBase::clicked, this, &PickerBase::showPanel); } void PickerBase::addColumn(const QString &name, const QVariantList &items, int width, Qt::AlignmentFlag align, PickerColumnFormatter *formatter) { PickerColumnButton *button = new PickerColumnButton(name, items, width, align, formatter, this); columns.append(button); hBoxLayout->addWidget(button, 0, Qt::AlignLeft); // update the style of buttons for (int i = 0; i < columns.count() - 1; ++i) { columns[i]->setProperty("hasBorder", true); columns[i]->setStyle(QApplication::style()); } } #define checkColumnIndex(index) \ do { \ if (index < 0 || index >= this->columns.count()) \ return; \ } while (0) void PickerBase::setColumnAlignment(int index, Qt::AlignmentFlag align) { checkColumnIndex(index); columns[index]->setAlignment(align); } void PickerBase::setColumnWidth(int index, int width) { checkColumnIndex(index); columns[index]->setFixedWidth(width); } void PickerBase::setColumnTight(int index) { checkColumnIndex(index); QFontMetrics fm = fontMetrics(); int w = -1; for (auto c : columns[index]->items()) { if (w < fm.width(c.toString())) { w = fm.width(c.toString()); } } w += 30; setColumnWidth(index, w); } void PickerBase::setColumnVisible(int index, bool visible) { checkColumnIndex(index); columns[index]->setVisible(visible); } QStringList PickerBase::value() const { QStringList ret; for (auto c : columns) { if (c->isVisible()) { ret.append(c->value()); } } return ret; } void PickerBase::setColumnValue(int index, const QString &value) { checkColumnIndex(index); columns[index]->setValue(value); } void PickerBase::setColumnFormatter(int index, PickerColumnFormatter *formatter) { checkColumnIndex(index); columns[index]->setFormatter(formatter); } void PickerBase::setColumnItems(int index, const QVariantList &items) { checkColumnIndex(index); columns[index]->setItems(items); } QVariant PickerBase::encodeValue(int index, const QVariant &value) { if (index < 0 || index >= this->columns.count()) return QVariant(); return columns.at(index)->formatter()->encode(value); } QVariant PickerBase::decodeValue(int index, const QVariant &value) { if (index < 0 || index >= this->columns.count()) return QVariant(); PickerColumnFormatter *f = columns.at(index)->formatter(); QVariant v = f->decode(value); return columns.at(index)->formatter()->decode(value); } QStringList PickerBase::panelInitialValue() { return value(); } void PickerBase::setColumn(int index, const QString &name, const QVariantList & /*items*/, int width, Qt::AlignmentFlag align) { checkColumnIndex(index); PickerColumnButton *button = columns.at(index); button->setText(name); button->setFixedWidth(width); button->setAlignment(align); } void PickerBase::clearColumns() { while (!columns.isEmpty()) { PickerColumnButton *btn = columns.takeFirst(); hBoxLayout->removeWidget(btn); btn->deleteLater(); } } void PickerBase::showPanel() { PickerPanel *panel = new PickerPanel(this); for (auto column : columns) { if (column->isVisible()) { panel->addColumn(column->items(), column->width(), column->alignment()); } } panel->setValue(this->panelInitialValue()); connect(panel, &PickerPanel::confirmed, this, &PickerBase::onConfirmed); connect(panel, &PickerPanel::columnValueChanged, [=](int index, const QString &value) { onColumnValueChanged(panel, index, value); }); panel->exec(this->mapToGlobal(QPoint(0, -37 * 4))); } void PickerBase::onConfirmed(const QStringList &value) { for (int i = 0; i < value.count(); ++i) { setColumnValue(i, value.at(i)); } } void PickerBase::onColumnValueChanged(PickerPanel *panel, int index, const QString &value) { Q_UNUSED(panel) Q_UNUSED(index) Q_UNUSED(value) } void PickerBase::mousePressEvent(QMouseEvent *event) { setButtonProperty("pressed", true); QPushButton::mousePressEvent(event); } void PickerBase::mouseReleaseEvent(QMouseEvent *event) { setButtonProperty("pressed", false); QPushButton::mouseReleaseEvent(event); } void PickerBase::enterEvent(QEvent * /*event*/) { setButtonProperty("enter", true); } void PickerBase::leaveEvent(QEvent * /*event*/) { setButtonProperty("enter", false); } void PickerBase::setButtonProperty(const char *name, const QVariant &value) { for (auto c : columns) { c->setProperty(name, value); c->setStyle(QApplication::style()); } } PickerPanel::PickerPanel(QWidget *parent) : QWidget(parent) { m_itemHeight = 37; m_view = new QFrame(this); m_itemMaskWidget = new ItemMaskWidget(m_listWidgets, this); m_hSeparatorWidget = new SeparatorWidget(Qt::Horizontal, m_view); m_yesButton = new TransparentToolButton(NEWFLICON(FluentIcon, ACCEPT), m_view); m_cancelButton = new TransparentToolButton(NEWFLICON(FluentIcon, CLOSE), m_view); m_hBoxLayout = new QHBoxLayout(this); m_listLayout = new QHBoxLayout(); m_buttonLayout = new QHBoxLayout(); m_vBoxLayout = new QVBoxLayout(m_view); m_isExpanded = false; m_ani = nullptr; m_shadowEffect = nullptr; initWidget(); } void PickerPanel::initWidget() { setWindowFlags(Qt::Popup | Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint); setAttribute(Qt::WA_TranslucentBackground); setShadowEffect(); m_yesButton->setIconSize(QSize(16, 16)); m_cancelButton->setIconSize(QSize(13, 13)); m_yesButton->setFixedHeight(33); m_cancelButton->setFixedHeight(33); m_hBoxLayout->setContentsMargins(12, 8, 12, 20); m_hBoxLayout->addWidget(m_view, 1, Qt::AlignCenter); m_hBoxLayout->setSizeConstraint(QHBoxLayout::SetMinimumSize); m_vBoxLayout->setSpacing(0); m_vBoxLayout->setContentsMargins(0, 0, 0, 0); m_vBoxLayout->addLayout(m_listLayout, 1); m_vBoxLayout->addWidget(m_hSeparatorWidget); m_vBoxLayout->addLayout(m_buttonLayout, 1); m_vBoxLayout->setSizeConstraint(QVBoxLayout::SetMinimumSize); m_buttonLayout->setSpacing(6); m_buttonLayout->setContentsMargins(3, 3, 3, 3); m_buttonLayout->addWidget(m_yesButton); m_buttonLayout->addWidget(m_cancelButton); m_yesButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_cancelButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); connect(m_yesButton, &TransparentToolButton::clicked, this, &PickerPanel::fadeOut); connect(m_yesButton, &TransparentToolButton::clicked, [this]() { emit confirmed(this->value()); }); connect(m_cancelButton, &TransparentToolButton::clicked, this, &PickerPanel::fadeOut); m_view->setObjectName("view"); FluentStyleSheet::apply("TIME_PICKER", this); } void PickerPanel::setShadowEffect(int blurRadius, const QPointF &offset, const QColor &color) { m_shadowEffect = new QGraphicsDropShadowEffect(m_view); m_shadowEffect->setBlurRadius(blurRadius); m_shadowEffect->setOffset(offset); m_shadowEffect->setColor(color); m_view->setGraphicsEffect(nullptr); m_view->setGraphicsEffect(m_shadowEffect); } void PickerPanel::addColumn(const QVariantList &items, int width, Qt::AlignmentFlag align) { if (!m_listWidgets.isEmpty()) { m_listLayout->addWidget(new SeparatorWidget(Qt::Vertical, m_view)); } CycleListWidget *w = new CycleListWidget(items, QSize(width, m_itemHeight), align, this); connect(w->vScrollBar, &SmoothScrollBar::valueChanged, m_itemMaskWidget, QOverload<>::of(&ItemMaskWidget::update)); const int N = m_listWidgets.count(); connect(w, &CycleListWidget::currentItemChanged, [N, this](QListWidgetItem *item) { emit columnValueChanged(N, item->text()); }); m_listWidgets.append(w); m_itemMaskWidget->listWidgets = m_listWidgets; m_listLayout->addWidget(w); } /// return the value of columns QStringList PickerPanel::value() const { QStringList ret; for (auto w : m_listWidgets) { ret << w->currentItem()->text(); } return ret; } /// set the value of columns void PickerPanel::setValue(const QStringList &value) { if (value.count() != m_listWidgets.count()) { return; } for (int i = 0; i < value.count(); ++i) { m_listWidgets.at(i)->setSelectedItem(value.at(i)); } } QString PickerPanel::columnValue(int index) const { if (index < 0 || index >= m_listWidgets.count()) { return ""; } CycleListWidget *w = m_listWidgets.at(index); QString s = w->currentItem()->text(); return m_listWidgets.at(index)->currentItem()->text(); } void PickerPanel::setColumnValue(int index, const QString &value) { if (index < 0 || index >= m_listWidgets.count()) { return; } m_listWidgets.at(index)->setSelectedItem(value); } CycleListWidget *PickerPanel::column(int index) const { if (index < 0 || index >= m_listWidgets.count()) { return nullptr; } return m_listWidgets.at(index); } void PickerPanel::exec(const QPoint &pos, bool ani) { if (isVisible()) { return; } // show before running animation, or the height calculation will be wrong this->show(); QRect rect = QApplication::screenAt(QCursor::pos())->availableGeometry(); int w = this->width() + 5; int h = this->height(); QPoint newPos = pos; newPos.setX(qMin(pos.x() - this->layout()->contentsMargins().left(), rect.right() - w)); newPos.setY(qMax(rect.top(), qMin(pos.y() - 4, rect.bottom() - h + 5))); this->move(newPos); if (!ani) { return; } m_isExpanded = false; m_ani = new QPropertyAnimation(m_view, "windowOpacity", this); connect(m_ani, &QPropertyAnimation::valueChanged, this, &PickerPanel::onAniValueChanged); m_ani->setStartValue(0); m_ani->setEndValue(1); m_ani->setDuration(150); m_ani->setEasingCurve(QEasingCurve::OutQuad); m_ani->start(); } void PickerPanel::resizeEvent(QResizeEvent * /*event*/) { m_itemMaskWidget->resize(m_view->width() - 3, m_itemHeight); QMargins m = m_hBoxLayout->contentsMargins(); m_itemMaskWidget->move(m.left() + 2, m.top() + 148); } void PickerPanel::fadeOut() { m_isExpanded = true; m_ani = new QPropertyAnimation(this, "windowOpacity", this); connect(m_ani, &QPropertyAnimation::valueChanged, this, &PickerPanel::onAniValueChanged); connect(m_ani, &QPropertyAnimation::finished, this, &PickerPanel::deleteLater); m_ani->setStartValue(1); m_ani->setEndValue(0); m_ani->setDuration(150); m_ani->setEasingCurve(QEasingCurve::OutQuad); m_ani->start(); } void PickerPanel::onAniValueChanged(const QVariant &value) { QMargins m = this->layout()->contentsMargins(); int w = m_view->width() + m.left() + m.right() + 120; int h = m_view->height() + m.top() + m.bottom() + 12; double opacity = value.toDouble(); if (!m_isExpanded) { int y = int(h / 2 * (1 - opacity)); setMask(QRegion(0, y, w, h - y * 2)); } else { int y = int(h / 3 * (1 - opacity)); setMask(QRegion(0, y, w, h - y * 2)); } }