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