about summary refs log tree commit diff stats
path: root/src/subway_map.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/subway_map.cpp')
-rw-r--r--src/subway_map.cpp724
1 files changed, 724 insertions, 0 deletions
diff --git a/src/subway_map.cpp b/src/subway_map.cpp new file mode 100644 index 0000000..e3b844d --- /dev/null +++ b/src/subway_map.cpp
@@ -0,0 +1,724 @@
1#include "subway_map.h"
2
3#include <fmt/core.h>
4#include <wx/dcbuffer.h>
5#include <wx/dcgraph.h>
6
7#include <sstream>
8
9#include "ap_state.h"
10#include "game_data.h"
11#include "global.h"
12#include "tracker_state.h"
13
14constexpr int AREA_ACTUAL_SIZE = 21;
15constexpr int OWL_ACTUAL_SIZE = 32;
16
17enum class ItemDrawType { kNone, kBox, kOwl };
18
19SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) {
20 SetBackgroundStyle(wxBG_STYLE_PAINT);
21
22 map_image_ =
23 wxImage(GetAbsolutePath("assets/subway.png").c_str(), wxBITMAP_TYPE_PNG);
24 if (!map_image_.IsOk()) {
25 return;
26 }
27
28 owl_image_ =
29 wxImage(GetAbsolutePath("assets/owl.png").c_str(), wxBITMAP_TYPE_PNG);
30 if (!owl_image_.IsOk()) {
31 return;
32 }
33
34 unchecked_eye_ =
35 wxBitmap(wxImage(GetAbsolutePath("assets/unchecked.png").c_str(),
36 wxBITMAP_TYPE_PNG)
37 .Scale(32, 32));
38 checked_eye_ = wxBitmap(
39 wxImage(GetAbsolutePath("assets/checked.png").c_str(), wxBITMAP_TYPE_PNG)
40 .Scale(32, 32));
41
42 tree_ = std::make_unique<quadtree::Quadtree<int, GetItemBox>>(
43 quadtree::Box<float>{0, 0, static_cast<float>(map_image_.GetWidth()),
44 static_cast<float>(map_image_.GetHeight())});
45 for (const SubwayItem &subway_item : GD_GetSubwayItems()) {
46 tree_->add(subway_item.id);
47 }
48
49 Redraw();
50
51 scroll_timer_ = new wxTimer(this);
52
53 Bind(wxEVT_PAINT, &SubwayMap::OnPaint, this);
54 Bind(wxEVT_MOTION, &SubwayMap::OnMouseMove, this);
55 Bind(wxEVT_MOUSEWHEEL, &SubwayMap::OnMouseScroll, this);
56 Bind(wxEVT_LEAVE_WINDOW, &SubwayMap::OnMouseLeave, this);
57 Bind(wxEVT_LEFT_DOWN, &SubwayMap::OnMouseClick, this);
58 Bind(wxEVT_TIMER, &SubwayMap::OnTimer, this);
59
60 zoom_slider_ = new wxSlider(this, wxID_ANY, 0, 0, 8, {15, 15});
61 zoom_slider_->Bind(wxEVT_SLIDER, &SubwayMap::OnZoomSlide, this);
62
63 help_button_ = new wxButton(this, wxID_ANY, "Help");
64 help_button_->Bind(wxEVT_BUTTON, &SubwayMap::OnClickHelp, this);
65 SetUpHelpButton();
66}
67
68void SubwayMap::OnConnect() {
69 networks_.Clear();
70
71 std::map<std::string, std::vector<int>> tagged;
72 for (const SubwayItem &subway_item : GD_GetSubwayItems()) {
73 if (AP_HasEarlyColorHallways() &&
74 subway_item.special == "starting_room_paintings") {
75 tagged["early_ch"].push_back(subway_item.id);
76 }
77
78 if (AP_IsPaintingShuffle() && !subway_item.paintings.empty()) {
79 continue;
80 }
81
82 for (const std::string &tag : subway_item.tags) {
83 tagged[tag].push_back(subway_item.id);
84 }
85
86 if (!AP_IsSunwarpShuffle() && subway_item.sunwarp &&
87 subway_item.sunwarp->type != SubwaySunwarpType::kFinal) {
88 std::string tag = fmt::format("subway{}", subway_item.sunwarp->dots);
89 tagged[tag].push_back(subway_item.id);
90 }
91
92 if (!AP_IsPilgrimageEnabled() &&
93 (subway_item.special == "sun_painting" ||
94 subway_item.special == "sun_painting_exit")) {
95 tagged["sun_painting"].push_back(subway_item.id);
96 }
97 }
98
99 if (AP_IsSunwarpShuffle()) {
100 for (const auto &[index, mapping] : AP_GetSunwarpMapping()) {
101 std::string tag = fmt::format("sunwarp{}", mapping.dots);
102
103 SubwaySunwarp fromWarp;
104 if (index < 6) {
105 fromWarp.dots = index + 1;
106 fromWarp.type = SubwaySunwarpType::kEnter;
107 } else {
108 fromWarp.dots = index - 5;
109 fromWarp.type = SubwaySunwarpType::kExit;
110 }
111
112 SubwaySunwarp toWarp;
113 if (mapping.exit_index < 6) {
114 toWarp.dots = mapping.exit_index + 1;
115 toWarp.type = SubwaySunwarpType::kEnter;
116 } else {
117 toWarp.dots = mapping.exit_index - 5;
118 toWarp.type = SubwaySunwarpType::kExit;
119 }
120
121 tagged[tag].push_back(GD_GetSubwayItemForSunwarp(fromWarp));
122 tagged[tag].push_back(GD_GetSubwayItemForSunwarp(toWarp));
123 }
124 }
125
126 for (const auto &[tag, items] : tagged) {
127 // Pairwise connect all items with the same tag.
128 for (auto tag_it1 = items.begin(); std::next(tag_it1) != items.end();
129 tag_it1++) {
130 for (auto tag_it2 = std::next(tag_it1); tag_it2 != items.end();
131 tag_it2++) {
132 networks_.AddLink(*tag_it1, *tag_it2);
133 }
134 }
135 }
136
137 checked_paintings_.clear();
138}
139
140void SubwayMap::UpdateIndicators() {
141 if (AP_IsPaintingShuffle()) {
142 for (const std::string &painting_id : AP_GetCheckedPaintings()) {
143 if (!checked_paintings_.count(painting_id)) {
144 checked_paintings_.insert(painting_id);
145
146 if (AP_GetPaintingMapping().count(painting_id)) {
147 std::optional<int> from_id = GD_GetSubwayItemForPainting(painting_id);
148 std::optional<int> to_id = GD_GetSubwayItemForPainting(
149 AP_GetPaintingMapping().at(painting_id));
150
151 if (from_id && to_id) {
152 networks_.AddLink(*from_id, *to_id);
153 }
154 }
155 }
156 }
157 }
158
159 Redraw();
160}
161
162void SubwayMap::UpdateSunwarp(SubwaySunwarp from_sunwarp,
163 SubwaySunwarp to_sunwarp) {
164 networks_.AddLink(GD_GetSubwayItemForSunwarp(from_sunwarp),
165 GD_GetSubwayItemForSunwarp(to_sunwarp));
166}
167
168void SubwayMap::Zoom(bool in) {
169 wxPoint focus_point;
170
171 if (mouse_position_) {
172 focus_point = *mouse_position_;
173 } else {
174 focus_point = {GetSize().GetWidth() / 2, GetSize().GetHeight() / 2};
175 }
176
177 if (in) {
178 if (zoom_ < 3.0) {
179 SetZoom(zoom_ + 0.25, focus_point);
180 }
181 } else {
182 if (zoom_ > 1.0) {
183 SetZoom(zoom_ - 0.25, focus_point);
184 }
185 }
186}
187
188void SubwayMap::OnPaint(wxPaintEvent &event) {
189 if (GetSize() != rendered_.GetSize()) {
190 wxSize panel_size = GetSize();
191 wxSize image_size = map_image_.GetSize();
192
193 render_x_ = 0;
194 render_y_ = 0;
195 render_width_ = panel_size.GetWidth();
196 render_height_ = panel_size.GetHeight();
197
198 if (image_size.GetWidth() * panel_size.GetHeight() >
199 panel_size.GetWidth() * image_size.GetHeight()) {
200 render_height_ = (panel_size.GetWidth() * image_size.GetHeight()) /
201 image_size.GetWidth();
202 render_y_ = (panel_size.GetHeight() - render_height_) / 2;
203 } else {
204 render_width_ = (image_size.GetWidth() * panel_size.GetHeight()) /
205 image_size.GetHeight();
206 render_x_ = (panel_size.GetWidth() - render_width_) / 2;
207 }
208
209 SetZoomPos({zoom_x_, zoom_y_});
210
211 SetUpHelpButton();
212 }
213
214 wxBufferedPaintDC dc(this);
215 dc.SetBackground(*wxWHITE_BRUSH);
216 dc.Clear();
217
218 {
219 wxMemoryDC rendered_dc;
220 rendered_dc.SelectObject(rendered_);
221
222 int dst_x;
223 int dst_y;
224 int dst_w;
225 int dst_h;
226 int src_x;
227 int src_y;
228 int src_w;
229 int src_h;
230
231 int zoomed_width = render_width_ * zoom_;
232 int zoomed_height = render_height_ * zoom_;
233
234 if (zoomed_width <= GetSize().GetWidth()) {
235 dst_x = (GetSize().GetWidth() - zoomed_width) / 2;
236 dst_w = zoomed_width;
237 src_x = 0;
238 src_w = map_image_.GetWidth();
239 } else {
240 dst_x = 0;
241 dst_w = GetSize().GetWidth();
242 src_x = -zoom_x_ * map_image_.GetWidth() / render_width_ / zoom_;
243 src_w =
244 GetSize().GetWidth() * map_image_.GetWidth() / render_width_ / zoom_;
245 }
246
247 if (zoomed_height <= GetSize().GetHeight()) {
248 dst_y = (GetSize().GetHeight() - zoomed_height) / 2;
249 dst_h = zoomed_height;
250 src_y = 0;
251 src_h = map_image_.GetHeight();
252 } else {
253 dst_y = 0;
254 dst_h = GetSize().GetHeight();
255 src_y = -zoom_y_ * map_image_.GetWidth() / render_width_ / zoom_;
256 src_h =
257 GetSize().GetHeight() * map_image_.GetWidth() / render_width_ / zoom_;
258 }
259
260 wxGCDC gcdc(dc);
261 gcdc.GetGraphicsContext()->SetInterpolationQuality(wxINTERPOLATION_GOOD);
262 gcdc.StretchBlit(dst_x, dst_y, dst_w, dst_h, &rendered_dc, src_x, src_y,
263 src_w, src_h);
264 }
265
266 if (hovered_item_) {
267 // Note that these requirements are duplicated on OnMouseClick so that it
268 // knows when an item has a hover effect.
269 const SubwayItem &subway_item = GD_GetSubwayItem(*hovered_item_);
270 if (subway_item.door && !GetDoorRequirements(*subway_item.door).empty()) {
271 const std::map<std::string, bool> &report =
272 GetDoorRequirements(*subway_item.door);
273
274 int acc_height = 10;
275 int col_width = 0;
276
277 for (const auto &[text, obtained] : report) {
278 wxSize item_extent = dc.GetTextExtent(text);
279 int item_height = std::max(32, item_extent.GetHeight()) + 10;
280 acc_height += item_height;
281
282 if (item_extent.GetWidth() > col_width) {
283 col_width = item_extent.GetWidth();
284 }
285 }
286
287 int item_width = col_width + 10 + 32;
288 int full_width = item_width + 20;
289
290 wxPoint popup_pos =
291 MapPosToRenderPos({subway_item.x + AREA_ACTUAL_SIZE / 2,
292 subway_item.y + AREA_ACTUAL_SIZE / 2});
293
294 if (popup_pos.x + full_width > GetSize().GetWidth()) {
295 popup_pos.x = GetSize().GetWidth() - full_width;
296 }
297 if (popup_pos.y + acc_height > GetSize().GetHeight()) {
298 popup_pos.y = GetSize().GetHeight() - acc_height;
299 }
300
301 dc.SetPen(*wxTRANSPARENT_PEN);
302 dc.SetBrush(*wxBLACK_BRUSH);
303 dc.DrawRectangle(popup_pos, {full_width, acc_height});
304
305 dc.SetFont(GetFont());
306
307 int cur_height = 10;
308
309 for (const auto &[text, obtained] : report) {
310 wxBitmap *eye_ptr = obtained ? &checked_eye_ : &unchecked_eye_;
311
312 dc.DrawBitmap(*eye_ptr, popup_pos + wxPoint{10, cur_height});
313
314 dc.SetTextForeground(obtained ? *wxWHITE : *wxRED);
315 wxSize item_extent = dc.GetTextExtent(text);
316 dc.DrawText(
317 text,
318 popup_pos +
319 wxPoint{10 + 32 + 10,
320 cur_height + (32 - dc.GetFontMetrics().height) / 2});
321
322 cur_height += 10 + 32;
323 }
324 }
325
326 if (networks_.IsItemInNetwork(*hovered_item_)) {
327 dc.SetBrush(*wxTRANSPARENT_BRUSH);
328
329 for (const auto &[item_id1, item_id2] :
330 networks_.GetNetworkGraph(*hovered_item_)) {
331 const SubwayItem &item1 = GD_GetSubwayItem(item_id1);
332 const SubwayItem &item2 = GD_GetSubwayItem(item_id2);
333
334 wxPoint item1_pos = MapPosToRenderPos(
335 {item1.x + AREA_ACTUAL_SIZE / 2, item1.y + AREA_ACTUAL_SIZE / 2});
336 wxPoint item2_pos = MapPosToRenderPos(
337 {item2.x + AREA_ACTUAL_SIZE / 2, item2.y + AREA_ACTUAL_SIZE / 2});
338
339 int left = std::min(item1_pos.x, item2_pos.x);
340 int top = std::min(item1_pos.y, item2_pos.y);
341 int right = std::max(item1_pos.x, item2_pos.x);
342 int bottom = std::max(item1_pos.y, item2_pos.y);
343
344 int halfwidth = right - left;
345 int halfheight = bottom - top;
346
347 if (halfwidth < 4 || halfheight < 4) {
348 dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4));
349 dc.DrawLine(item1_pos, item2_pos);
350 dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2));
351 dc.DrawLine(item1_pos, item2_pos);
352 } else {
353 int ellipse_x;
354 int ellipse_y;
355 double start;
356 double end;
357
358 if (item1_pos.x > item2_pos.x) {
359 ellipse_y = top;
360
361 if (item1_pos.y > item2_pos.y) {
362 ellipse_x = left - halfwidth;
363
364 start = 0;
365 end = 90;
366 } else {
367 ellipse_x = left;
368
369 start = 90;
370 end = 180;
371 }
372 } else {
373 ellipse_y = top - halfheight;
374
375 if (item1_pos.y > item2_pos.y) {
376 ellipse_x = left - halfwidth;
377
378 start = 270;
379 end = 360;
380 } else {
381 ellipse_x = left;
382
383 start = 180;
384 end = 270;
385 }
386 }
387
388 dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4));
389 dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2,
390 halfheight * 2, start, end);
391 dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2));
392 dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2,
393 halfheight * 2, start, end);
394 }
395 }
396 }
397 }
398
399 event.Skip();
400}
401
402void SubwayMap::OnMouseMove(wxMouseEvent &event) {
403 wxPoint mouse_pos = RenderPosToMapPos(event.GetPosition());
404
405 std::vector<int> hovered = tree_->query(
406 {static_cast<float>(mouse_pos.x), static_cast<float>(mouse_pos.y), 2, 2});
407 if (!hovered.empty()) {
408 actual_hover_ = hovered[0];
409 } else {
410 actual_hover_ = std::nullopt;
411 }
412
413 if (!sticky_hover_ && actual_hover_ != hovered_item_) {
414 hovered_item_ = actual_hover_;
415
416 Refresh();
417 }
418
419 if (scroll_mode_) {
420 EvaluateScroll(event.GetPosition());
421 }
422
423 mouse_position_ = event.GetPosition();
424
425 event.Skip();
426}
427
428void SubwayMap::OnMouseScroll(wxMouseEvent &event) {
429 double new_zoom = zoom_;
430 if (event.GetWheelRotation() > 0) {
431 new_zoom = std::min(3.0, zoom_ + 0.25);
432 } else {
433 new_zoom = std::max(1.0, zoom_ - 0.25);
434 }
435
436 if (zoom_ != new_zoom) {
437 SetZoom(new_zoom, event.GetPosition());
438 }
439
440 event.Skip();
441}
442
443void SubwayMap::OnMouseLeave(wxMouseEvent &event) {
444 SetScrollSpeed(0, 0);
445 mouse_position_ = std::nullopt;
446}
447
448void SubwayMap::OnMouseClick(wxMouseEvent &event) {
449 bool finished = false;
450
451 if (actual_hover_) {
452 const SubwayItem &subway_item = GD_GetSubwayItem(*actual_hover_);
453 if ((subway_item.door && !GetDoorRequirements(*subway_item.door).empty()) ||
454 networks_.IsItemInNetwork(*hovered_item_)) {
455 if (actual_hover_ != hovered_item_) {
456 hovered_item_ = actual_hover_;
457
458 if (!hovered_item_) {
459 sticky_hover_ = false;
460 }
461
462 Refresh();
463 } else {
464 sticky_hover_ = !sticky_hover_;
465 }
466
467 finished = true;
468 }
469 }
470
471 if (!finished) {
472 if (scroll_mode_) {
473 scroll_mode_ = false;
474
475 SetScrollSpeed(0, 0);
476
477 SetCursor(wxCURSOR_ARROW);
478 } else if (event.GetPosition().x < GetSize().GetWidth() / 6 ||
479 event.GetPosition().x > 5 * GetSize().GetWidth() / 6 ||
480 event.GetPosition().y < GetSize().GetHeight() / 6 ||
481 event.GetPosition().y > 5 * GetSize().GetHeight() / 6) {
482 scroll_mode_ = true;
483
484 EvaluateScroll(event.GetPosition());
485
486 SetCursor(wxCURSOR_CROSS);
487 } else {
488 sticky_hover_ = false;
489 }
490 }
491}
492
493void SubwayMap::OnTimer(wxTimerEvent &event) {
494 SetZoomPos({zoom_x_ + scroll_x_, zoom_y_ + scroll_y_});
495 Refresh();
496}
497
498void SubwayMap::OnZoomSlide(wxCommandEvent &event) {
499 double new_zoom = 1.0 + 0.25 * zoom_slider_->GetValue();
500
501 if (new_zoom != zoom_) {
502 SetZoom(new_zoom, {GetSize().GetWidth() / 2, GetSize().GetHeight() / 2});
503 }
504}
505
506void SubwayMap::OnClickHelp(wxCommandEvent &event) {
507 wxMessageBox(
508 "Zoom in/out using the mouse wheel, Ctrl +/-, or the slider in the "
509 "corner.\nClick on a side of the screen to start panning. It will follow "
510 "your mouse. Click again to stop.\nHover over a door to see the "
511 "requirements to open it.\nHover over a warp or active painting to see "
512 "what it is connected to.\nIn painting shuffle, paintings that have not "
513 "yet been checked will not show their connections.\nA green shaded owl "
514 "means that there is a painting entrance there.\nA red shaded owl means "
515 "that there are only painting exits there.\nClick on a door or "
516 "warp to make the popup stick until you click again.",
517 "Subway Map Help");
518}
519
520void SubwayMap::Redraw() {
521 rendered_ = wxBitmap(map_image_);
522
523 wxMemoryDC dc;
524 dc.SelectObject(rendered_);
525
526 wxGCDC gcdc(dc);
527
528 for (const SubwayItem &subway_item : GD_GetSubwayItems()) {
529 ItemDrawType draw_type = ItemDrawType::kNone;
530 const wxBrush *brush_color = wxGREY_BRUSH;
531 std::optional<wxColour> shade_color;
532
533 if (AP_HasEarlyColorHallways() &&
534 subway_item.special == "starting_room_paintings") {
535 draw_type = ItemDrawType::kOwl;
536 shade_color = wxColour(0, 255, 0, 128);
537 } else if (subway_item.special == "sun_painting") {
538 if (!AP_IsPilgrimageEnabled()) {
539 if (IsDoorOpen(*subway_item.door)) {
540 draw_type = ItemDrawType::kOwl;
541 shade_color = wxColour(0, 255, 0, 128);
542 } else {
543 draw_type = ItemDrawType::kBox;
544 brush_color = wxRED_BRUSH;
545 }
546 }
547 } else if (!subway_item.paintings.empty()) {
548 if (AP_IsPaintingShuffle()) {
549 bool has_checked_painting = false;
550 bool has_unchecked_painting = false;
551 bool has_mapped_painting = false;
552 bool has_codomain_painting = false;
553
554 for (const std::string &painting_id : subway_item.paintings) {
555 if (checked_paintings_.count(painting_id)) {
556 has_checked_painting = true;
557
558 if (AP_GetPaintingMapping().count(painting_id)) {
559 has_mapped_painting = true;
560 } else if (AP_IsPaintingMappedTo(painting_id)) {
561 has_codomain_painting = true;
562 }
563 } else {
564 has_unchecked_painting = true;
565 }
566 }
567
568 if (has_unchecked_painting || has_mapped_painting ||
569 has_codomain_painting) {
570 draw_type = ItemDrawType::kOwl;
571
572 if (has_checked_painting) {
573 if (has_mapped_painting) {
574 shade_color = wxColour(0, 255, 0, 128);
575 } else {
576 shade_color = wxColour(255, 0, 0, 128);
577 }
578 }
579 }
580 } else if (!subway_item.tags.empty()) {
581 draw_type = ItemDrawType::kOwl;
582 }
583 } else if (subway_item.door) {
584 draw_type = ItemDrawType::kBox;
585
586 if (IsDoorOpen(*subway_item.door)) {
587 brush_color = wxGREEN_BRUSH;
588 } else {
589 brush_color = wxRED_BRUSH;
590 }
591 }
592
593 wxPoint real_area_pos = {subway_item.x, subway_item.y};
594
595 int real_area_size =
596 (draw_type == ItemDrawType::kOwl ? OWL_ACTUAL_SIZE : AREA_ACTUAL_SIZE);
597
598 if (draw_type == ItemDrawType::kBox) {
599 gcdc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 1));
600 gcdc.SetBrush(*brush_color);
601 gcdc.DrawRectangle(real_area_pos, {real_area_size, real_area_size});
602 } else if (draw_type == ItemDrawType::kOwl) {
603 wxBitmap owl_bitmap = wxBitmap(owl_image_.Scale(
604 real_area_size, real_area_size, wxIMAGE_QUALITY_BILINEAR));
605 gcdc.DrawBitmap(owl_bitmap, real_area_pos);
606
607 if (shade_color) {
608 gcdc.SetBrush(wxBrush(*shade_color));
609 gcdc.DrawRectangle(real_area_pos, {real_area_size, real_area_size});
610 }
611 }
612 }
613}
614
615void SubwayMap::SetUpHelpButton() {
616 help_button_->SetPosition({
617 GetSize().GetWidth() - help_button_->GetSize().GetWidth() - 15,
618 15,
619 });
620}
621
622void SubwayMap::EvaluateScroll(wxPoint pos) {
623 int scroll_x;
624 int scroll_y;
625 if (pos.x < GetSize().GetWidth() / 9) {
626 scroll_x = 20;
627 } else if (pos.x < GetSize().GetWidth() / 6) {
628 scroll_x = 5;
629 } else if (pos.x > 8 * GetSize().GetWidth() / 9) {
630 scroll_x = -20;
631 } else if (pos.x > 5 * GetSize().GetWidth() / 6) {
632 scroll_x = -5;
633 } else {
634 scroll_x = 0;
635 }
636 if (pos.y < GetSize().GetHeight() / 9) {
637 scroll_y = 20;
638 } else if (pos.y < GetSize().GetHeight() / 6) {
639 scroll_y = 5;
640 } else if (pos.y > 8 * GetSize().GetHeight() / 9) {
641 scroll_y = -20;
642 } else if (pos.y > 5 * GetSize().GetHeight() / 6) {
643 scroll_y = -5;
644 } else {
645 scroll_y = 0;
646 }
647
648 SetScrollSpeed(scroll_x, scroll_y);
649}
650
651wxPoint SubwayMap::MapPosToRenderPos(wxPoint pos) const {
652 return {static_cast<int>(pos.x * render_width_ * zoom_ /
653 map_image_.GetSize().GetWidth() +
654 zoom_x_),
655 static_cast<int>(pos.y * render_width_ * zoom_ /
656 map_image_.GetSize().GetWidth() +
657 zoom_y_)};
658}
659
660wxPoint SubwayMap::MapPosToVirtualPos(wxPoint pos) const {
661 return {static_cast<int>(pos.x * render_width_ * zoom_ /
662 map_image_.GetSize().GetWidth()),
663 static_cast<int>(pos.y * render_width_ * zoom_ /
664 map_image_.GetSize().GetWidth())};
665}
666
667wxPoint SubwayMap::RenderPosToMapPos(wxPoint pos) const {
668 return {
669 std::clamp(static_cast<int>((pos.x - zoom_x_) * map_image_.GetWidth() /
670 render_width_ / zoom_),
671 0, map_image_.GetWidth() - 1),
672 std::clamp(static_cast<int>((pos.y - zoom_y_) * map_image_.GetWidth() /
673 render_width_ / zoom_),
674 0, map_image_.GetHeight() - 1)};
675}
676
677void SubwayMap::SetZoomPos(wxPoint pos) {
678 if (render_width_ * zoom_ <= GetSize().GetWidth()) {
679 zoom_x_ = (GetSize().GetWidth() - render_width_ * zoom_) / 2;
680 } else {
681 zoom_x_ = std::clamp(
682 pos.x, GetSize().GetWidth() - static_cast<int>(render_width_ * zoom_),
683 0);
684 }
685 if (render_height_ * zoom_ <= GetSize().GetHeight()) {
686 zoom_y_ = (GetSize().GetHeight() - render_height_ * zoom_) / 2;
687 } else {
688 zoom_y_ = std::clamp(
689 pos.y, GetSize().GetHeight() - static_cast<int>(render_height_ * zoom_),
690 0);
691 }
692}
693
694void SubwayMap::SetScrollSpeed(int scroll_x, int scroll_y) {
695 bool should_timer = (scroll_x != 0 || scroll_y != 0);
696 if (should_timer != scroll_timer_->IsRunning()) {
697 if (should_timer) {
698 scroll_timer_->Start(1000 / 60);
699 } else {
700 scroll_timer_->Stop();
701 }
702 }
703
704 scroll_x_ = scroll_x;
705 scroll_y_ = scroll_y;
706}
707
708void SubwayMap::SetZoom(double zoom, wxPoint static_point) {
709 wxPoint map_pos = RenderPosToMapPos(static_point);
710 zoom_ = zoom;
711
712 wxPoint virtual_pos = MapPosToVirtualPos(map_pos);
713 SetZoomPos(-(virtual_pos - static_point));
714
715 Refresh();
716
717 zoom_slider_->SetValue((zoom - 1.0) / 0.25);
718}
719
720quadtree::Box<float> SubwayMap::GetItemBox::operator()(const int &id) const {
721 const SubwayItem &subway_item = GD_GetSubwayItem(id);
722 return {static_cast<float>(subway_item.x), static_cast<float>(subway_item.y),
723 AREA_ACTUAL_SIZE, AREA_ACTUAL_SIZE};
724}