2025-08-08 12:04:43 -07:00
|
|
|
/* Copyright (C) 2025 Wildfire Games.
|
2023-12-02 16:30:12 -08:00
|
|
|
* This file is part of 0 A.D.
|
2020-07-20 19:08:50 -07:00
|
|
|
*
|
2023-12-02 16:30:12 -08:00
|
|
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
2020-07-20 19:08:50 -07:00
|
|
|
* 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.
|
|
|
|
|
*
|
2023-12-02 16:30:12 -08:00
|
|
|
* 0 A.D. is distributed in the hope that it will be useful,
|
2020-07-20 19:08:50 -07:00
|
|
|
* 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
|
2023-12-02 16:30:12 -08:00
|
|
|
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
2020-07-20 19:08:50 -07:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "precompiled.h"
|
|
|
|
|
|
|
|
|
|
#include "PseudoMiniMapPanel.h"
|
|
|
|
|
|
2025-08-08 12:04:43 -07:00
|
|
|
#include "tools/atlas/GameInterface/Messages.h"
|
|
|
|
|
#include "tools/atlas/GameInterface/Shareable.h"
|
2020-07-20 19:08:50 -07:00
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <cmath>
|
2025-08-08 12:04:43 -07:00
|
|
|
#include <cstdint>
|
|
|
|
|
#include <vector>
|
|
|
|
|
#include <wx/brush.h>
|
|
|
|
|
#include <wx/clntdata.h>
|
|
|
|
|
#include <wx/colour.h>
|
2020-07-20 19:08:50 -07:00
|
|
|
#include <wx/dcbuffer.h>
|
|
|
|
|
#include <wx/dcgraph.h>
|
|
|
|
|
#include <wx/defs.h>
|
|
|
|
|
#include <wx/event.h>
|
2025-08-08 12:04:43 -07:00
|
|
|
#include <wx/graphics.h>
|
|
|
|
|
#include <wx/mousestate.h>
|
|
|
|
|
#include <wx/pen.h>
|
|
|
|
|
#include <wx/tbarbase.h>
|
|
|
|
|
#include <wx/wxcrt.h>
|
|
|
|
|
|
|
|
|
|
class wxWindow;
|
2020-07-20 19:08:50 -07:00
|
|
|
|
|
|
|
|
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<uint8_t> 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<wxStringClientData*>(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()
|