From bdaf7752e911cb9d40ac527dc4bc1af43a7ef7bb Mon Sep 17 00:00:00 2001 From: Benjamin Kraft Date: Sat, 9 Sep 2023 13:33:22 +0200 Subject: [PATCH] segment ui --- src/GLWidget.cpp | 10 ++ src/GLWidget.h | 7 ++ src/MainWindow.cpp | 231 +++++++++++++++++++++++++++++++++++++++++++++ src/MainWindow.h | 40 ++++++++ src/Pendulum.cpp | 1 + src/Pendulum.h | 3 + src/Simulation.cpp | 3 + src/Simulation.h | 18 ++++ src/Slider.cpp | 2 + src/Slider.h | 37 ++++++++ src/main.cpp | 12 ++- 11 files changed, 361 insertions(+), 3 deletions(-) create mode 100644 src/GLWidget.cpp create mode 100644 src/GLWidget.h create mode 100644 src/MainWindow.cpp create mode 100644 src/MainWindow.h create mode 100644 src/Pendulum.cpp create mode 100644 src/Pendulum.h create mode 100644 src/Simulation.cpp create mode 100644 src/Simulation.h create mode 100644 src/Slider.cpp create mode 100644 src/Slider.h diff --git a/src/GLWidget.cpp b/src/GLWidget.cpp new file mode 100644 index 0000000..0b5091e --- /dev/null +++ b/src/GLWidget.cpp @@ -0,0 +1,10 @@ +#include "GLWidget.h" +#include +#include +#include + +void GLWidget::paintEvent(QPaintEvent *e) { + auto p = new QPainter(this); + p->fillRect(e->rect(), Qt::black); + p->end(); +} diff --git a/src/GLWidget.h b/src/GLWidget.h new file mode 100644 index 0000000..4db76f2 --- /dev/null +++ b/src/GLWidget.h @@ -0,0 +1,7 @@ +#include +#include + +class GLWidget : public QOpenGLWidget, protected QOpenGLFunctions { +protected: + void paintEvent(QPaintEvent* e) override; +}; \ No newline at end of file diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp new file mode 100644 index 0000000..321418c --- /dev/null +++ b/src/MainWindow.cpp @@ -0,0 +1,231 @@ +#include "MainWindow.h" +#include +#include "GLWidget.h" +#include "Simulation.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Slider.h" +#include + +MainWindow::MainWindow() { + simulation = new Simulation; + + masses = std::vector(MaxSegments); + lengths = std::vector(MaxSegments); + + resetMasses(); + resetLengths(); + + buildUI(); +} + +void MainWindow::buildUI() { + setMinimumSize(800, 500); + + auto uiLyt = new QVBoxLayout; + glWidget = new GLWidget; + + auto mainLyt = new QHBoxLayout(this); + mainLyt->addLayout(uiLyt); + mainLyt->addWidget(glWidget, 1); + + auto scrollArea = new QScrollArea; + scrollArea->setWidget(buildAddUI()); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea->setWidgetResizable(true); + + auto l1 = new QLabel("Add Pendula"); + auto l2 = new QLabel("Simulation"); + auto f = l1->font(); + f.setBold(true); + f.setPixelSize(15); + l1->setFont(f); + l2->setFont(f); + + uiLyt->addWidget(l1); + uiLyt->addWidget(scrollArea); + uiLyt->addSpacing(30); + uiLyt->addWidget(l2); + uiLyt->addWidget(buildSimulationUI()); +} + +QWidget * MainWindow::buildAddUI() { + auto w = new QWidget; + + auto lyt = new QVBoxLayout(w); + lyt->setAlignment(Qt::AlignTop); + + auto segLabel = new QLabel(); + auto segmentSlider = new Slider<>(segLabel, "Segments: %d", &segments); + segmentGrid = new QGridLayout(); + + segmentGrid->addWidget(new QLabel("Mass"), 0, 1); + segmentGrid->addWidget(new QLabel("Length"), 0, 3); + + lyt->addWidget(segLabel); + lyt->addWidget(segmentSlider); + lyt->addLayout(segmentGrid); + + connect(segmentSlider, &QSlider::valueChanged, [this, segmentSlider](int newV){ + int oldSegments = segmentSlider->targetOld; + + if (segments < oldSegments){ + for (int row = segments; row < oldSegments; row++){ + for (int i = 0; i < 5; i++){ + auto item = segmentGrid->itemAtPosition(row + 1, i); + segmentGrid->removeItem(item); + delete item->widget(); + } + } + } + if (segments > oldSegments){ + for (int row = oldSegments; row < segments; row++){ + char buff[100]; + snprintf(buff, sizeof(buff), "(%02d)", row); + segmentGrid->addWidget(new QLabel(buff), row + 1, 0); + + auto massLabel = new QLabel; + auto massSlider = new Slider(massLabel, "%03.1fkg", &masses[row], [](int v){ + return double(v) / 10; + }, [](double v) { + return int(v * 10); + }); + massSlider->setFromTarget(); + massSlider->setMinimum(1); + + auto lengthLabel = new QLabel; + auto lengthSlider = new Slider(lengthLabel, "% 4.1fm", &lengths[row], [](int v){ + return double(v) / 10; + }, [](double v) { + return int(v * 10); + }); + lengthSlider->setMaximum(250); + lengthSlider->setFromTarget(); + lengthSlider->setMinimum(1); + + segmentGrid->addWidget(massLabel, row + 1, 1); + segmentGrid->addWidget(massSlider, row + 1, 2); + segmentGrid->addWidget(lengthLabel, row + 1, 3); + segmentGrid->addWidget(lengthSlider, row + 1, 4); + } + } + }); + segmentSlider->setValue(2); + segmentSlider->setMinimum(1); + segmentSlider->setMaximum(MaxSegments); + + auto segmentButtonGrid = new QHBoxLayout; + lyt->addLayout(segmentButtonGrid); + + auto resetMassesBtn = new QPushButton("Reset"); + auto normalizeLengthsBtn = new QPushButton("Normalize"); + auto resetLengthsBtn = new QPushButton("Reset"); + + connect(resetMassesBtn, &QPushButton::clicked, this, &MainWindow::resetMasses); + connect(normalizeLengthsBtn, &QPushButton::clicked, this, &MainWindow::normalizeLengths); + connect(resetLengthsBtn, &QPushButton::clicked, this, &MainWindow::resetLengths); + + segmentButtonGrid->addWidget(resetMassesBtn); + segmentButtonGrid->addWidget(normalizeLengthsBtn); + segmentButtonGrid->addWidget(resetLengthsBtn); + + lyt->addWidget(new QLabel("Starting Angle:")); + auto dial = new QDial; + dial->setMinimum(0); + dial->setMaximum(360); + dial->setMinimumSize(100, 100); + lyt->addWidget(dial); + + auto colorLyt = new QHBoxLayout; + lyt->addLayout(colorLyt); + + colorLyt->addWidget(new QPushButton("Select color")); + colorLyt->addWidget(new QLabel("Color this label")); + + lyt->addWidget(new QCheckBox("Add multiple")); + lyt->addWidget(new QCheckBox("Use rainbow coloring")); + + lyt->addWidget(new QLabel("Count:")); + // lyt->addWidget(new Slider(nullptr, [](int v) {return "";})); + + lyt->addWidget(new QLabel("Property to change slightly:")); + auto group = new QButtonGroup; + auto b1 = new QRadioButton("Starting Angle"); + auto b2 = new QRadioButton("Starting Mass"); + auto b3 = new QRadioButton("Starting Length"); + group->addButton(b1); + group->addButton(b2); + group->addButton(b3); + lyt->addWidget(b1); + lyt->addWidget(b2); + lyt->addWidget(b3); + b1->setChecked(true); + + lyt->addWidget(new QLabel("Index:")); + // lyt->addWidget(new Slider(nullptr, [](int v) {return "";})); + + lyt->addWidget(new QLabel("Change Amount:")); + // lyt->addWidget(new Slider(nullptr, [](int v) {return "";})); + + auto btnLyt = new QHBoxLayout; + lyt->addLayout(btnLyt); + + btnLyt->addWidget(new QPushButton("Add")); + btnLyt->addWidget(new QPushButton("Remove")); + + return w; +} + +QWidget * MainWindow::buildSimulationUI() { + auto w = new QWidget; + + auto lyt = new QVBoxLayout(w); + lyt->addWidget(new QLabel("Gravity:")); + // lyt->addWidget(new Slider(nullptr, [](int v) {return "";})); + lyt->addWidget(new QLabel("Timescale:")); + // lyt->addWidget(new Slider(nullptr, [](int v) {return "";})); + lyt->addWidget(new QLabel("PBD Substeps:")); + // lyt->addWidget(new Slider(nullptr, [](int v) {return "";})); + + auto btnLyt = new QHBoxLayout; + lyt->addLayout(btnLyt); + + btnLyt->addWidget(new QPushButton("Reset")); + btnLyt->addWidget(new QPushButton("Resume/Pause")); + + return w; +} + +void MainWindow::resetMasses() { + for (int i = 0; i < MaxSegments; i++) + masses[i] = 1; + for (int i = 0; i < segments; i++){ + dynamic_cast *>(segmentGrid->itemAtPosition(i + 1, 2)->widget())->setFromTarget(); + } +} + +void MainWindow::resetLengths() { + for (int i = 0; i < MaxSegments; i++) + lengths[i] = 1; + for (int i = 0; i < segments; i++){ + dynamic_cast *>(segmentGrid->itemAtPosition(i + 1, 4)->widget())->setFromTarget(); + } +} + +void MainWindow::normalizeLengths() { + double sum = std::reduce(lengths.begin(), lengths.begin() + segments); + double factor = simulation->size / 2 / sum; + for (int i = 0; i < segments; i++){ + lengths[i] *= factor; + } + for (int i = 0; i < segments; i++){ + dynamic_cast *>(segmentGrid->itemAtPosition(i + 1, 4)->widget())->setFromTarget(); + } +} diff --git a/src/MainWindow.h b/src/MainWindow.h new file mode 100644 index 0000000..5e6d36f --- /dev/null +++ b/src/MainWindow.h @@ -0,0 +1,40 @@ +#include +#include + +class Simulation; +class QGridLayout; +class GLWidget; + +enum Property {Angle, Mass, Length}; + +class MainWindow : public QWidget { +public: + explicit MainWindow(); +private: + void buildUI(); + QWidget * buildAddUI(); + QWidget * buildSimulationUI(); + Simulation * simulation; + + GLWidget * glWidget; + + static const int MaxSegments = 50; + + std::vector masses, lengths; + int segments = 0; + double startingAngle = 90; + QColor color = Qt::white; + bool multiple = false; + bool rainbow = false; + int count = 100; + Property changeProperty = Angle; + int changeIndex = 0; + double changeAmount = 0.001; + + QGridLayout * segmentGrid; + +public slots: + void resetMasses(); + void resetLengths(); + void normalizeLengths(); +}; \ No newline at end of file diff --git a/src/Pendulum.cpp b/src/Pendulum.cpp new file mode 100644 index 0000000..a7a47ec --- /dev/null +++ b/src/Pendulum.cpp @@ -0,0 +1 @@ +#include "Pendulum.h" \ No newline at end of file diff --git a/src/Pendulum.h b/src/Pendulum.h new file mode 100644 index 0000000..eecc726 --- /dev/null +++ b/src/Pendulum.h @@ -0,0 +1,3 @@ +class Pendulum { + +}; \ No newline at end of file diff --git a/src/Simulation.cpp b/src/Simulation.cpp new file mode 100644 index 0000000..1bbd888 --- /dev/null +++ b/src/Simulation.cpp @@ -0,0 +1,3 @@ +#include "Simulation.h" + +Simulation::Simulation() = default; diff --git a/src/Simulation.h b/src/Simulation.h new file mode 100644 index 0000000..50c1fc7 --- /dev/null +++ b/src/Simulation.h @@ -0,0 +1,18 @@ +#include +#include + +class Pendulum; + +class Simulation { +public: + explicit Simulation(); + + double size = 50; + +private: + std::vector pendula; + + double gravity; + double timescale; + uint64_t substeps; +}; \ No newline at end of file diff --git a/src/Slider.cpp b/src/Slider.cpp new file mode 100644 index 0000000..7e81b2d --- /dev/null +++ b/src/Slider.cpp @@ -0,0 +1,2 @@ +#include "Slider.h" +#include \ No newline at end of file diff --git a/src/Slider.h b/src/Slider.h new file mode 100644 index 0000000..2fcc80b --- /dev/null +++ b/src/Slider.h @@ -0,0 +1,37 @@ +#include +#include +#include + +template +class Slider : public QSlider { +public: + explicit Slider(QLabel* label, const char *format, T * target, + const std::function &convertToT = [](int v){ return T(v); }, + const std::function &convertFromT = [](T v){ return int(v); }) : QSlider(Qt::Horizontal) { + this->convertToT = convertToT; + this->convertFromT = convertFromT; + this->target = target; + + connect(this, &QSlider::valueChanged, this, [=, this](int newValue){ + T convertedValue = convertToT(newValue); + + char buff[100]; + snprintf(buff, sizeof(buff), format, convertedValue); + label->setText(buff); + + targetOld = *target; + *target = convertedValue; + }); + } + + void setFromTarget(){ + setValue(convertFromT(*target)); + } + + T targetOld; + +private: + T * target; + std::function convertToT; + std::function convertFromT; +}; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index a1acf65..7621d26 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,10 +1,16 @@ -#include -#include #include -#include +#include +#include "MainWindow.h" int main(int argc, char* argv[]) { QApplication app(argc, argv); + QSurfaceFormat fmt; + fmt.setSamples(4); + QSurfaceFormat::setDefaultFormat(fmt); + + MainWindow w; + w.show(); + return QApplication::exec(); }