From c87281384f716c8ca2f57bb8ea65d6d01bb06fae Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Sat, 18 May 2024 20:33:16 -0400 Subject: Zoom in and scroll subway map --- src/subway_map.cpp | 171 ++++++++++++++++++++++++++++++++++++++++++----------- src/subway_map.h | 15 +++++ 2 files changed, 151 insertions(+), 35 deletions(-) (limited to 'src') diff --git a/src/subway_map.cpp b/src/subway_map.cpp index 5a4be4b..b02c616 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp @@ -12,11 +12,7 @@ constexpr int AREA_ACTUAL_SIZE = 21; constexpr int OWL_ACTUAL_SIZE = 32; -enum class ItemDrawType { - kNone, - kBox, - kOwl -}; +enum class ItemDrawType { kNone, kBox, kOwl }; SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { SetBackgroundStyle(wxBG_STYLE_PAINT); @@ -50,8 +46,13 @@ SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { Redraw(); + scroll_timer_ = new wxTimer(this); + Bind(wxEVT_PAINT, &SubwayMap::OnPaint, this); Bind(wxEVT_MOTION, &SubwayMap::OnMouseMove, this); + Bind(wxEVT_MOUSEWHEEL, &SubwayMap::OnMouseScroll, this); + Bind(wxEVT_LEAVE_WINDOW, &SubwayMap::OnMouseLeave, this); + Bind(wxEVT_TIMER, &SubwayMap::OnTimer, this); } void SubwayMap::OnConnect() { @@ -67,7 +68,8 @@ void SubwayMap::OnConnect() { tagged[tag].push_back(subway_item.id); } - if (!AP_IsSunwarpShuffle() && subway_item.sunwarp && subway_item.sunwarp->type != SubwaySunwarpType::kFinal) { + if (!AP_IsSunwarpShuffle() && subway_item.sunwarp && + subway_item.sunwarp->type != SubwaySunwarpType::kFinal) { std::ostringstream tag; tag << "sunwarp" << subway_item.sunwarp->dots; @@ -108,7 +110,7 @@ void SubwayMap::UpdateIndicators() { } void SubwayMap::UpdateSunwarp(SubwaySunwarp from_sunwarp, - SubwaySunwarp to_sunwarp) { + SubwaySunwarp to_sunwarp) { networks_.AddLink(GD_GetSubwayItemForSunwarp(from_sunwarp), GD_GetSubwayItemForSunwarp(to_sunwarp)); } @@ -119,7 +121,9 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { } wxBufferedPaintDC dc(this); - dc.DrawBitmap(rendered_, 0, 0); + dc.SetBackground(*wxWHITE_BRUSH); + dc.Clear(); + dc.DrawBitmap(rendered_, zoom_x_, zoom_y_); if (hovered_item_) { const SubwayItem &subway_item = GD_GetSubwayItem(*hovered_item_); @@ -139,7 +143,7 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { col_width = item_extent.GetWidth(); } } - + int item_width = col_width + 10 + 32; int full_width = item_width + 20; @@ -162,7 +166,7 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { int cur_height = 10; - for (const auto& [text, obtained] : report) { + for (const auto &[text, obtained] : report) { wxBitmap *eye_ptr = obtained ? &unchecked_eye_ : &checked_eye_; dc.DrawBitmap(*eye_ptr, popup_pos + wxPoint{10, cur_height}); @@ -191,7 +195,7 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { {item1.x + AREA_ACTUAL_SIZE / 2, item1.y + AREA_ACTUAL_SIZE / 2}); wxPoint item2_pos = MapPosToRenderPos( {item2.x + AREA_ACTUAL_SIZE / 2, item2.y + AREA_ACTUAL_SIZE / 2}); - + int left = std::min(item1_pos.x, item2_pos.x); int top = std::min(item1_pos.y, item2_pos.y); int right = std::max(item1_pos.x, item2_pos.x); @@ -242,11 +246,11 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { } dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4)); - dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, halfheight * 2, - start, end); + dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, + halfheight * 2, start, end); dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); - dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, halfheight * 2, - start, end); + dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, + halfheight * 2, start, end); } } } @@ -257,7 +261,7 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { void SubwayMap::OnMouseMove(wxMouseEvent &event) { wxPoint mouse_pos = RenderPosToMapPos(event.GetPosition()); - + std::vector hovered = tree_->query( {static_cast(mouse_pos.x), static_cast(mouse_pos.y), 2, 2}); std::optional new_hovered_item; @@ -271,9 +275,65 @@ void SubwayMap::OnMouseMove(wxMouseEvent &event) { Refresh(); } + int scroll_x; + int scroll_y; + if (event.GetPosition().x < GetSize().GetWidth() / 9) { + scroll_x = 20; + } else if (event.GetPosition().x < GetSize().GetWidth() / 6) { + scroll_x = 5; + } else if (event.GetPosition().x > 8 * GetSize().GetWidth() / 9) { + scroll_x = -20; + } else if (event.GetPosition().x > 5 * GetSize().GetWidth() / 6) { + scroll_x = -5; + } else { + scroll_x = 0; + } + if (event.GetPosition().y < GetSize().GetHeight() / 9) { + scroll_y = 20; + } else if (event.GetPosition().y < GetSize().GetHeight() / 6) { + scroll_y = 5; + } else if (event.GetPosition().y > 8 * GetSize().GetHeight() / 9) { + scroll_y = -20; + } else if (event.GetPosition().y > 5 * GetSize().GetHeight() / 6) { + scroll_y = -5; + } else { + scroll_y = 0; + } + + SetScrollSpeed(scroll_x, scroll_y); + event.Skip(); } +void SubwayMap::OnMouseScroll(wxMouseEvent &event) { + double new_zoom = zoom_; + if (event.GetWheelRotation() > 0) { + new_zoom = std::min(3.0, zoom_ + 0.25); + } else { + new_zoom = std::max(1.0, zoom_ - 0.25); + } + + if (zoom_ != new_zoom) { + wxPoint map_pos = RenderPosToMapPos(event.GetPosition()); + zoom_ = new_zoom; + + wxPoint virtual_pos = MapPosToVirtualPos(map_pos); + SetZoomPos(-(virtual_pos - event.GetPosition())); + + Redraw(); + Refresh(); + } + + event.Skip(); +} + +void SubwayMap::OnMouseLeave(wxMouseEvent &event) { SetScrollSpeed(0, 0); } + +void SubwayMap::OnTimer(wxTimerEvent &event) { + SetZoomPos({zoom_x_ + scroll_x_, zoom_y_ + scroll_y_}); + Refresh(); +} + void SubwayMap::Redraw() { wxSize panel_size = GetSize(); wxSize image_size = map_image_.GetSize(); @@ -294,10 +354,10 @@ void SubwayMap::Redraw() { render_x_ = (panel_size.GetWidth() - render_width_) / 2; } - rendered_ = wxBitmap( - map_image_ - .Scale(render_width_, render_height_, wxIMAGE_QUALITY_BILINEAR) - .Size(panel_size, {render_x_, render_y_}, 255, 255, 255)); + rendered_ = wxBitmap(map_image_.Scale( + render_width_ * zoom_, render_height_ * zoom_, wxIMAGE_QUALITY_BILINEAR)); + + SetZoomPos({zoom_x_, zoom_y_}); wxMemoryDC dc; dc.SelectObject(rendered_); @@ -353,10 +413,10 @@ void SubwayMap::Redraw() { } } - wxPoint real_area_pos = MapPosToRenderPos({subway_item.x, subway_item.y}); + wxPoint real_area_pos = MapPosToVirtualPos({subway_item.x, subway_item.y}); int real_area_size = - render_width_ * + render_width_ * zoom_ * (draw_type == ItemDrawType::kOwl ? OWL_ACTUAL_SIZE : AREA_ACTUAL_SIZE) / image_size.GetWidth(); if (real_area_size == 0) { @@ -366,32 +426,73 @@ void SubwayMap::Redraw() { if (draw_type == ItemDrawType::kBox) { dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 1)); dc.SetBrush(*brush_color); - dc.DrawRectangle(real_area_pos, - {real_area_size, real_area_size}); + dc.DrawRectangle(real_area_pos, {real_area_size, real_area_size}); } else if (draw_type == ItemDrawType::kOwl) { - wxBitmap owl_bitmap = wxBitmap( - owl_image_.Scale(real_area_size, real_area_size, - wxIMAGE_QUALITY_BILINEAR)); + wxBitmap owl_bitmap = wxBitmap(owl_image_.Scale( + real_area_size, real_area_size, wxIMAGE_QUALITY_BILINEAR)); dc.DrawBitmap(owl_bitmap, real_area_pos); } } } - wxPoint SubwayMap::MapPosToRenderPos(wxPoint pos) const { - return {pos.x * render_width_ / map_image_.GetSize().GetWidth() + render_x_, - pos.y * render_width_ / map_image_.GetSize().GetWidth() + render_y_}; + return {static_cast(pos.x * render_width_ * zoom_ / + map_image_.GetSize().GetWidth() + + zoom_x_), + static_cast(pos.y * render_width_ * zoom_ / + map_image_.GetSize().GetWidth() + + zoom_y_)}; +} + +wxPoint SubwayMap::MapPosToVirtualPos(wxPoint pos) const { + return {static_cast(pos.x * render_width_ * zoom_ / + map_image_.GetSize().GetWidth()), + static_cast(pos.y * render_width_ * zoom_ / + map_image_.GetSize().GetWidth())}; } wxPoint SubwayMap::RenderPosToMapPos(wxPoint pos) const { return { - std::clamp((pos.x - render_x_) * map_image_.GetWidth() / render_width_, 0, - map_image_.GetWidth() - 1), - std::clamp((pos.y - render_y_) * map_image_.GetWidth() / render_width_, 0, - map_image_.GetHeight() - 1)}; + std::clamp(static_cast((pos.x - zoom_x_) * map_image_.GetWidth() / + render_width_ / zoom_), + 0, map_image_.GetWidth() - 1), + std::clamp(static_cast((pos.y - zoom_y_) * map_image_.GetWidth() / + render_width_ / zoom_), + 0, map_image_.GetHeight() - 1)}; +} + +void SubwayMap::SetZoomPos(wxPoint pos) { + if (render_width_ * zoom_ <= GetSize().GetWidth()) { + zoom_x_ = (GetSize().GetWidth() - render_width_ * zoom_) / 2; + } else { + zoom_x_ = std::clamp( + pos.x, GetSize().GetWidth() - static_cast(render_width_ * zoom_), + 0); + } + if (render_height_ * zoom_ <= GetSize().GetHeight()) { + zoom_y_ = (GetSize().GetHeight() - render_height_ * zoom_) / 2; + } else { + zoom_y_ = std::clamp( + pos.y, GetSize().GetHeight() - static_cast(render_height_ * zoom_), + 0); + } +} + +void SubwayMap::SetScrollSpeed(int scroll_x, int scroll_y) { + bool should_timer = (scroll_x != 0 || scroll_y != 0); + if (should_timer != scroll_timer_->IsRunning()) { + if (should_timer) { + scroll_timer_->Start(1000 / 60); + } else { + scroll_timer_->Stop(); + } + } + + scroll_x_ = scroll_x; + scroll_y_ = scroll_y; } -quadtree::Box SubwayMap::GetItemBox::operator()(const int& id) const { +quadtree::Box SubwayMap::GetItemBox::operator()(const int &id) const { const SubwayItem &subway_item = GD_GetSubwayItem(id); return {static_cast(subway_item.x), static_cast(subway_item.y), AREA_ACTUAL_SIZE, AREA_ACTUAL_SIZE}; diff --git a/src/subway_map.h b/src/subway_map.h index 0d26d0b..9b0b43f 100644 --- a/src/subway_map.h +++ b/src/subway_map.h @@ -29,12 +29,19 @@ class SubwayMap : public wxPanel { private: void OnPaint(wxPaintEvent &event); void OnMouseMove(wxMouseEvent &event); + void OnMouseScroll(wxMouseEvent &event); + void OnMouseLeave(wxMouseEvent &event); + void OnTimer(wxTimerEvent &event); void Redraw(); wxPoint MapPosToRenderPos(wxPoint pos) const; + wxPoint MapPosToVirtualPos(wxPoint pos) const; wxPoint RenderPosToMapPos(wxPoint pos) const; + void SetZoomPos(wxPoint pos); + void SetScrollSpeed(int scroll_x, int scroll_y); + wxImage map_image_; wxImage owl_image_; wxBitmap unchecked_eye_; @@ -46,6 +53,14 @@ class SubwayMap : public wxPanel { int render_width_ = 0; int render_height_ = 0; + double zoom_ = 1.0; + int zoom_x_ = 0; // in render space + int zoom_y_ = 0; + + wxTimer* scroll_timer_; + int scroll_x_ = 0; + int scroll_y_ = 0; + struct GetItemBox { quadtree::Box operator()(const int &id) const; }; -- cgit 1.4.1