#pragma once #include #include #include #include #include #include #include #include #include "common/params.h" #include "selfdrive/ui/qt/widgets/input.h" #include "selfdrive/ui/qt/widgets/toggle.h" class ElidedLabel : public QLabel { Q_OBJECT public: explicit ElidedLabel(QWidget *parent = 0); explicit ElidedLabel(const QString &text, QWidget *parent = 0); signals: void clicked(); protected: void paintEvent(QPaintEvent *event) override; void resizeEvent(QResizeEvent* event) override; void mouseReleaseEvent(QMouseEvent *event) override { if (rect().contains(event->pos())) { emit clicked(); } } QString lastText_, elidedText_; }; class AbstractControl : public QFrame { Q_OBJECT public: void setDescription(const QString &desc) { if (description) description->setText(desc); } void setTitle(const QString &title) { title_label->setText(title); } void setValue(const QString &val) { value->setText(val); } const QString getDescription() { return description->text(); } QLabel *icon_label; QPixmap icon_pixmap; public slots: void showDescription() { description->setVisible(true); } signals: void showDescriptionEvent(); protected: AbstractControl(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr); void hideEvent(QHideEvent *e) override; QHBoxLayout *hlayout; QPushButton *title_label; private: ElidedLabel *value; QLabel *description = nullptr; }; // widget to display a value class LabelControl : public AbstractControl { Q_OBJECT public: LabelControl(const QString &title, const QString &text = "", const QString &desc = "", QWidget *parent = nullptr) : AbstractControl(title, desc, "", parent) { label.setText(text); label.setAlignment(Qt::AlignRight | Qt::AlignVCenter); hlayout->addWidget(&label); } void setText(const QString &text) { label.setText(text); } private: ElidedLabel label; }; // widget for a button with a label class ButtonControl : public AbstractControl { Q_OBJECT public: ButtonControl(const QString &title, const QString &text, const QString &desc = "", QWidget *parent = nullptr); inline void setText(const QString &text) { btn.setText(text); } inline QString text() const { return btn.text(); } signals: void clicked(); public slots: void setEnabled(bool enabled) { btn.setEnabled(enabled); } private: QPushButton btn; }; class ToggleControl : public AbstractControl { Q_OBJECT public: ToggleControl(const QString &title, const QString &desc = "", const QString &icon = "", const bool state = false, QWidget *parent = nullptr) : AbstractControl(title, desc, icon, parent) { toggle.setFixedSize(150, 100); if (state) { toggle.togglePosition(); } hlayout->addWidget(&toggle); QObject::connect(&toggle, &Toggle::stateChanged, this, &ToggleControl::toggleFlipped); } void setEnabled(bool enabled) { toggle.setEnabled(enabled); toggle.update(); } signals: void toggleFlipped(bool state); protected: Toggle toggle; }; // widget to toggle params class ParamControl : public ToggleControl { Q_OBJECT public: ParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr) : ToggleControl(title, desc, icon, false, parent) { key = param.toStdString(); QObject::connect(this, &ParamControl::toggleFlipped, [=](bool state) { QString content("

" + title + "


" "

" + getDescription() + "

"); ConfirmationDialog dialog(content, tr("Enable"), tr("Cancel"), true, this); bool confirmed = store_confirm && params.getBool(key + "Confirmed"); if (!confirm || confirmed || !state || dialog.exec()) { if (store_confirm && state) params.putBool(key + "Confirmed", true); params.putBool(key, state); setIcon(state); } else { toggle.togglePosition(); } }); } void setConfirmation(bool _confirm, bool _store_confirm) { confirm = _confirm; store_confirm = _store_confirm; } void setActiveIcon(const QString &icon) { active_icon_pixmap = QPixmap(icon).scaledToWidth(80, Qt::SmoothTransformation); } void refresh() { bool state = params.getBool(key); if (state != toggle.on) { toggle.togglePosition(); setIcon(state); } } void showEvent(QShowEvent *event) override { refresh(); } private: void setIcon(bool state) { if (state && !active_icon_pixmap.isNull()) { icon_label->setPixmap(active_icon_pixmap); } else if (!icon_pixmap.isNull()) { icon_label->setPixmap(icon_pixmap); } } std::string key; Params params; QPixmap active_icon_pixmap; bool confirm = false; bool store_confirm = false; }; class ButtonParamControl : public AbstractControl { Q_OBJECT public: ButtonParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, const std::vector &button_texts, const int minimum_button_width = 225) : AbstractControl(title, desc, icon) { const QString style = R"( QPushButton { border-radius: 50px; font-size: 40px; font-weight: 500; height:100px; padding: 0 25 0 25; color: #E4E4E4; background-color: #393939; } QPushButton:pressed { background-color: #4a4a4a; } QPushButton:checked:enabled { background-color: #33Ab4C; } QPushButton:disabled { color: #33E4E4E4; } )"; key = param.toStdString(); int value = atoi(params.get(key).c_str()); button_group = new QButtonGroup(this); button_group->setExclusive(true); for (int i = 0; i < button_texts.size(); i++) { QPushButton *button = new QPushButton(button_texts[i], this); button->setCheckable(true); button->setChecked(i == value); button->setStyleSheet(style); button->setMinimumWidth(minimum_button_width); hlayout->addWidget(button); button_group->addButton(button, i); } QObject::connect(button_group, QOverload::of(&QButtonGroup::buttonToggled), [=](int id, bool checked) { if (checked) { params.put(key, std::to_string(id)); } }); } void setEnabled(bool enable) { for (auto btn : button_group->buttons()) { btn->setEnabled(enable); } } private: std::string key; Params params; QButtonGroup *button_group; }; class ListWidget : public QWidget { Q_OBJECT public: explicit ListWidget(QWidget *parent = 0) : QWidget(parent), outer_layout(this) { outer_layout.setMargin(0); outer_layout.setSpacing(0); outer_layout.addLayout(&inner_layout); inner_layout.setMargin(0); inner_layout.setSpacing(25); // default spacing is 25 outer_layout.addStretch(); } inline void addItem(QWidget *w) { inner_layout.addWidget(w); } inline void addItem(QLayout *layout) { inner_layout.addLayout(layout); } inline void setSpacing(int spacing) { inner_layout.setSpacing(spacing); } private: void paintEvent(QPaintEvent *) override { QPainter p(this); p.setPen(Qt::gray); for (int i = 0; i < inner_layout.count() - 1; ++i) { QWidget *widget = inner_layout.itemAt(i)->widget(); if (widget == nullptr || widget->isVisible()) { QRect r = inner_layout.itemAt(i)->geometry(); int bottom = r.bottom() + inner_layout.spacing() / 2; p.drawLine(r.left() + 40, bottom, r.right() - 40, bottom); } } } QVBoxLayout outer_layout; QVBoxLayout inner_layout; }; // convenience class for wrapping layouts class LayoutWidget : public QWidget { Q_OBJECT public: LayoutWidget(QLayout *l, QWidget *parent = nullptr) : QWidget(parent) { setLayout(l); } };