/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "PseudoMiniMapPanel.h" #include "GameInterface/MessagePasser.h" #include "GameInterface/Messages.h" #include "ScenarioEditor/Tools/Common/Tools.h" #include #include #include #include #include #include namespace { const int PanelRadius = 64 + 1; const wxPoint PanelCenter = wxPoint(PanelRadius + 1, PanelRadius + 1); const wxPoint ScreenToneOffset(-2 * PanelRadius, -2 * PanelRadius); const wxPen Rim(*wxBLACK, 3); const wxPen BackgroundMask(*wxBLACK, 2 * PanelRadius); bool Within(const wxPoint& test, const wxPoint& center, int radius) { int dx = abs(test.x - center.x); if (dx > radius) return false; int dy = abs(test.y - center.y); if (dy > radius) return false; if (dx + dy <= radius) return true; return dx * dx + dy * dy <= radius * radius; } } PseudoMiniMapPanel::PseudoMiniMapPanel(wxWindow* parent, int currentSize) : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(PanelRadius * 2 + 1, PanelRadius * 2 + 1)), m_CurrentSize(currentSize), m_ScreenTones(), m_LastMousePos(-1, -1), m_Dragging(false), m_SelectionRadius(PanelRadius), m_SelectionCenter(PanelCenter), m_SameOrGrowing(true), m_NewSize(currentSize) { AtlasMessage::qRasterizeMinimap qryBackground; qryBackground.Post(); int dim = qryBackground.dimension; std::vector imageBytes = *qryBackground.imageBytes; // Data is destined for a wxImage, which uses free. uint8_t* data = new uint8_t[imageBytes.size()]; std::copy(imageBytes.cbegin(), imageBytes.cend(), data); m_Background = wxImage(dim, dim, data); m_Background.Rescale(PanelRadius * 2, PanelRadius * 2, wxIMAGE_QUALITY_BOX_AVERAGE); m_Backgrounds[PanelRadius] = wxBitmap(m_Background); SetBackgroundStyle(wxBG_STYLE_PAINT); } wxPoint PseudoMiniMapPanel::GetOffset() const { // Since offset is from center, amplitude is (at most) half the largest size. int size = std::max(m_CurrentSize, m_NewSize) / 2; // If the map is growing, the display is opposite what the actual offset is. float scalar = (m_SameOrGrowing ? 1.0 : -1.0) / PanelRadius * size; // Rebase offsets to center. int hOffset = m_SelectionCenter.x - PanelCenter.x; int vOffset = m_SelectionCenter.y - PanelCenter.y; return wxPoint(scalar * hOffset, scalar * vOffset); } void PseudoMiniMapPanel::OnNewSize(wxCommandEvent& evt) { if (!evt.IsSelection()) return; evt.Skip(); m_NewSize = wxAtoi(static_cast(evt.GetClientObject())->GetData()); m_SameOrGrowing = m_NewSize >= m_CurrentSize; m_SelectionRadius = std::min(m_NewSize, m_CurrentSize) * PanelRadius / std::max(m_NewSize, m_CurrentSize); if (!m_SameOrGrowing && m_ScreenTones.find(m_SelectionRadius) == m_ScreenTones.cend()) { wxImage overlay = wxImage(PanelRadius * 4, PanelRadius * 4); overlay.InitAlpha(); wxGraphicsContext* gc = wxGraphicsContext::Create(overlay); gc->SetBrush(*wxGREY_BRUSH); gc->DrawRectangle(0, 0, PanelRadius * 4, PanelRadius * 4); gc->SetBrush(*wxBLACK_BRUSH); gc->DrawEllipse(PanelRadius * 2 - m_SelectionRadius, PanelRadius * 2 - m_SelectionRadius, m_SelectionRadius * 2, m_SelectionRadius * 2); gc->SetPen(*wxWHITE_PEN); gc->DrawEllipse(PanelRadius * 2 - m_SelectionRadius, PanelRadius * 2 - m_SelectionRadius, m_SelectionRadius * 2, m_SelectionRadius * 2); delete gc; // Black -> Converted to transparent. // White -> converted to black. overlay.ConvertColourToAlpha(0, 0, 0); m_ScreenTones[m_SelectionRadius] = wxBitmap(overlay); } else if (m_SameOrGrowing && m_Backgrounds.find(m_SelectionRadius) == m_Backgrounds.cend()) { wxImage rescaled = wxImage(m_Background); rescaled.Rescale(2 * m_SelectionRadius, 2 * m_SelectionRadius, wxIMAGE_QUALITY_BOX_AVERAGE); m_Backgrounds[m_SelectionRadius] = wxBitmap(rescaled); } Refresh(); } void PseudoMiniMapPanel::OnMouseDown(wxMouseEvent& evt) { // Capture on button-down, so we can respond even when the mouse // moves off the window if (!m_Dragging && evt.ButtonDown() && Within(evt.GetPosition(), PanelCenter, PanelRadius) && Within(evt.GetPosition(), m_SelectionCenter, m_SelectionRadius)) { m_LastMousePos = evt.GetPosition(); m_Dragging = true; } } void PseudoMiniMapPanel::OnMouseUp(wxMouseEvent& evt) { if (m_Dragging && !(evt.ButtonIsDown(wxMOUSE_BTN_LEFT) || evt.ButtonIsDown(wxMOUSE_BTN_MIDDLE) || evt.ButtonIsDown(wxMOUSE_BTN_RIGHT)) ) { m_Dragging = false; } } void PseudoMiniMapPanel::OnMouseMove(wxMouseEvent& evt) { if (m_Dragging && evt.Dragging()) { if (m_LastMousePos == evt.GetPosition()) return; wxPoint delta = evt.GetPosition() - m_LastMousePos; wxPoint moved = m_SelectionCenter + delta; if (!Within(moved, PanelCenter, PanelRadius)) return; m_SelectionCenter = moved; m_LastMousePos = evt.GetPosition(); Refresh(); } } void PseudoMiniMapPanel::OnMouseLeave(wxMouseEvent& WXUNUSED(evt)) { m_Dragging = false; } void PseudoMiniMapPanel::PaintEvent(wxPaintEvent& WXUNUSED(evt)) { wxAutoBufferedPaintDC dca(this); // Background must be grabbed from paint dc, not gc, or color may be transparent. wxColor background = dca.GetBackground().GetColour(); wxGCDC dc(dca); if (m_SameOrGrowing) { dc.DrawBitmap(m_Backgrounds[m_SelectionRadius], m_SelectionCenter - wxSize(m_SelectionRadius, m_SelectionRadius)); dc.SetBrush(*wxTRANSPARENT_BRUSH); dc.SetPen(BackgroundMask); dc.DrawCircle(m_SelectionCenter, PanelRadius + m_SelectionRadius); const wxPen BorderPen(*wxWHITE, 2); dc.SetPen(BorderPen); dc.DrawCircle(m_SelectionCenter, m_SelectionRadius); } else { dc.DrawBitmap(m_Backgrounds[PanelRadius], 0, 0); // "fade out" trimmed areas by drawing a screentone ring ring. dc.DrawBitmap(m_ScreenTones[m_SelectionRadius], ScreenToneOffset + m_SelectionCenter); } // Centering markers. dc.SetBrush(*wxBLACK_BRUSH); dc.SetPen(*wxBLACK_PEN); dc.DrawCircle(m_SelectionCenter, 2); dc.SetPen(*wxWHITE_PEN); dc.DrawLine(PanelRadius - 10, PanelRadius, PanelRadius + 10, PanelRadius); dc.DrawLine(PanelRadius, PanelRadius + 10, PanelRadius, PanelRadius - 10); // Round border. dc.SetBrush(*wxTRANSPARENT_BRUSH); dc.SetPen(Rim); dc.DrawCircle(PanelCenter, PanelRadius - 1); wxPen mask(background, PanelRadius); dc.SetPen(mask); dc.DrawCircle(PanelCenter, PanelRadius + PanelRadius / 2 - 1); } void PseudoMiniMapPanel::EraseBackground(wxEraseEvent& WXUNUSED(evt)) { // Do nothing - don't erase to remove flicker. } BEGIN_EVENT_TABLE(PseudoMiniMapPanel, wxPanel) EVT_LEAVE_WINDOW(PseudoMiniMapPanel::OnMouseUp) EVT_LEFT_DOWN(PseudoMiniMapPanel::OnMouseDown) EVT_LEFT_UP(PseudoMiniMapPanel::OnMouseUp) EVT_RIGHT_DOWN(PseudoMiniMapPanel::OnMouseDown) EVT_RIGHT_UP(PseudoMiniMapPanel::OnMouseUp) EVT_MIDDLE_DOWN(PseudoMiniMapPanel::OnMouseDown) EVT_MIDDLE_UP(PseudoMiniMapPanel::OnMouseUp) EVT_MOTION(PseudoMiniMapPanel::OnMouseMove) EVT_LEAVE_WINDOW(PseudoMiniMapPanel::OnMouseLeave) EVT_PAINT(PseudoMiniMapPanel::PaintEvent) END_EVENT_TABLE()