From 79424e86dd7aa28c1b25868d86fa8ebffc801593 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Mon, 10 Mar 2025 12:45:14 -0400 Subject: Optimized AreaPopup indicators The list of indicators and the size of the window are calculated only when necessary (new connection, DPI changed, or hunt panel settings changed) and otherwise all we do is redraw the image. --- src/area_popup.cpp | 147 ++++++++++++++++++++++++++------------------------ src/area_popup.h | 25 +++++++++ src/tracker_frame.cpp | 16 +++--- src/tracker_frame.h | 1 + src/tracker_panel.cpp | 15 ++++-- src/tracker_panel.h | 2 +- 6 files changed, 125 insertions(+), 81 deletions(-) diff --git a/src/area_popup.cpp b/src/area_popup.cpp index d7f45b6..a8e6004 100644 --- a/src/area_popup.cpp +++ b/src/area_popup.cpp @@ -27,28 +27,26 @@ AreaPopup::AreaPopup(wxWindow* parent, int area_id) Bind(wxEVT_PAINT, &AreaPopup::OnPaint, this); Bind(wxEVT_DPI_CHANGED, &AreaPopup::OnDPIChanged, this); - UpdateIndicators(); + ResetIndicators(); } -void AreaPopup::UpdateIndicators() { - const MapArea& map_area = GD_GetMapArea(area_id_); +void AreaPopup::ResetIndicators() { + indicators_.clear(); + const MapArea& map_area = GD_GetMapArea(area_id_); wxFont the_font = GetFont().Scale(GetDPIScaleFactor()); + TrackerPanel* tracker_panel = dynamic_cast(GetParent()); // Start calculating extents. wxMemoryDC mem_dc; mem_dc.SetFont(the_font.Bold()); - wxSize header_extent = mem_dc.GetTextExtent(map_area.name); + header_extent_ = mem_dc.GetTextExtent(map_area.name); - int acc_height = header_extent.GetHeight() + FromDIP(20); + int acc_height = header_extent_.GetHeight() + FromDIP(20); int col_width = 0; mem_dc.SetFont(the_font); - TrackerPanel* tracker_panel = dynamic_cast(GetParent()); - - std::vector real_locations; - for (int section_id = 0; section_id < map_area.locations.size(); section_id++) { const Location& location = map_area.locations.at(section_id); @@ -67,7 +65,7 @@ void AreaPopup::UpdateIndicators() { } } - real_locations.push_back(section_id); + indicators_.emplace_back(section_id, kLOCATION, acc_height); wxSize item_extent = mem_dc.GetTextExtent(location.name); int item_height = @@ -85,6 +83,8 @@ void AreaPopup::UpdateIndicators() { continue; } + indicators_.emplace_back(painting_id, kPAINTING, acc_height); + const PaintingExit& painting = GD_GetPaintingExit(painting_id); wxSize item_extent = mem_dc.GetTextExtent(painting.display_name); int item_height = @@ -98,84 +98,91 @@ void AreaPopup::UpdateIndicators() { } int item_width = col_width + FromDIP(10 + 32); - int full_width = std::max(header_extent.GetWidth(), item_width) + FromDIP(20); + full_width_ = std::max(header_extent_.GetWidth(), item_width) + FromDIP(20); + full_height_ = acc_height; Fit(); - SetVirtualSize(full_width, acc_height); + SetVirtualSize(full_width_, full_height_); + + UpdateIndicators(); +} + +void AreaPopup::UpdateIndicators() { + const MapArea& map_area = GD_GetMapArea(area_id_); + wxFont the_font = GetFont().Scale(GetDPIScaleFactor()); + TrackerPanel* tracker_panel = dynamic_cast(GetParent()); + + rendered_ = wxBitmap(full_width_, full_height_); - rendered_ = wxBitmap(full_width, acc_height); + wxMemoryDC mem_dc; mem_dc.SelectObject(rendered_); mem_dc.SetPen(*wxTRANSPARENT_PEN); mem_dc.SetBrush(*wxBLACK_BRUSH); - mem_dc.DrawRectangle({0, 0}, {full_width, acc_height}); + mem_dc.DrawRectangle({0, 0}, {full_width_, full_height_}); mem_dc.SetFont(the_font.Bold()); mem_dc.SetTextForeground(*wxWHITE); mem_dc.DrawText(map_area.name, - {(full_width - header_extent.GetWidth()) / 2, FromDIP(10)}); - - int cur_height = header_extent.GetHeight() + FromDIP(20); + {(full_width_ - header_extent_.GetWidth()) / 2, FromDIP(10)}); mem_dc.SetFont(the_font); - for (int section_id : real_locations) { - const Location& location = map_area.locations.at(section_id); - - bool checked = false; - if (IsLocationWinCondition(location)) { - checked = AP_HasReachedGoal(); - } else if (tracker_panel->IsPanelsMode()) { - const Panel& panel = GD_GetPanel(*location.single_panel); - if (panel.non_counting) { - checked = AP_HasCheckedGameLocation(location.ap_location_id); - } else { - checked = tracker_panel->GetSolvedPanels().contains(panel.nodepath); + for (const IndicatorInfo& indicator : indicators_) { + switch (indicator.type) { + case kLOCATION: { + const Location& location = map_area.locations.at(indicator.id); + + bool checked = false; + if (IsLocationWinCondition(location)) { + checked = AP_HasReachedGoal(); + } else if (tracker_panel->IsPanelsMode()) { + const Panel& panel = GD_GetPanel(*location.single_panel); + if (panel.non_counting) { + checked = AP_HasCheckedGameLocation(location.ap_location_id); + } else { + checked = tracker_panel->GetSolvedPanels().contains(panel.nodepath); + } + } else { + checked = AP_HasCheckedGameLocation(location.ap_location_id) || + (location.hunt && + AP_HasCheckedHuntPanel(location.ap_location_id)); + } + + const wxBitmap* eye_ptr = checked ? checked_eye_ : unchecked_eye_; + + mem_dc.DrawBitmap(*eye_ptr, {FromDIP(10), indicator.y}); + + bool reachable = IsLocationReachable(location.ap_location_id); + const wxColour* text_color = reachable ? wxWHITE : wxRED; + mem_dc.SetTextForeground(*text_color); + + wxSize item_extent = mem_dc.GetTextExtent(location.name); + mem_dc.DrawText( + location.name, + {FromDIP(10 + 32 + 10), + indicator.y + (FromDIP(32) - mem_dc.GetFontMetrics().height) / 2}); + + break; } - } else { - checked = - AP_HasCheckedGameLocation(location.ap_location_id) || - (location.hunt && AP_HasCheckedHuntPanel(location.ap_location_id)); - } - - const wxBitmap* eye_ptr = checked ? checked_eye_ : unchecked_eye_; + case kPAINTING: { + const PaintingExit& painting = GD_GetPaintingExit(indicator.id); - mem_dc.DrawBitmap(*eye_ptr, {FromDIP(10), cur_height}); + bool reachable = IsPaintingReachable(indicator.id); + const wxColour* text_color = reachable ? wxWHITE : wxRED; + mem_dc.SetTextForeground(*text_color); - bool reachable = IsLocationReachable(location.ap_location_id); - const wxColour* text_color = reachable ? wxWHITE : wxRED; - mem_dc.SetTextForeground(*text_color); + bool checked = reachable && AP_IsPaintingChecked(painting.internal_id); + const wxBitmap* eye_ptr = checked ? checked_owl_ : unchecked_owl_; + mem_dc.DrawBitmap(*eye_ptr, {FromDIP(10), indicator.y}); - wxSize item_extent = mem_dc.GetTextExtent(location.name); - mem_dc.DrawText( - location.name, - {FromDIP(10 + 32 + 10), - cur_height + (FromDIP(32) - mem_dc.GetFontMetrics().height) / 2}); - - cur_height += FromDIP(10 + 32); - } + wxSize item_extent = mem_dc.GetTextExtent(painting.display_name); + mem_dc.DrawText( + painting.display_name, + {FromDIP(10 + 32 + 10), + indicator.y + (FromDIP(32) - mem_dc.GetFontMetrics().height) / 2}); - if (AP_IsPaintingShuffle() && !tracker_panel->IsPanelsMode()) { - for (int painting_id : map_area.paintings) { - if (IsPaintingPostgame(painting_id)) { - continue; + break; } - - const PaintingExit& painting = GD_GetPaintingExit(painting_id); - - bool reachable = IsPaintingReachable(painting_id); - const wxColour* text_color = reachable ? wxWHITE : wxRED; - mem_dc.SetTextForeground(*text_color); - - bool checked = reachable && AP_IsPaintingChecked(painting.internal_id); - const wxBitmap* eye_ptr = checked ? checked_owl_ : unchecked_owl_; - mem_dc.DrawBitmap(*eye_ptr, {FromDIP(10), cur_height}); - - wxSize item_extent = mem_dc.GetTextExtent(painting.display_name); - mem_dc.DrawText(painting.display_name, - {FromDIP(10 + 32 + 10), - cur_height + (FromDIP(32) - mem_dc.GetFontMetrics().height) / 2}); - - cur_height += FromDIP(10 + 32); } } } @@ -190,7 +197,7 @@ void AreaPopup::OnPaint(wxPaintEvent& event) { void AreaPopup::OnDPIChanged(wxDPIChangedEvent& event) { LoadIcons(); - UpdateIndicators(); + ResetIndicators(); event.Skip(); } diff --git a/src/area_popup.h b/src/area_popup.h index 2401e4e..50e10e8 100644 --- a/src/area_popup.h +++ b/src/area_popup.h @@ -7,13 +7,32 @@ #include #endif +#include + class AreaPopup : public wxScrolledCanvas { public: AreaPopup(wxWindow* parent, int area_id); + void ResetIndicators(); void UpdateIndicators(); private: + enum IndicatorType { + kLOCATION, + kPAINTING, + }; + + struct IndicatorInfo { + // For locations, the id is an index into the map area's locations list. + // For paintings, it is a real painting id. + int id; + IndicatorType type; + int y; + + IndicatorInfo(int id, IndicatorType type, int y) + : id(id), type(type), y(y) {} + }; + void OnPaint(wxPaintEvent& event); void OnDPIChanged(wxDPIChangedEvent& event); @@ -26,6 +45,12 @@ class AreaPopup : public wxScrolledCanvas { const wxBitmap* unchecked_owl_; const wxBitmap* checked_owl_; + int full_width_ = 0; + int full_height_ = 0; + wxSize header_extent_; + + std::vector indicators_; + wxBitmap rendered_; }; diff --git a/src/tracker_frame.cpp b/src/tracker_frame.cpp index 6a4ab2e..fa68582 100644 --- a/src/tracker_frame.cpp +++ b/src/tracker_frame.cpp @@ -255,8 +255,9 @@ void TrackerFrame::OnSettings(wxCommandEvent &event) { GetTrackerConfig().track_position = dlg.GetTrackPosition(); GetTrackerConfig().Save(); - UpdateIndicators( - StateUpdate{.cleared_locations = true, .player_position = true}); + UpdateIndicators(StateUpdate{.cleared_locations = true, + .player_position = true, + .changed_settings = true}); } } @@ -328,7 +329,7 @@ void TrackerFrame::OnSashPositionChanged(wxSplitterEvent& event) { } void TrackerFrame::OnStateReset(wxCommandEvent &event) { - tracker_panel_->UpdateIndicators(); + tracker_panel_->UpdateIndicators(/*reset=*/true); achievements_pane_->UpdateIndicators(); items_pane_->ResetIndicators(); options_pane_->OnConnect(); @@ -350,7 +351,7 @@ void TrackerFrame::OnStateChanged(StateChangedEvent &event) { panels_panel_->SetPanelsMode(); notebook_->AddPage(panels_panel_, "Panels"); } - panels_panel_->UpdateIndicators(); + panels_panel_->UpdateIndicators(/*reset=*/false); if (notebook_->GetSelection() == 2) { Refresh(); } @@ -361,10 +362,13 @@ void TrackerFrame::OnStateChanged(StateChangedEvent &event) { if (!state.items.empty() || !state.paintings.empty() || state.cleared_locations || (state.hunt_panels && GetTrackerConfig().show_hunt_panels)) { - tracker_panel_->UpdateIndicators(); + // TODO: The only real reason to reset tracker_panel during an active + // connection is if the hunt panels setting changes. If we remove hunt + // panels later, we can get rid of this. + tracker_panel_->UpdateIndicators(/*reset=*/state.changed_settings); subway_map_->UpdateIndicators(); if (panels_panel_ != nullptr) { - panels_panel_->UpdateIndicators(); + panels_panel_->UpdateIndicators(/*reset=*/false); } Refresh(); } else if (state.player_position && GetTrackerConfig().track_position) { diff --git a/src/tracker_frame.h b/src/tracker_frame.h index 7704208..131c7b8 100644 --- a/src/tracker_frame.h +++ b/src/tracker_frame.h @@ -57,6 +57,7 @@ struct StateUpdate { bool cleared_locations = false; bool hunt_panels = false; bool player_position = false; + bool changed_settings = false; }; class StateChangedEvent : public wxEvent { diff --git a/src/tracker_panel.cpp b/src/tracker_panel.cpp index b4e6697..64e6ab3 100644 --- a/src/tracker_panel.cpp +++ b/src/tracker_panel.cpp @@ -50,13 +50,19 @@ TrackerPanel::TrackerPanel(wxWindow *parent) : wxPanel(parent, wxID_ANY) { Bind(wxEVT_MOTION, &TrackerPanel::OnMouseMove, this); } -void TrackerPanel::UpdateIndicators() { +void TrackerPanel::UpdateIndicators(bool reset) { if (panels_mode_ && !savedata_path_) { solved_panels_ = IPC_GetSolvedPanels(); } - for (AreaIndicator &area : areas_) { - area.popup->UpdateIndicators(); + if (reset) { + for (AreaIndicator &area : areas_) { + area.popup->ResetIndicators(); + } + } else { + for (AreaIndicator &area : areas_) { + area.popup->UpdateIndicators(); + } } Redraw(); @@ -74,6 +80,7 @@ void TrackerPanel::SetSavedataPath(std::string savedata_path) { savedata_path_ = savedata_path; panels_mode_ = true; + UpdateIndicators(/*reset=*/true); RefreshSavedata(); } @@ -90,7 +97,7 @@ void TrackerPanel::RefreshSavedata() { } } - UpdateIndicators(); + UpdateIndicators(/*reset=*/false); Refresh(); } diff --git a/src/tracker_panel.h b/src/tracker_panel.h index b7067f5..ae89a35 100644 --- a/src/tracker_panel.h +++ b/src/tracker_panel.h @@ -17,7 +17,7 @@ class TrackerPanel : public wxPanel { public: TrackerPanel(wxWindow *parent); - void UpdateIndicators(); + void UpdateIndicators(bool reset); void SetPanelsMode(); -- cgit 1.4.1