#ifndef PROPERTY_WIDGETS_H #define PROPERTY_WIDGETS_H #include class QPushButton; class QSlider; #include #include #include #include #include #include #include #include #include #include "Core/Property.h" #include "Core/Object.h" #include "Core/Signal.h" #include "Math/Dense.h" #include "Settings.h" namespace uLib { namespace Qt { double parseWithUnits(const QString& text, double* factorOut = nullptr, QString* suffixOut = nullptr); class PropertyWidgetBase : public QWidget { Q_OBJECT public: PropertyWidgetBase(PropertyBase* prop, QWidget* parent = nullptr); virtual ~PropertyWidgetBase(); PropertyBase* getProperty() const { return m_BaseProperty; } protected: PropertyBase* m_BaseProperty; QHBoxLayout* m_Layout; QLabel* m_Label; // Stores the uLib signal connection so it can be disconnected on destruction, // preventing use-after-free when PropertyEditor::clear() deletes widgets. Connection m_Connection; }; class UnitLineEdit : public QLineEdit { Q_OBJECT public: UnitLineEdit(QWidget* parent = nullptr); void setValue(double val); void setUnits(const QString& suffix, double factor = 1.0); double getValue() const { return m_Value; } void setIntegerOnly(bool b); signals: void valueManualChanged(double val); private slots: void onEditingFinished(); private: void updateText(); double m_Value; double m_Factor; QString m_Suffix; bool m_IsInteger; }; class DoublePropertyWidget : public PropertyWidgetBase { Q_OBJECT public: DoublePropertyWidget(Property* prop, QWidget* parent = nullptr); private: Property* m_Prop; UnitLineEdit* m_Edit; }; class FloatPropertyWidget : public PropertyWidgetBase { Q_OBJECT public: FloatPropertyWidget(Property* prop, QWidget* parent = nullptr); private: Property* m_Prop; UnitLineEdit* m_Edit; }; class IntPropertyWidget : public PropertyWidgetBase { Q_OBJECT public: IntPropertyWidget(Property* prop, QWidget* parent = nullptr); private: Property* m_Prop; UnitLineEdit* m_Edit; }; template class VectorPropertyWidget : public PropertyWidgetBase { public: VectorPropertyWidget(Property* prop, QWidget* parent = nullptr) : PropertyWidgetBase(prop, parent), m_Prop(prop) { std::string unit = prop->GetUnits(); double factor = 1.0; QString prefSuffix; if (!unit.empty()) { auto dim = Settings::Instance().IdentifyDimension(unit); std::string pref = Settings::Instance().GetPreferredUnit(dim); factor = Settings::Instance().GetUnitFactor(pref); prefSuffix = QString::fromStdString(pref); } for (int i = 0; i < Size; ++i) { m_Edits[i] = new UnitLineEdit(this); if (std::is_integral::value) { m_Edits[i]->setIntegerOnly(true); } if (!prefSuffix.isEmpty()) { m_Edits[i]->setUnits(prefSuffix, factor); } m_Layout->addWidget(m_Edits[i], 1); connect(m_Edits[i], &UnitLineEdit::valueManualChanged, [this, i](double val){ VecT v = m_Prop->Get(); v(i) = (typename VecT::Scalar)val; if (m_Prop->Get() != v) m_Prop->Set(v); }); } updateEdits(); m_Connection = uLib::Object::connect(m_Prop, &Property::PropertyChanged, [this](){ updateEdits(); }); } ~VectorPropertyWidget() { m_Connection.disconnect(); } private: void updateEdits() { VecT v = m_Prop->Get(); for (int i = 0; i < Size; ++i) { if (!m_Edits[i]->hasFocus()) { m_Edits[i]->setValue((double)v(i)); } } } Property* m_Prop; UnitLineEdit* m_Edits[Size]; }; class RangePropertyWidget : public PropertyWidgetBase { Q_OBJECT public: RangePropertyWidget(Property* prop, QWidget* parent = nullptr); virtual ~RangePropertyWidget(); private slots: void onSliderChanged(int val); private: void updateUi(); Property* m_Prop; QSlider* m_Slider; UnitLineEdit* m_Edit; }; class BoolPropertyWidget : public PropertyWidgetBase { Q_OBJECT public: BoolPropertyWidget(Property* prop, QWidget* parent = nullptr); virtual ~BoolPropertyWidget(); private: Property* m_Prop; QCheckBox* m_CheckBox; }; class ColorPropertyWidget : public PropertyWidgetBase { Q_OBJECT public: ColorPropertyWidget(Property* prop, QWidget* parent = nullptr); virtual ~ColorPropertyWidget(); private slots: void onClicked(); private: void updateButtonColor(); Property* m_Prop; QPushButton* m_Button; }; class StringPropertyWidget : public PropertyWidgetBase { Q_OBJECT public: StringPropertyWidget(Property* prop, QWidget* parent = nullptr); virtual ~StringPropertyWidget(); private: Property* m_Prop; QLineEdit* m_LineEdit; }; class PropertyEditor : public QWidget { Q_OBJECT public: PropertyEditor(QWidget* parent = nullptr); virtual ~PropertyEditor(); void setObject(uLib::Object* obj, bool displayOnly = false); template void registerFactory(std::function factory) { m_Factories[std::type_index(typeid(T))] = factory; } private: void clear(); uLib::Object* m_Object; QVBoxLayout* m_MainLayout; QScrollArea* m_ScrollArea; QWidget* m_Container; QVBoxLayout* m_ContainerLayout; std::map> m_Factories; }; } // namespace Qt } // namespace uLib #endif // PROPERTY_WIDGETS_H