Skip to content

Commit efcb6a6

Browse files
committed
.
1 parent f1a6e11 commit efcb6a6

6 files changed

Lines changed: 138 additions & 42 deletions

File tree

src/application/LayoutReviewWidget.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ QWidget* createNavigationRail(
166166
"}"
167167
);
168168
auto* activityLayout = new QVBoxLayout(activityBar);
169-
activityLayout->setContentsMargins(0, 0, 0, 0);
169+
activityLayout->setContentsMargins(0, 0, 0, 12);
170170
activityLayout->setSpacing(0);
171171

172172
const auto makeActivityButton = [&](const QIcon& icon, const QString& tooltip, bool checked, auto&& handler) {

src/application/ScenarioAuthoringWidget.cpp

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <QInputDialog>
1010
#include <QLabel>
1111
#include <QLineEdit>
12+
#include <QMessageBox>
1213
#include <QPainter>
1314
#include <QPushButton>
1415
#include <QScrollArea>
@@ -84,6 +85,25 @@ QString blockScheduleSummary(const safecrowd::domain::ConnectionBlockDraft& bloc
8485
return intervals.join(", ");
8586
}
8687

88+
int totalOccupantCount(const ScenarioAuthoringWidget::ScenarioState& scenario) {
89+
int total = 0;
90+
for (const auto& placement : scenario.crowdPlacements) {
91+
total += std::max(0, placement.occupantCount);
92+
}
93+
if (total > 0 || !scenario.crowdPlacements.empty()) {
94+
return total;
95+
}
96+
97+
for (const auto& placement : scenario.draft.population.initialPlacements) {
98+
total += static_cast<int>(placement.targetAgentCount);
99+
}
100+
return total;
101+
}
102+
103+
bool scenarioHasOccupants(const ScenarioAuthoringWidget::ScenarioState& scenario) {
104+
return totalOccupantCount(scenario) > 0;
105+
}
106+
87107
const safecrowd::domain::Zone2D* firstStartZone(const safecrowd::domain::FacilityLayout2D& layout) {
88108
const auto it = std::find_if(layout.zones.begin(), layout.zones.end(), [](const auto& zone) {
89109
return zone.kind == safecrowd::domain::ZoneKind::Room || zone.kind == safecrowd::domain::ZoneKind::Unknown;
@@ -163,7 +183,7 @@ QWidget* createNavigationRail(
163183
"QToolButton:checked { background: #ffffff; border-left-color: #1f5fae; }");
164184

165185
auto* layout = new QVBoxLayout(activityBar);
166-
layout->setContentsMargins(0, 0, 0, 0);
186+
layout->setContentsMargins(0, 0, 0, 12);
167187
layout->setSpacing(0);
168188

169189
const auto makeActivityButton = [&](const QIcon& icon, const QString& tooltip, ScenarioAuthoringWidget::NavigationView view) {
@@ -385,6 +405,11 @@ ScenarioAuthoringWidget::ScenarioAuthoringWidget(
385405
currentScenarioIndex_(initialState.currentScenarioIndex),
386406
navigationView_(initialState.navigationView),
387407
rightPanelMode_(initialState.rightPanelMode) {
408+
for (auto& scenario : scenarios_) {
409+
if (!scenarioHasOccupants(scenario)) {
410+
scenario.stagedForRun = false;
411+
}
412+
}
388413
rightPanelMode_ = RightPanelMode::Scenario;
389414
initializeUi(false);
390415
}
@@ -424,7 +449,7 @@ SavedScenarioAuthoringState ScenarioAuthoringWidget::currentSavedState() const {
424449
state.scenarios.push_back({
425450
.draft = std::move(draft),
426451
.baseScenarioId = scenario.baseScenarioId.toStdString(),
427-
.stagedForRun = scenario.stagedForRun,
452+
.stagedForRun = scenario.stagedForRun && scenarioHasOccupants(scenario),
428453
});
429454
}
430455
return state;
@@ -573,10 +598,7 @@ void ScenarioAuthoringWidget::refreshInspector() {
573598
if (!hasScenario) {
574599
scenarioSummaryLabel_->setText("No scenario selected");
575600
} else {
576-
int people = 0;
577-
for (const auto& placement : scenario->crowdPlacements) {
578-
people += placement.occupantCount;
579-
}
601+
const int people = totalOccupantCount(*scenario);
580602
const auto blockCount = static_cast<int>(scenario->draft.control.connectionBlocks.size());
581603
scenarioSummaryLabel_->setText(QString("Name: %1\nRole: %2\nPopulation: %3\nStart: %4\nDestination: %5\nEvents: %6\nBlocked exits: %7")
582604
.arg(
@@ -613,9 +635,13 @@ void ScenarioAuthoringWidget::refreshInspector() {
613635
newScenarioButton_->setText(hasScenario ? "New Scenario from Current" : "New Scenario");
614636
}
615637
if (stageScenarioButton_ != nullptr) {
616-
stageScenarioButton_->setEnabled(hasScenario);
617638
const bool staged = hasScenario && scenario->stagedForRun;
639+
const bool hasOccupants = hasScenario && scenarioHasOccupants(*scenario);
640+
stageScenarioButton_->setEnabled(hasScenario && (staged || hasOccupants));
618641
stageScenarioButton_->setText(staged ? "Unstage" : "Stage Scenario");
642+
stageScenarioButton_->setToolTip(hasScenario && !staged && !hasOccupants
643+
? "Add at least one occupant before staging this scenario."
644+
: QString{});
619645
stageScenarioButton_->setStyleSheet(staged
620646
? QString(
621647
"QPushButton { background: #b42318; border: 1px solid #b42318; border-radius: 12px; color: white; font-weight: 600; padding: 10px 18px; }"
@@ -627,7 +653,7 @@ void ScenarioAuthoringWidget::refreshInspector() {
627653
"QPushButton:disabled { background: #d7e0ea; border-color: #d7e0ea; color: #8a98a8; }"));
628654
}
629655
const auto stagedCount = std::count_if(scenarios_.begin(), scenarios_.end(), [](const auto& scenario) {
630-
return scenario.stagedForRun;
656+
return scenario.stagedForRun && scenarioHasOccupants(scenario);
631657
});
632658
if (stagedScenariosLabel_ != nullptr) {
633659
QStringList lines;
@@ -636,7 +662,7 @@ void ScenarioAuthoringWidget::refreshInspector() {
636662
} else {
637663
lines << "Staged scenarios";
638664
for (const auto& stagedScenario : scenarios_) {
639-
if (!stagedScenario.stagedForRun) {
665+
if (!stagedScenario.stagedForRun || !scenarioHasOccupants(stagedScenario)) {
640666
continue;
641667
}
642668
const auto role = stagedScenario.draft.role == safecrowd::domain::ScenarioRole::Baseline ? "Baseline" : "Alternative";
@@ -781,6 +807,15 @@ void ScenarioAuthoringWidget::stageCurrentScenario() {
781807
return;
782808
}
783809

810+
if (!scenario->stagedForRun && !scenarioHasOccupants(*scenario)) {
811+
QMessageBox::warning(
812+
this,
813+
"Cannot stage scenario",
814+
"Add at least one occupant before staging this scenario.");
815+
refreshInspector();
816+
return;
817+
}
818+
784819
scenario->stagedForRun = !scenario->stagedForRun;
785820
refreshInspector();
786821
}
@@ -803,6 +838,9 @@ void ScenarioAuthoringWidget::updateCurrentScenarioPlacements(const std::vector<
803838
initialPlacement.initialVelocity = placement.velocity;
804839
scenario->draft.population.initialPlacements.push_back(std::move(initialPlacement));
805840
}
841+
if (!scenarioHasOccupants(*scenario)) {
842+
scenario->stagedForRun = false;
843+
}
806844

807845
refreshNavigationPanel();
808846
refreshInspector();
@@ -899,14 +937,14 @@ QWidget* ScenarioAuthoringWidget::createScenarioPanel() {
899937
stagedScenariosLabel_->setStyleSheet(ui::mutedTextStyleSheet());
900938
QStringList lines;
901939
const auto stagedCount = std::count_if(scenarios_.begin(), scenarios_.end(), [](const auto& scenario) {
902-
return scenario.stagedForRun;
940+
return scenario.stagedForRun && scenarioHasOccupants(scenario);
903941
});
904942
if (stagedCount == 0) {
905943
lines << "No staged scenarios";
906944
} else {
907945
lines << "Staged scenarios";
908946
for (const auto& scenario : scenarios_) {
909-
if (!scenario.stagedForRun) {
947+
if (!scenario.stagedForRun || !scenarioHasOccupants(scenario)) {
910948
continue;
911949
}
912950
const auto role = scenario.draft.role == safecrowd::domain::ScenarioRole::Baseline ? "Baseline" : "Alternative";
@@ -960,7 +998,9 @@ const ScenarioAuthoringWidget::ScenarioState* ScenarioAuthoringWidget::currentSc
960998

961999
const ScenarioAuthoringWidget::ScenarioState* ScenarioAuthoringWidget::firstStagedBaselineScenario() const {
9621000
const auto it = std::find_if(scenarios_.begin(), scenarios_.end(), [](const auto& scenario) {
963-
return scenario.stagedForRun && scenario.draft.role == safecrowd::domain::ScenarioRole::Baseline;
1001+
return scenario.stagedForRun
1002+
&& scenarioHasOccupants(scenario)
1003+
&& scenario.draft.role == safecrowd::domain::ScenarioRole::Baseline;
9641004
});
9651005
return it == scenarios_.end() ? nullptr : &(*it);
9661006
}

src/application/ScenarioResultWidget.cpp

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <QSizePolicy>
1818
#include <QSlider>
1919
#include <QSignalBlocker>
20+
#include <QStringList>
2021
#include <QTimer>
2122
#include <QVBoxLayout>
2223

@@ -194,27 +195,45 @@ QLabel* createReportSectionHeader(const QString& text, QWidget* parent) {
194195
return label;
195196
}
196197

198+
QPushButton* createReportRowButton(const QStringList& lines, QWidget* parent) {
199+
auto* button = new QPushButton(parent);
200+
button->setFont(ui::font(ui::FontRole::Body));
201+
button->setCursor(Qt::PointingHandCursor);
202+
button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
203+
button->setMinimumWidth(0);
204+
button->setStyleSheet(ui::ghostRowStyleSheet());
205+
206+
auto* layout = new QVBoxLayout(button);
207+
layout->setContentsMargins(14, 12, 14, 12);
208+
layout->setSpacing(4);
209+
for (const auto& line : lines) {
210+
auto* label = createLabel(line, button, ui::FontRole::Body);
211+
label->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
212+
label->setStyleSheet("QLabel { background: transparent; border: 0; padding: 0; }");
213+
layout->addWidget(label);
214+
}
215+
return button;
216+
}
217+
197218
QPushButton* createBottleneckRowButton(
198219
const safecrowd::domain::ScenarioBottleneckMetric& bottleneck,
199220
std::size_t index,
200221
QWidget* parent) {
201222
const auto label = QString::fromStdString(bottleneck.label);
202223
const auto id = QString::fromStdString(bottleneck.connectionId);
203-
const auto idLine = (!id.isEmpty() && id != label) ? QString("\nID: %1").arg(id) : QString{};
204-
const auto detectedLine = bottleneck.detectedAtSeconds.has_value()
205-
? QString("\nDetected: %1 sec").arg(*bottleneck.detectedAtSeconds, 0, 'f', 1)
206-
: QString{};
207-
auto* button = new QPushButton(
208-
QString("%1. %2%3\n%4 nearby, %5 stalled%6")
209-
.arg(static_cast<int>(index + 1))
210-
.arg(label, idLine)
211-
.arg(static_cast<int>(bottleneck.nearbyAgentCount))
212-
.arg(static_cast<int>(bottleneck.stalledAgentCount))
213-
.arg(detectedLine),
214-
parent);
215-
button->setFont(ui::font(ui::FontRole::Body));
216-
button->setCursor(Qt::PointingHandCursor);
217-
button->setStyleSheet(ui::ghostRowStyleSheet());
224+
QStringList lines{
225+
QString("%1. %2").arg(static_cast<int>(index + 1)).arg(label),
226+
};
227+
if (!id.isEmpty() && id != label) {
228+
lines.push_back(QString("ID: %1").arg(id));
229+
}
230+
lines.push_back(QString("%1 nearby, %2 stalled")
231+
.arg(static_cast<int>(bottleneck.nearbyAgentCount))
232+
.arg(static_cast<int>(bottleneck.stalledAgentCount)));
233+
if (bottleneck.detectedAtSeconds.has_value()) {
234+
lines.push_back(QString("Detected: %1 sec").arg(*bottleneck.detectedAtSeconds, 0, 'f', 1));
235+
}
236+
auto* button = createReportRowButton(lines, parent);
218237
button->setToolTip(QString("%1\nClick to focus this bottleneck on the canvas.")
219238
.arg(safecrowd::domain::scenarioBottleneckDefinition()));
220239
return button;
@@ -224,20 +243,17 @@ QPushButton* createHotspotRowButton(
224243
const safecrowd::domain::ScenarioCongestionHotspot& hotspot,
225244
std::size_t index,
226245
QWidget* parent) {
227-
const auto detectedLine = hotspot.detectedAtSeconds.has_value()
228-
? QString("\nDetected: %1 sec").arg(*hotspot.detectedAtSeconds, 0, 'f', 1)
229-
: QString{};
230-
auto* button = new QPushButton(
231-
QString("%1. (%2, %3) - %4 agents%5")
246+
QStringList lines{
247+
QString("%1. (%2, %3) - %4 agents")
232248
.arg(static_cast<int>(index + 1))
233249
.arg(hotspot.center.x, 0, 'f', 1)
234250
.arg(hotspot.center.y, 0, 'f', 1)
235-
.arg(static_cast<int>(hotspot.agentCount))
236-
.arg(detectedLine),
237-
parent);
238-
button->setFont(ui::font(ui::FontRole::Body));
239-
button->setCursor(Qt::PointingHandCursor);
240-
button->setStyleSheet(ui::ghostRowStyleSheet());
251+
.arg(static_cast<int>(hotspot.agentCount)),
252+
};
253+
if (hotspot.detectedAtSeconds.has_value()) {
254+
lines.push_back(QString("Detected: %1 sec").arg(*hotspot.detectedAtSeconds, 0, 'f', 1));
255+
}
256+
auto* button = createReportRowButton(lines, parent);
241257
button->setToolTip(QString("%1\nClick to focus this hotspot on the canvas.")
242258
.arg(safecrowd::domain::scenarioHotspotDefinition()));
243259
return button;
@@ -590,7 +606,7 @@ QWidget* createResultPanel(
590606
layout->setContentsMargins(0, 0, 0, 0);
591607
layout->setSpacing(12);
592608

593-
layout->addWidget(shell != nullptr ? shell->createPanelHeader("Results", panel) : createLabel("Results", panel, ui::FontRole::Title));
609+
layout->addWidget(shell != nullptr ? shell->createPanelHeader("Results", panel, false) : createLabel("Results", panel, ui::FontRole::Title));
594610
auto* scenarioLabel = createLabel(QString("Scenario: %1").arg(QString::fromStdString(scenario.name)), panel);
595611
scenarioLabel->setStyleSheet(ui::mutedTextStyleSheet());
596612
layout->addWidget(scenarioLabel);
@@ -672,9 +688,11 @@ QWidget* createResultFindingsPanel(
672688
auto* area = new QScrollArea(panel);
673689
area->setWidgetResizable(true);
674690
area->setFrameShape(QFrame::NoFrame);
691+
area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
675692
ui::polishScrollArea(area);
676693

677694
auto* content = new QWidget(area);
695+
content->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
678696
auto* contentLayout = new QVBoxLayout(content);
679697
contentLayout->setContentsMargins(0, 0, 10, 0);
680698
contentLayout->setSpacing(12);

src/application/ScenarioRunWidget.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ ScenarioRunWidget::ScenarioRunWidget(
185185

186186
shell_ = new WorkspaceShell(WorkspaceShellOptions{
187187
.showTopBar = true,
188-
.navigationMode = WorkspaceNavigationMode::None,
188+
.navigationMode = WorkspaceNavigationMode::RailOnly,
189189
.showReviewPanel = true,
190190
.reviewPanelWidth = 280,
191191
}, this);
@@ -229,7 +229,7 @@ QWidget* ScenarioRunWidget::createRunPanel() {
229229
layout->setContentsMargins(0, 0, 0, 0);
230230
layout->setSpacing(12);
231231

232-
layout->addWidget(shell_ != nullptr ? shell_->createPanelHeader("Run", panel) : createLabel("Run", panel, ui::FontRole::Title));
232+
layout->addWidget(shell_ != nullptr ? shell_->createPanelHeader("Run", panel, false) : createLabel("Run", panel, ui::FontRole::Title));
233233
scenarioLabel_ = createLabel("", panel);
234234
scenarioLabel_->setStyleSheet(ui::mutedTextStyleSheet());
235235
statusLabel_ = createLabel("", panel);

src/application/WorkspaceShell.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ void WorkspaceShell::initialize(const WorkspaceShellOptions& options) {
161161
navigationRailLayout_ = new QVBoxLayout(navigationRail_);
162162
navigationRailLayout_->setContentsMargins(0, 0, 0, 0);
163163
navigationRailLayout_->setSpacing(0);
164+
rebuildDefaultNavigationRail();
164165
leftClusterLayout->addWidget(navigationRail_);
165166

166167
navigationPanel_ = createPanel(navigationCluster_);
@@ -209,13 +210,45 @@ void WorkspaceShell::setFixedWidthVisible(QWidget* widget, bool visible, int wid
209210
widget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
210211
}
211212

213+
QWidget* WorkspaceShell::createDefaultNavigationRail() {
214+
auto* rail = new QFrame(navigationRail_);
215+
rail->setFixedWidth(navigationRailWidth_);
216+
rail->setStyleSheet(
217+
"QFrame {"
218+
" background: #eef3f8;"
219+
" border: 0;"
220+
" border-right: 1px solid #d7e0ea;"
221+
" border-radius: 0px;"
222+
"}"
223+
);
224+
225+
auto* layout = new QVBoxLayout(rail);
226+
layout->setContentsMargins(0, 0, 0, 12);
227+
layout->setSpacing(0);
228+
layout->addStretch(1);
229+
if (backHandler_) {
230+
layout->addWidget(createBackButton(rail), 0, Qt::AlignHCenter);
231+
}
232+
return rail;
233+
}
234+
235+
void WorkspaceShell::rebuildDefaultNavigationRail() {
236+
if (navigationRailLayout_ == nullptr || customNavigationRail_) {
237+
return;
238+
}
239+
replaceSingleWidget(navigationRailLayout_, createDefaultNavigationRail());
240+
}
241+
212242
void WorkspaceShell::setTools(const QStringList& tools) {
213243
tools_ = tools;
214244
rebuildTopBar();
215245
}
216246

217247
void WorkspaceShell::setBackHandler(std::function<void()> handler) {
218248
backHandler_ = std::move(handler);
249+
if (!customNavigationRail_) {
250+
rebuildDefaultNavigationRail();
251+
}
219252
}
220253

221254
QPushButton* WorkspaceShell::createBackButton(QWidget* parent) const {
@@ -293,6 +326,7 @@ void WorkspaceShell::setOpenProjectHandler(std::function<void()> handler) {
293326
}
294327

295328
void WorkspaceShell::setNavigationRail(QWidget* rail) {
329+
customNavigationRail_ = true;
296330
replaceSingleWidget(navigationRailLayout_, rail);
297331
}
298332

@@ -310,6 +344,7 @@ void WorkspaceShell::setNavigationMode(WorkspaceNavigationMode mode) {
310344
}
311345

312346
const auto showRail = mode == WorkspaceNavigationMode::RailOnly
347+
|| mode == WorkspaceNavigationMode::PanelOnly
313348
|| mode == WorkspaceNavigationMode::RailAndPanel;
314349
const auto showPanel = mode == WorkspaceNavigationMode::PanelOnly
315350
|| mode == WorkspaceNavigationMode::RailAndPanel;

src/application/WorkspaceShell.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ class WorkspaceShell : public QWidget {
5252
private:
5353
void initialize(const WorkspaceShellOptions& options);
5454
void setFixedWidthVisible(QWidget* widget, bool visible, int width);
55+
QWidget* createDefaultNavigationRail();
56+
void rebuildDefaultNavigationRail();
5557
void clearTopBar();
5658
void rebuildTopBar();
5759
QPushButton* createTopBarButton(const QString& text);
@@ -76,6 +78,7 @@ class WorkspaceShell : public QWidget {
7678
std::function<void()> saveProjectHandler_{};
7779
std::function<void()> backHandler_{};
7880
QStringList tools_{};
81+
bool customNavigationRail_{false};
7982
};
8083

8184
} // namespace safecrowd::application

0 commit comments

Comments
 (0)