From 1aa7bf1713861e239f4cf32122ead9a929591e5a Mon Sep 17 00:00:00 2001 From: Benjamin Kraft Date: Sat, 9 Sep 2023 16:37:35 +0200 Subject: [PATCH] ui done --- src/MainWindow.cpp | 384 +++++++++++++++++++++++++++++++-------------- src/MainWindow.h | 11 +- src/Simulation.h | 9 +- 3 files changed, 285 insertions(+), 119 deletions(-) diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 321418c..bcff645 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -11,8 +11,8 @@ #include #include #include -#include "Slider.h" #include +#include MainWindow::MainWindow() { simulation = new Simulation; @@ -62,123 +62,227 @@ QWidget * MainWindow::buildAddUI() { auto lyt = new QVBoxLayout(w); lyt->setAlignment(Qt::AlignTop); + // Segments auto segLabel = new QLabel(); auto segmentSlider = new Slider<>(segLabel, "Segments: %d", &segments); - segmentGrid = new QGridLayout(); + { + 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); + } - segmentGrid->addWidget(new QLabel("Mass"), 0, 1); - segmentGrid->addWidget(new QLabel("Length"), 0, 3); + // Buttons for Segments + { + auto segmentButtonGrid = new QHBoxLayout; + lyt->addLayout(segmentButtonGrid); - lyt->addWidget(segLabel); - lyt->addWidget(segmentSlider); - lyt->addLayout(segmentGrid); + auto resetMassesBtn = new QPushButton("Reset"); + auto normalizeLengthsBtn = new QPushButton("Normalize"); + auto resetLengthsBtn = new QPushButton("Reset"); - connect(segmentSlider, &QSlider::valueChanged, [this, segmentSlider](int newV){ - int oldSegments = segmentSlider->targetOld; + connect(resetMassesBtn, &QPushButton::clicked, this, &MainWindow::resetMasses); + connect(normalizeLengthsBtn, &QPushButton::clicked, this, &MainWindow::normalizeLengths); + connect(resetLengthsBtn, &QPushButton::clicked, this, &MainWindow::resetLengths); - 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(); - } - } + segmentButtonGrid->addWidget(resetMassesBtn); + segmentButtonGrid->addWidget(normalizeLengthsBtn); + segmentButtonGrid->addWidget(resetLengthsBtn); + } + + // Starting Angle + { + auto angleLabel = new QLabel; + auto dial = new QDial; + dial->setWrapping(true); + dial->setMinimum(0); + dial->setMaximum(360); + dial->setMinimumSize(100, 100); + connect(dial, &QDial::valueChanged, [this, angleLabel](int value){ + angleLabel->setText("Starting Angle: " + QString::fromStdString(std::to_string(value)) + "°"); + startingAngle = value; + }); + dial->setValue(startingAngle); + + lyt->addWidget(angleLabel); + lyt->addWidget(dial); + } + + // Color + { + auto colorLyt = new QHBoxLayout; + lyt->addLayout(colorLyt); + + auto colorBtn = new QPushButton("Select color"); + auto colorLabel = new QLabel; + colorLabel->setStyleSheet("background-color: white"); + + auto setLabelColor = [colorLabel](QColor c){ + QString hex = c.name(); + colorLabel->setStyleSheet("QLabel {background-color: " + hex + "; border: 2px solid black; border-radius: 5px;}"); + }; + connect(colorBtn, &QPushButton::clicked, [this, setLabelColor](){ + color = QColorDialog::getColor(color); + setLabelColor(color); + }); + setLabelColor(color); + + colorLyt->addWidget(colorBtn); + colorLyt->addWidget(colorLabel); + } + + + // Multiple + { + auto multipleWidget = new QWidget; + auto multipleCheckbox = new QCheckBox("Add multiple"); + multipleCheckbox->setCheckState(Qt::Unchecked); + connect(multipleCheckbox, &QCheckBox::stateChanged, [this, multipleWidget](int state){ + multiple = state == Qt::Checked; + multipleWidget->setVisible(multiple); + }); + + lyt->addWidget(multipleCheckbox); + lyt->addWidget(multipleWidget); + + auto multipleLyt = new QVBoxLayout; + multipleWidget->setLayout(multipleLyt); + multipleWidget->setVisible(false); + + // Rainbow coloring + { + auto rainbowCheckbox = new QCheckBox("Use rainbow coloring instead"); + connect(rainbowCheckbox, &QCheckBox::stateChanged, [this](int state){ + rainbow = state == Qt::Checked; + }); + multipleLyt->addWidget(rainbowCheckbox); } - 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); - } + + // Count + { + auto countLabel = new QLabel; + auto countSlider = new Slider<>(countLabel, "Count: %d", &count); + countSlider->setMaximum(1000); + countSlider->setValue(count); + countSlider->setMinimum(5); + + multipleLyt->addWidget(countLabel); + multipleLyt->addWidget(countSlider); } - }); - 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 "";})); + + // Change + { + multipleLyt->addWidget(new QLabel("Property to change slightly:")); + auto group = new QButtonGroup; + auto b1 = new QRadioButton("Starting Angle"); + auto b2 = new QRadioButton("Specific Mass"); + auto b3 = new QRadioButton("Specific Length"); + group->addButton(b1, 0); + group->addButton(b2, 1); + group->addButton(b3, 2); + multipleLyt->addWidget(b1); + multipleLyt->addWidget(b2); + multipleLyt->addWidget(b3); + b1->setChecked(true); + + auto indexLabel = new QLabel; + indexLabel->setVisible(false); + auto indexSlider = new Slider<>(indexLabel, "Index: %d", &changeIndex); + indexSlider->setVisible(false); + indexSlider->setValue(1); + indexSlider->setValue(0); + indexSlider->setMaximum(segments - 1); + + connect(segmentSlider, &QSlider::valueChanged, [this, indexSlider](){ + indexSlider->setMaximum(segments - 1); + }); + + connect(group, &QButtonGroup::idClicked, [this, indexLabel, indexSlider](int id){ + changeProperty = static_cast(id); + bool indexVisible = changeProperty == Mass || changeProperty == Length; + indexLabel->setVisible(indexVisible); + indexSlider->setVisible(indexVisible); + }); + + multipleLyt->addWidget(indexLabel); + multipleLyt->addWidget(indexSlider); + + auto amountLabel = new QLabel; + auto amountSlider = new Slider(amountLabel, "Change Amount: %5.3f%%", &changeAmount, [](int v){ + return double(v) / 1000; + }, [](double v){ + return int(v * 1000); + }); + amountSlider->setMinimum(1); + amountSlider->setMaximum(1000); + multipleLyt->addWidget(amountLabel); + multipleLyt->addWidget(amountSlider); + } + } auto btnLyt = new QHBoxLayout; lyt->addLayout(btnLyt); - btnLyt->addWidget(new QPushButton("Add")); - btnLyt->addWidget(new QPushButton("Remove")); + auto addBtn = new QPushButton("Add"); + auto removeBtn = new QPushButton("Remove"); + + connect(addBtn, &QPushButton::clicked, this, &MainWindow::add); + connect(removeBtn, &QPushButton::clicked, this, &MainWindow::remove); + + btnLyt->addWidget(addBtn); + btnLyt->addWidget(removeBtn); return w; } @@ -187,18 +291,52 @@ 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 gravityLabel = new QLabel; + auto timescaleLabel = new QLabel; + auto substepsLabel = new QLabel; + + gravitySlider = new Slider(gravityLabel, "Gravity: %.2f N/kg", &simulation->gravity, [](int v){ + return double(v) / 100; + }, [](double v){ + return int(v * 100); + }); + timescaleSlider = new Slider(timescaleLabel, "Timescale: x%4.2f", &simulation->timescale, [](int v){ + return double(v) / 100; + }, [](double v){ + return int(v * 100); + }); + substepsSlider = new Slider<>(substepsLabel, "Substeps: %d", &simulation->substeps); + + gravitySlider->setMaximum(3000); + timescaleSlider->setMinimum(1); + timescaleSlider->setMaximum(500); + substepsSlider->setMinimum(1); + substepsSlider->setMaximum(1000); + + resetSimulationControl(); + + lyt->addWidget(gravityLabel); + lyt->addWidget(gravitySlider); + lyt->addWidget(timescaleLabel); + lyt->addWidget(timescaleSlider); + lyt->addWidget(substepsLabel); + lyt->addWidget(substepsSlider); auto btnLyt = new QHBoxLayout; lyt->addLayout(btnLyt); - btnLyt->addWidget(new QPushButton("Reset")); - btnLyt->addWidget(new QPushButton("Resume/Pause")); + auto reset = new QPushButton("Reset"); + auto togglePlay = new QPushButton("Resume"); + + connect(reset, &QPushButton::clicked, this, &MainWindow::resetSimulationControl); + connect(togglePlay, &QPushButton::clicked, this, &MainWindow::toggleSimulation); + connect(togglePlay, &QPushButton::clicked, [this, togglePlay](){ + togglePlay->setText(simulation->isPlaying ? "Pause" : "Resume"); + }); + + btnLyt->addWidget(reset); + btnLyt->addWidget(togglePlay); return w; } @@ -229,3 +367,21 @@ void MainWindow::normalizeLengths() { dynamic_cast *>(segmentGrid->itemAtPosition(i + 1, 4)->widget())->setFromTarget(); } } + +void MainWindow::add() { + +} + +void MainWindow::remove() { + +} + +void MainWindow::resetSimulationControl() { + gravitySlider->setValue(981); + timescaleSlider->setValue(100); + substepsSlider->setValue(30); +} + +void MainWindow::toggleSimulation() { + simulation->isPlaying = !simulation->isPlaying; +} diff --git a/src/MainWindow.h b/src/MainWindow.h index 5e6d36f..fbb5ef6 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -1,5 +1,6 @@ #include #include +#include "Slider.h" class Simulation; class QGridLayout; @@ -22,7 +23,7 @@ private: std::vector masses, lengths; int segments = 0; - double startingAngle = 90; + int startingAngle = 90; QColor color = Qt::white; bool multiple = false; bool rainbow = false; @@ -32,9 +33,17 @@ private: double changeAmount = 0.001; QGridLayout * segmentGrid; + Slider * gravitySlider, * timescaleSlider; + Slider<> * substepsSlider; + public slots: void resetMasses(); void resetLengths(); void normalizeLengths(); + void add(); + void remove(); + + void resetSimulationControl(); + void toggleSimulation(); }; \ No newline at end of file diff --git a/src/Simulation.h b/src/Simulation.h index 50c1fc7..1d0af59 100644 --- a/src/Simulation.h +++ b/src/Simulation.h @@ -9,10 +9,11 @@ public: double size = 50; -private: - std::vector pendula; - double gravity; double timescale; - uint64_t substeps; + int substeps; + + bool isPlaying = false; + + std::vector pendula; }; \ No newline at end of file