@@ -40,6 +40,12 @@ constexpr int kFloorSelectorMargin = 14;
4040const QColor kMovingAgentColor (" #1f5fae" );
4141const QColor kStalledAgentColor (" #7c3aed" );
4242
43+ enum class TimelineVisualState {
44+ Future,
45+ Active,
46+ Expired,
47+ };
48+
4349std::string defaultFloorId (const safecrowd::domain::FacilityLayout2D& layout) {
4450 if (!layout.floors .empty () && !layout.floors .front ().id .empty ()) {
4551 return layout.floors .front ().id ;
@@ -90,6 +96,39 @@ QString formatEnvironmentHazardTooltip(const safecrowd::domain::EnvironmentHazar
9096 return text;
9197}
9298
99+ QString visualStateLabel (TimelineVisualState state) {
100+ switch (state) {
101+ case TimelineVisualState::Future:
102+ return QStringLiteral (" Future" );
103+ case TimelineVisualState::Expired:
104+ return QStringLiteral (" Expired" );
105+ case TimelineVisualState::Active:
106+ default :
107+ return QStringLiteral (" Active" );
108+ }
109+ }
110+
111+ std::optional<TimelineVisualState> environmentHazardVisualState (
112+ const safecrowd::domain::EnvironmentHazardDraft& hazard,
113+ double elapsedSeconds) {
114+ if (safecrowd::domain::environmentHazardActiveAt (hazard, elapsedSeconds)) {
115+ return TimelineVisualState::Active;
116+ }
117+ const auto start = std::max (0.0 , hazard.startSeconds );
118+ if (elapsedSeconds + 1e-9 < start) {
119+ return TimelineVisualState::Future;
120+ }
121+ return TimelineVisualState::Expired;
122+ }
123+
124+ QString formatEnvironmentHazardTooltip (
125+ const safecrowd::domain::EnvironmentHazardDraft& hazard,
126+ TimelineVisualState state) {
127+ auto text = formatEnvironmentHazardTooltip (hazard);
128+ text.append (QString (" \n State: %1" ).arg (visualStateLabel (state)));
129+ return text;
130+ }
131+
93132safecrowd::domain::Point2D connectionCenter (const safecrowd::domain::Connection2D& connection) {
94133 return {
95134 .x = (connection.centerSpan .start .x + connection.centerSpan .end .x ) * 0.5 ,
@@ -159,9 +198,38 @@ QString formatScheduleTooltip(const safecrowd::domain::ConnectionBlockDraft& blo
159198
160199 for (const auto & interval : block.intervals ) {
161200 const auto start = std::max (0.0 , interval.startSeconds );
162- const auto end = std::max (start, interval.endSeconds );
163- text.append (QString (" \n - %1s ~ %2s" ).arg (start, 0 , ' f' , 1 ).arg (end, 0 , ' f' , 1 ));
201+ if (interval.endSeconds <= interval.startSeconds ) {
202+ text.append (QString (" \n - %1s ~ open" ).arg (start, 0 , ' f' , 1 ));
203+ } else {
204+ text.append (QString (" \n - %1s ~ %2s" ).arg (start, 0 , ' f' , 1 ).arg (std::max (start, interval.endSeconds ), 0 , ' f' , 1 ));
205+ }
206+ }
207+ return text;
208+ }
209+
210+ std::optional<TimelineVisualState> connectionBlockVisualState (
211+ const safecrowd::domain::ConnectionBlockDraft& block,
212+ double elapsedSeconds) {
213+ if (block.connectionId .empty ()) {
214+ return std::nullopt ;
164215 }
216+ if (safecrowd::domain::connectionBlockActiveAt (block, elapsedSeconds)) {
217+ return TimelineVisualState::Active;
218+ }
219+ if (block.intervals .empty ()) {
220+ return TimelineVisualState::Active;
221+ }
222+ const auto hasFutureInterval = std::any_of (block.intervals .begin (), block.intervals .end (), [&](const auto & interval) {
223+ return elapsedSeconds + 1e-9 < std::max (0.0 , interval.startSeconds );
224+ });
225+ return hasFutureInterval ? TimelineVisualState::Future : TimelineVisualState::Expired;
226+ }
227+
228+ QString formatScheduleTooltip (
229+ const safecrowd::domain::ConnectionBlockDraft& block,
230+ TimelineVisualState state) {
231+ auto text = formatScheduleTooltip (block);
232+ text.append (QString (" \n State: %1" ).arg (visualStateLabel (state)));
165233 return text;
166234}
167235
@@ -182,7 +250,7 @@ QString formatRouteGuidanceTooltip(const safecrowd::domain::RouteGuidanceDraft&
182250 return text;
183251}
184252
185- std::optional<std::size_t > hoveredBlockedConnectionIndex (
253+ std::optional<std::size_t > hoveredConnectionBlockIndex (
186254 const safecrowd::domain::FacilityLayout2D& layout,
187255 const std::vector<safecrowd::domain::ConnectionBlockDraft>& blocks,
188256 const LayoutCanvasTransform& transform,
@@ -196,7 +264,7 @@ std::optional<std::size_t> hoveredBlockedConnectionIndex(
196264
197265 for (std::size_t index = 0 ; index < blocks.size (); ++index) {
198266 const auto & block = blocks[index];
199- if (!safecrowd::domain::connectionBlockActiveAt (block, elapsedSeconds)) {
267+ if (!connectionBlockVisualState (block, elapsedSeconds). has_value ( )) {
200268 continue ;
201269 }
202270 const auto it = std::find_if (layout.connections .begin (), layout.connections .end (), [&](const auto & connection) {
@@ -222,7 +290,7 @@ std::optional<std::size_t> hoveredBlockedConnectionIndex(
222290 return closestIndex;
223291}
224292
225- std::optional<std::size_t > hoveredActiveEnvironmentHazardIndex (
293+ std::optional<std::size_t > hoveredEnvironmentHazardIndex (
226294 const safecrowd::domain::FacilityLayout2D& layout,
227295 const std::vector<safecrowd::domain::EnvironmentHazardDraft>& hazards,
228296 const LayoutCanvasTransform& transform,
@@ -235,7 +303,7 @@ std::optional<std::size_t> hoveredActiveEnvironmentHazardIndex(
235303 double closestDistanceSq = kHoverRadiusPixels * kHoverRadiusPixels ;
236304 for (std::size_t index = 0 ; index < hazards.size (); ++index) {
237305 const auto & hazard = hazards[index];
238- if (!safecrowd::domain::environmentHazardActiveAt (hazard, elapsedSeconds)) {
306+ if (!environmentHazardVisualState (hazard, elapsedSeconds). has_value ( )) {
239307 continue ;
240308 }
241309 if (!matchesFloor (safecrowd::domain::environmentHazardFloorId (layout, hazard), currentFloorId)) {
@@ -342,7 +410,7 @@ std::optional<QPointF> routeGuidanceMarkerCenter(
342410 std::vector<QPointF> blockedCenters;
343411 blockedCenters.reserve (blocks.size ());
344412 for (const auto & block : blocks) {
345- if (!safecrowd::domain::connectionBlockActiveAt (block, elapsedSeconds)) {
413+ if (!connectionBlockVisualState (block, elapsedSeconds). has_value ( )) {
346414 continue ;
347415 }
348416 const auto connectionIt = std::find_if (layout.connections .begin (), layout.connections .end (), [&](const auto & connection) {
@@ -618,14 +686,14 @@ void SimulationCanvasWidget::mouseMoveEvent(QMouseEvent* event) {
618686 currentFloorId_,
619687 elapsedSeconds,
620688 event->position ());
621- const auto hoveredIndex = hoveredBlockedConnectionIndex (
689+ const auto hoveredIndex = hoveredConnectionBlockIndex (
622690 layout_,
623691 connectionBlocks_,
624692 transform,
625693 currentFloorId_,
626694 elapsedSeconds,
627695 event->position ());
628- const auto hoveredHazard = hoveredActiveEnvironmentHazardIndex (
696+ const auto hoveredHazard = hoveredEnvironmentHazardIndex (
629697 layout_,
630698 environmentHazards_,
631699 transform,
@@ -651,7 +719,8 @@ void SimulationCanvasWidget::mouseMoveEvent(QMouseEvent* event) {
651719
652720 if (hoveredIndex.has_value ()) {
653721 const auto & block = connectionBlocks_[*hoveredIndex];
654- const auto tooltip = formatScheduleTooltip (block);
722+ const auto state = connectionBlockVisualState (block, elapsedSeconds);
723+ const auto tooltip = state.has_value () ? formatScheduleTooltip (block, *state) : formatScheduleTooltip (block);
655724 if (tooltip.isEmpty ()) {
656725 QWidget::mouseMoveEvent (event);
657726 return ;
@@ -670,7 +739,10 @@ void SimulationCanvasWidget::mouseMoveEvent(QMouseEvent* event) {
670739
671740 if (hoveredHazard.has_value ()) {
672741 const auto & hazard = environmentHazards_[*hoveredHazard];
673- const auto tooltip = formatEnvironmentHazardTooltip (hazard);
742+ const auto state = environmentHazardVisualState (hazard, elapsedSeconds);
743+ const auto tooltip = state.has_value ()
744+ ? formatEnvironmentHazardTooltip (hazard, *state)
745+ : formatEnvironmentHazardTooltip (hazard);
674746 const auto hoveredId = hazard.id .empty ()
675747 ? QString (" %1:%2:%3" )
676748 .arg (hazardKindLabel (hazard.kind ))
@@ -861,11 +933,9 @@ void SimulationCanvasWidget::drawConnectionBlockOverlay(QPainter& painter, const
861933 const auto elapsedSeconds = std::max (0.0 , frame_.elapsedSeconds );
862934
863935 painter.save ();
864- painter.setBrush (Qt::NoBrush);
865- painter.setPen (QPen (QColor (" #c0392b" ), 2.8 , Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
866-
867936 for (const auto & block : connectionBlocks_) {
868- if (!safecrowd::domain::connectionBlockActiveAt (block, elapsedSeconds)) {
937+ const auto state = connectionBlockVisualState (block, elapsedSeconds);
938+ if (!state.has_value ()) {
869939 continue ;
870940 }
871941
@@ -880,9 +950,32 @@ void SimulationCanvasWidget::drawConnectionBlockOverlay(QPainter& painter, const
880950 }
881951
882952 const auto center = transform.map (connectionCenter (*it));
953+ QColor color (" #c0392b" );
954+ Qt::PenStyle penStyle = Qt::SolidLine;
955+ double penWidth = 2.8 ;
956+ if (*state == TimelineVisualState::Future) {
957+ color = QColor (" #64748b" );
958+ penStyle = Qt::DashLine;
959+ penWidth = 2.2 ;
960+ } else if (*state == TimelineVisualState::Expired) {
961+ color = QColor (100 , 116 , 139 , 120 );
962+ penStyle = Qt::DotLine;
963+ penWidth = 2.0 ;
964+ }
965+
883966 const double r = 10.0 ;
967+ painter.setBrush (Qt::NoBrush);
968+ painter.setPen (QPen (color, penWidth, penStyle, Qt::RoundCap, Qt::RoundJoin));
884969 painter.drawEllipse (center, r, r);
885- painter.drawLine (QPointF (center.x () - 6.5 , center.y () + 6.5 ), QPointF (center.x () + 6.5 , center.y () - 6.5 ));
970+ if (*state == TimelineVisualState::Future) {
971+ painter.drawLine (center, QPointF (center.x (), center.y () - 5.8 ));
972+ painter.drawLine (center, QPointF (center.x () + 5.2 , center.y ()));
973+ } else if (*state == TimelineVisualState::Expired) {
974+ painter.drawLine (QPointF (center.x () - 5.6 , center.y () + 0.4 ), QPointF (center.x () - 1.8 , center.y () + 4.2 ));
975+ painter.drawLine (QPointF (center.x () - 1.8 , center.y () + 4.2 ), QPointF (center.x () + 6.0 , center.y () - 5.0 ));
976+ } else {
977+ painter.drawLine (QPointF (center.x () - 6.5 , center.y () + 6.5 ), QPointF (center.x () + 6.5 , center.y () - 6.5 ));
978+ }
886979 }
887980
888981 painter.restore ();
@@ -900,7 +993,8 @@ void SimulationCanvasWidget::drawEnvironmentHazardOverlay(QPainter& painter, con
900993 painter.setCompositionMode (QPainter::CompositionMode_SourceOver);
901994
902995 for (const auto & hazard : environmentHazards_) {
903- if (!safecrowd::domain::environmentHazardActiveAt (hazard, elapsedSeconds)) {
996+ const auto state = environmentHazardVisualState (hazard, elapsedSeconds);
997+ if (!state.has_value ()) {
904998 continue ;
905999 }
9061000 if (!matchesFloor (safecrowd::domain::environmentHazardFloorId (layout_, hazard), currentFloorId_)) {
@@ -918,9 +1012,27 @@ void SimulationCanvasWidget::drawEnvironmentHazardOverlay(QPainter& painter, con
9181012 std::hypot (radiusAnchor.x () - center.x (), radiusAnchor.y () - center.y ()));
9191013
9201014 const auto isFire = hazard.kind == safecrowd::domain::EnvironmentHazardKind::Fire;
921- const QColor core = isFire ? QColor (220 , 38 , 38 , 110 ) : QColor (71 , 85 , 105 , 92 );
922- const QColor mid = isFire ? QColor (249 , 115 , 22 , 46 ) : QColor (148 , 163 , 184 , 40 );
923- const QColor edge = isFire ? QColor (249 , 115 , 22 , 0 ) : QColor (148 , 163 , 184 , 0 );
1015+ QColor core = isFire ? QColor (220 , 38 , 38 , 110 ) : QColor (71 , 85 , 105 , 92 );
1016+ QColor mid = isFire ? QColor (249 , 115 , 22 , 46 ) : QColor (148 , 163 , 184 , 40 );
1017+ QColor edge = isFire ? QColor (249 , 115 , 22 , 0 ) : QColor (148 , 163 , 184 , 0 );
1018+ QColor outline = isFire ? QColor (185 , 28 , 28 , 180 ) : QColor (71 , 85 , 105 , 165 );
1019+ Qt::PenStyle outlineStyle = Qt::DashLine;
1020+ QColor markerFill = isFire ? QColor (" #c2410c" ) : QColor (" #64748b" );
1021+ if (*state == TimelineVisualState::Future) {
1022+ core = isFire ? QColor (220 , 38 , 38 , 42 ) : QColor (71 , 85 , 105 , 34 );
1023+ mid = isFire ? QColor (249 , 115 , 22 , 16 ) : QColor (148 , 163 , 184 , 14 );
1024+ edge = QColor (0 , 0 , 0 , 0 );
1025+ outline = QColor (100 , 116 , 139 , 135 );
1026+ outlineStyle = Qt::DashLine;
1027+ markerFill = QColor (100 , 116 , 139 , 170 );
1028+ } else if (*state == TimelineVisualState::Expired) {
1029+ core = QColor (100 , 116 , 139 , 28 );
1030+ mid = QColor (100 , 116 , 139 , 10 );
1031+ edge = QColor (100 , 116 , 139 , 0 );
1032+ outline = QColor (100 , 116 , 139 , 90 );
1033+ outlineStyle = Qt::DotLine;
1034+ markerFill = QColor (100 , 116 , 139 , 115 );
1035+ }
9241036 QRadialGradient gradient (center, radius);
9251037 gradient.setColorAt (0.0 , core);
9261038 gradient.setColorAt (0.48 , mid);
@@ -931,16 +1043,15 @@ void SimulationCanvasWidget::drawEnvironmentHazardOverlay(QPainter& painter, con
9311043
9321044 painter.setBrush (Qt::NoBrush);
9331045 painter.setPen (QPen (
934- isFire ? QColor ( 185 , 28 , 28 , 180 ) : QColor ( 71 , 85 , 105 , 165 ) ,
1046+ outline ,
9351047 1.8 ,
936- Qt::DashLine ,
1048+ outlineStyle ,
9371049 Qt::RoundCap,
9381050 Qt::RoundJoin));
9391051 painter.drawEllipse (center, radius, radius);
9401052
941- const QColor fill = isFire ? QColor (" #c2410c" ) : QColor (" #64748b" );
9421053 painter.setPen (Qt::NoPen);
943- painter.setBrush (fill );
1054+ painter.setBrush (markerFill );
9441055 painter.drawEllipse (center, 11.0 , 11.0 );
9451056
9461057 painter.setPen (QPen (Qt::white, 2.0 , Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
0 commit comments