Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions src/application/ScenarioCanvasWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <QPushButton>
#include <QSpinBox>
#include <QToolButton>
#include <QToolTip>
#include <QVBoxLayout>
#include <QWheelEvent>

Expand Down Expand Up @@ -55,6 +56,66 @@ bool matchesFloor(const std::string& elementFloorId, const QString& floorId) {
return floorId.isEmpty() || elementFloorId.empty() || QString::fromStdString(elementFloorId) == floorId;
}

QString formatConnectionBlockTooltip(const safecrowd::domain::ConnectionBlockDraft& block) {
if (block.connectionId.empty()) {
return {};
}

QString text = QStringLiteral("Block schedule");
if (block.intervals.empty()) {
text.append("\n- Always");
return text;
}

for (const auto& interval : block.intervals) {
const auto start = std::max(0.0, interval.startSeconds);
const auto end = std::max(start, interval.endSeconds);
text.append(QString("\n- %1s ~ %2s").arg(start, 0, 'f', 1).arg(end, 0, 'f', 1));
}
return text;
}

std::optional<std::size_t> hoveredConnectionBlockIndex(
const safecrowd::domain::FacilityLayout2D& layout,
const std::vector<safecrowd::domain::ConnectionBlockDraft>& blocks,
const LayoutCanvasTransform& transform,
const QString& currentFloorId,
const QPointF& screenPosition) {
constexpr double kHoverRadiusPixels = 14.0;

std::optional<std::size_t> closestIndex;
double closestDistanceSq = kHoverRadiusPixels * kHoverRadiusPixels;

for (std::size_t index = 0; index < blocks.size(); ++index) {
const auto& block = blocks[index];
if (block.connectionId.empty()) {
continue;
}

const auto it = std::find_if(layout.connections.begin(), layout.connections.end(), [&](const auto& connection) {
return connection.id == block.connectionId;
});
if (it == layout.connections.end()) {
continue;
}
if (!matchesFloor(it->floorId, currentFloorId)) {
continue;
}

const auto center = transform.map({.x = (it->centerSpan.start.x + it->centerSpan.end.x) * 0.5,
.y = (it->centerSpan.start.y + it->centerSpan.end.y) * 0.5});
const auto dx = center.x() - screenPosition.x();
const auto dy = center.y() - screenPosition.y();
const auto distanceSq = (dx * dx) + (dy * dy);
if (distanceSq <= closestDistanceSq) {
closestDistanceSq = distanceSq;
closestIndex = index;
}
}

return closestIndex;
}

QString defaultFloorId(const safecrowd::domain::FacilityLayout2D& layout) {
if (!layout.floors.empty() && !layout.floors.front().id.empty()) {
return QString::fromStdString(layout.floors.front().id);
Expand Down Expand Up @@ -699,6 +760,12 @@ void ScenarioCanvasWidget::keyReleaseEvent(QKeyEvent* event) {
QWidget::keyReleaseEvent(event);
}

void ScenarioCanvasWidget::leaveEvent(QEvent* event) {
hoveredConnectionBlockId_.clear();
QToolTip::hideText();
QWidget::leaveEvent(event);
}

void ScenarioCanvasWidget::mouseDoubleClickEvent(QMouseEvent* event) {
if (event->button() == Qt::LeftButton) {
camera_.reset();
Expand All @@ -716,17 +783,46 @@ void ScenarioCanvasWidget::mouseMoveEvent(QMouseEvent* event) {
}

if (dragging_) {
if (!hoveredConnectionBlockId_.isEmpty()) {
hoveredConnectionBlockId_.clear();
QToolTip::hideText();
}
dragCurrent_ = event->position();
update();
event->accept();
return;
}
if (selectionDragging_) {
if (!hoveredConnectionBlockId_.isEmpty()) {
hoveredConnectionBlockId_.clear();
QToolTip::hideText();
}
selectionDragCurrent_ = event->position();
update();
event->accept();
return;
}

if (const auto bounds = collectBounds(); bounds.has_value()) {
const auto transform = currentTransform(*bounds);
const auto hoveredIndex = hoveredConnectionBlockIndex(layout_, connectionBlocks_, transform, currentFloorId_, event->position());
if (!hoveredIndex.has_value()) {
if (!hoveredConnectionBlockId_.isEmpty()) {
hoveredConnectionBlockId_.clear();
QToolTip::hideText();
}
} else {
const auto& block = connectionBlocks_[*hoveredIndex];
const auto tooltip = formatConnectionBlockTooltip(block);
if (!tooltip.isEmpty()) {
const auto hoveredId = QString::fromStdString(block.id.empty() ? block.connectionId : block.id);
if (hoveredId != hoveredConnectionBlockId_) {
hoveredConnectionBlockId_ = hoveredId;
QToolTip::showText(event->globalPosition().toPoint(), tooltip, this);
}
}
}
}
QWidget::mouseMoveEvent(event);
}

Expand Down
2 changes: 2 additions & 0 deletions src/application/ScenarioCanvasWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class ScenarioCanvasWidget : public QWidget {
bool eventFilter(QObject* watched, QEvent* event) override;
void keyPressEvent(QKeyEvent* event) override;
void keyReleaseEvent(QKeyEvent* event) override;
void leaveEvent(QEvent* event) override;
void mouseDoubleClickEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
Expand Down Expand Up @@ -145,6 +146,7 @@ class ScenarioCanvasWidget : public QWidget {
QSpinBox* groupCountSpinBox_{nullptr};
QLabel* groupDistributionLabel_{nullptr};
QComboBox* groupDistributionComboBox_{nullptr};
QString hoveredConnectionBlockId_{};
std::function<void(const QString&)> layoutElementActivatedHandler_{};
std::function<void(const QString&)> crowdSelectionChangedHandler_{};
std::function<void(const std::vector<ScenarioCrowdPlacement>&)> placementsChangedHandler_{};
Expand Down
108 changes: 108 additions & 0 deletions src/application/SimulationCanvasWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <QRadialGradient>
#include <QResizeEvent>
#include <QSignalBlocker>
#include <QToolTip>
#include <QWheelEvent>

namespace safecrowd::application {
Expand Down Expand Up @@ -93,6 +94,65 @@ QColor densityHeatmapColor(double ratio, int alpha) {
return QColor(220, 38, 38, alpha);
}

QString formatScheduleTooltip(const safecrowd::domain::ConnectionBlockDraft& block) {
if (block.connectionId.empty()) {
return {};
}

QString text = QStringLiteral("Block schedule");
if (block.intervals.empty()) {
text.append("\n- Always");
return text;
}

for (const auto& interval : block.intervals) {
const auto start = std::max(0.0, interval.startSeconds);
const auto end = std::max(start, interval.endSeconds);
text.append(QString("\n- %1s ~ %2s").arg(start, 0, 'f', 1).arg(end, 0, 'f', 1));
}
return text;
}

std::optional<std::size_t> hoveredBlockedConnectionIndex(
const safecrowd::domain::FacilityLayout2D& layout,
const std::vector<safecrowd::domain::ConnectionBlockDraft>& blocks,
const LayoutCanvasTransform& transform,
const std::string& currentFloorId,
double elapsedSeconds,
const QPointF& screenPosition) {
constexpr double kHoverRadiusPixels = 14.0;

std::optional<std::size_t> closestIndex;
double closestDistanceSq = kHoverRadiusPixels * kHoverRadiusPixels;

for (std::size_t index = 0; index < blocks.size(); ++index) {
const auto& block = blocks[index];
if (!connectionShouldBeBlocked(block, elapsedSeconds)) {
continue;
}
const auto it = std::find_if(layout.connections.begin(), layout.connections.end(), [&](const auto& connection) {
return connection.id == block.connectionId;
});
if (it == layout.connections.end()) {
continue;
}
if (!matchesFloor(it->floorId, currentFloorId)) {
continue;
}

const auto center = transform.map(connectionCenter(*it));
const auto dx = center.x() - screenPosition.x();
const auto dy = center.y() - screenPosition.y();
const auto distanceSq = (dx * dx) + (dy * dy);
if (distanceSq <= closestDistanceSq) {
closestDistanceSq = distanceSq;
closestIndex = index;
}
}

return closestIndex;
}

} // namespace

SimulationCanvasWidget::SimulationCanvasWidget(safecrowd::domain::FacilityLayout2D layout, QWidget* parent)
Expand Down Expand Up @@ -211,6 +271,12 @@ void SimulationCanvasWidget::keyReleaseEvent(QKeyEvent* event) {
QWidget::keyReleaseEvent(event);
}

void SimulationCanvasWidget::leaveEvent(QEvent* event) {
hoveredConnectionBlockId_.clear();
QToolTip::hideText();
QWidget::leaveEvent(event);
}

void SimulationCanvasWidget::mouseDoubleClickEvent(QMouseEvent* event) {
if (event->button() == Qt::LeftButton) {
camera_.reset();
Expand All @@ -228,6 +294,48 @@ void SimulationCanvasWidget::mouseMoveEvent(QMouseEvent* event) {
update();
return;
}

const auto bounds = collectBounds();
if (!bounds.has_value()) {
if (!hoveredConnectionBlockId_.empty()) {
hoveredConnectionBlockId_.clear();
QToolTip::hideText();
}
QWidget::mouseMoveEvent(event);
return;
}

const auto transform = currentTransform(*bounds);
const auto elapsedSeconds = std::max(0.0, frame_.elapsedSeconds);
const auto hoveredIndex = hoveredBlockedConnectionIndex(
layout_,
connectionBlocks_,
transform,
currentFloorId_,
elapsedSeconds,
event->position());

if (!hoveredIndex.has_value()) {
if (!hoveredConnectionBlockId_.empty()) {
hoveredConnectionBlockId_.clear();
QToolTip::hideText();
}
QWidget::mouseMoveEvent(event);
return;
}

const auto& block = connectionBlocks_[*hoveredIndex];
const auto tooltip = formatScheduleTooltip(block);
if (tooltip.isEmpty()) {
QWidget::mouseMoveEvent(event);
return;
}

const auto hoveredId = block.id.empty() ? block.connectionId : block.id;
if (hoveredId != hoveredConnectionBlockId_) {
hoveredConnectionBlockId_ = hoveredId;
QToolTip::showText(event->globalPosition().toPoint(), tooltip, this);
}
QWidget::mouseMoveEvent(event);
}

Expand Down
3 changes: 3 additions & 0 deletions src/application/SimulationCanvasWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class SimulationCanvasWidget : public QWidget {
bool eventFilter(QObject* watched, QEvent* event) override;
void keyPressEvent(QKeyEvent* event) override;
void keyReleaseEvent(QKeyEvent* event) override;
void leaveEvent(QEvent* event) override;
void mouseDoubleClickEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
Expand Down Expand Up @@ -95,6 +96,8 @@ class SimulationCanvasWidget : public QWidget {
double layoutCacheZoom_{0.0};
double layoutCacheDevicePixelRatio_{0.0};
bool layoutCacheValid_{false};

std::string hoveredConnectionBlockId_{};
};

} // namespace safecrowd::application
Loading