mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
iwyu 0.26 now want's full declaration of TYPE in case of std::vector<TYPE>, which is good as it allows the header to be self standing. There are some other changes suggested in part of the headers not being updated when they should have been and some due to changes in iwyu itself. Ref: #8086 Signed-off-by: Ralph Sennhauser <ralph.sennhauser@gmail.com>
631 lines
20 KiB
C++
631 lines
20 KiB
C++
/* Copyright (C) 2026 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
|
|
#include "Terrain.h"
|
|
|
|
#include "tools/atlas/AtlasUI/CustomControls/Buttons/ToolButton.h"
|
|
#include "tools/atlas/AtlasUI/General/Observable.h"
|
|
#include "tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.h"
|
|
#include "tools/atlas/AtlasUI/ScenarioEditor/Sections/Common/Sidebar.h"
|
|
#include "tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Brushes.h"
|
|
#include "tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/MiscState.h"
|
|
#include "tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Tools.h"
|
|
#include "tools/atlas/GameInterface/MessagePasser.h"
|
|
#include "tools/atlas/GameInterface/Messages.h"
|
|
#include "tools/atlas/GameInterface/Shareable.h"
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
#include <vector>
|
|
#include <wx/arrstr.h>
|
|
#include <wx/bitmap.h>
|
|
#include <wx/bmpbndl.h>
|
|
#include <wx/bmpbuttn.h>
|
|
#include <wx/bookctrl.h>
|
|
#include <wx/busyinfo.h>
|
|
#include <wx/button.h>
|
|
#include <wx/chartype.h>
|
|
#include <wx/checkbox.h>
|
|
#include <wx/choice.h>
|
|
#include <wx/clntdata.h>
|
|
#include <wx/colour.h>
|
|
#include <wx/gdicmn.h>
|
|
#include <wx/image.h>
|
|
#include <wx/notebook.h>
|
|
#include <wx/object.h>
|
|
#include <wx/panel.h>
|
|
#include <wx/scrolwin.h>
|
|
#include <wx/sizer.h>
|
|
#include <wx/statbmp.h>
|
|
#include <wx/statbox.h>
|
|
#include <wx/stattext.h>
|
|
#include <wx/string.h>
|
|
#include <wx/timer.h>
|
|
#include <wx/toolbar.h>
|
|
#include <wx/translation.h>
|
|
#include <wx/unichar.h>
|
|
#include <wx/window.h>
|
|
#include <wx/wxcrt.h>
|
|
|
|
namespace
|
|
{
|
|
|
|
const int PREVIEW_RELOAD_DELAY_MILLISECONDS = 2000;
|
|
const int PREVIEW_RELOAD_TIMEOUT_DELAY_MILLISECONDS = 200;
|
|
const float PREVIEW_RELOAD_TIMEOUT_THRESHOLD_SECONDS = 0.1f;
|
|
|
|
} // anonymous namespace
|
|
|
|
class TextureNotebook;
|
|
|
|
class TerrainBottomBar : public wxPanel
|
|
{
|
|
public:
|
|
TerrainBottomBar(ScenarioEditor& scenarioEditor, wxWindow* parent);
|
|
void LoadTerrain();
|
|
void OnShutdown();
|
|
private:
|
|
TextureNotebook* m_Textures;
|
|
};
|
|
|
|
enum
|
|
{
|
|
ID_Passability = 1,
|
|
ID_ShowPriorities
|
|
};
|
|
|
|
// Helper function for adding tooltips
|
|
static wxWindow* Tooltipped(wxWindow* window, const wxString& tip)
|
|
{
|
|
window->SetToolTip(tip);
|
|
return window;
|
|
}
|
|
|
|
// Add spaces into the displayed name so there are more wrapping opportunities
|
|
static wxString FormatTextureName(wxString name)
|
|
{
|
|
if (name.Len())
|
|
name[0] = wxToupper(name[0]);
|
|
name.Replace(_T("_"), _T(" "));
|
|
|
|
return name;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
class TexturePreviewPanel : public wxPanel
|
|
{
|
|
private:
|
|
static const int imageWidth = 120;
|
|
static const int imageHeight = 40;
|
|
|
|
public:
|
|
TexturePreviewPanel(wxWindow* parent)
|
|
: wxPanel(parent, wxID_ANY), m_Timer(this)
|
|
{
|
|
m_Conn = g_SelectedTexture.RegisterObserver(0, &TexturePreviewPanel::OnTerrainChange, this);
|
|
m_Sizer = new wxStaticBoxSizer(wxVERTICAL, this, _T("Texture"));
|
|
SetSizer(m_Sizer);
|
|
|
|
// Use placeholder bitmap for now
|
|
m_Sizer->Add(new wxStaticBitmap(m_Sizer->GetStaticBox(), wxID_ANY, wxNullBitmap), wxSizerFlags().Expand().Border(wxALL, 5));
|
|
}
|
|
|
|
void LoadPreview()
|
|
{
|
|
if (m_TextureName.IsEmpty())
|
|
{
|
|
// If we haven't got a texture yet, copy the global
|
|
m_TextureName = g_SelectedTexture;
|
|
}
|
|
|
|
Freeze();
|
|
|
|
m_Sizer->Clear(true);
|
|
|
|
AtlasMessage::qGetTerrainTexturePreview qry((std::wstring)m_TextureName.wc_str(), imageWidth, imageHeight);
|
|
qry.Post();
|
|
|
|
AtlasMessage::sTerrainTexturePreview preview = qry.preview;
|
|
|
|
// Check for invalid/missing texture - shouldn't happen
|
|
if (!wxString(qry.preview->name.c_str()).IsEmpty())
|
|
{
|
|
wxFlexGridSizer* sizer = new wxFlexGridSizer(1, 5, 5);
|
|
|
|
// Construct the wrapped-text label
|
|
wxStaticText* label = new wxStaticText(m_Sizer->GetStaticBox(), wxID_ANY, FormatTextureName(*qry.preview->name), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER);
|
|
label->Wrap(m_Sizer->GetSize().GetX());
|
|
|
|
// cppcheck-suppress "memleak"
|
|
unsigned char* buf = (unsigned char*)(malloc(preview.imageData.GetSize()));
|
|
// imagedata.GetBuffer() gives a Shareable<unsigned char>*, which
|
|
// is stored the same as a unsigned char*, so we can just copy it.
|
|
memcpy(buf, preview.imageData.GetBuffer(), preview.imageData.GetSize());
|
|
wxImage img(qry.preview->imageWidth, qry.preview->imageHeight, buf);
|
|
|
|
wxStaticBitmap* bitmap = new wxStaticBitmap(m_Sizer->GetStaticBox(), wxID_ANY, wxBitmap(img), wxDefaultPosition, wxSize(qry.preview->imageWidth, qry.preview->imageHeight), wxBORDER_SIMPLE);
|
|
|
|
sizer->Add(bitmap, wxSizerFlags().Align(wxALIGN_CENTER));
|
|
sizer->Add(label, wxSizerFlags().Align(wxALIGN_CENTER));
|
|
m_Sizer->Add(sizer, wxSizerFlags().Align(wxALIGN_CENTER).Border(wxALL, 5));
|
|
|
|
// We have to force the sidebar to layout manually
|
|
GetParent()->GetParent()->Layout();
|
|
|
|
if (preview.loaded && m_Timer.IsRunning())
|
|
{
|
|
m_Timer.Stop();
|
|
}
|
|
else if (!preview.loaded && !m_Timer.IsRunning())
|
|
{
|
|
m_Timer.Start(PREVIEW_RELOAD_DELAY_MILLISECONDS);
|
|
}
|
|
}
|
|
|
|
Layout();
|
|
Thaw();
|
|
}
|
|
|
|
void OnTerrainChange(const wxString& texture)
|
|
{
|
|
// Check if texture really changed, to avoid doing this too often
|
|
if (texture != m_TextureName)
|
|
{
|
|
// Load new texture preview
|
|
m_TextureName = texture;
|
|
LoadPreview();
|
|
}
|
|
}
|
|
|
|
void OnTimer(wxTimerEvent& WXUNUSED(evt))
|
|
{
|
|
LoadPreview();
|
|
}
|
|
|
|
private:
|
|
ObservableScopedConnection m_Conn;
|
|
wxStaticBoxSizer* m_Sizer;
|
|
wxTimer m_Timer;
|
|
wxString m_TextureName;
|
|
|
|
DECLARE_EVENT_TABLE();
|
|
};
|
|
|
|
BEGIN_EVENT_TABLE(TexturePreviewPanel, wxPanel)
|
|
EVT_TIMER(wxID_ANY, TexturePreviewPanel::OnTimer)
|
|
END_EVENT_TABLE();
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
TerrainSidebar::TerrainSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebarContainer, wxWindow* bottomBarContainer) :
|
|
Sidebar(scenarioEditor, sidebarContainer, bottomBarContainer)
|
|
{
|
|
wxSizer* scrollSizer = new wxBoxSizer(wxVERTICAL);
|
|
wxScrolledWindow* scrolledWindow = new wxScrolledWindow(this);
|
|
scrolledWindow->SetScrollRate(10, 10);
|
|
scrolledWindow->SetSizer(scrollSizer);
|
|
m_MainSizer->Add(scrolledWindow, wxSizerFlags().Proportion(1).Expand());
|
|
|
|
{
|
|
/////////////////////////////////////////////////////////////////////////
|
|
// Terrain elevation
|
|
wxStaticBoxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, scrolledWindow, _("Elevation tools"));
|
|
wxSizer* gridSizer = new wxGridSizer(4, 5, 5);
|
|
gridSizer->Add(Tooltipped(new ToolButton(scenarioEditor.GetToolManager(), sizer->GetStaticBox(), _("Modify"), _T("AlterElevation"), wxSize(48, -1)),
|
|
_("Brush with left mouse buttons to raise terrain,\nright mouse button to lower it")), wxSizerFlags().Expand());
|
|
gridSizer->Add(Tooltipped(new ToolButton(scenarioEditor.GetToolManager(), sizer->GetStaticBox(), _("Ridge"), _T("PikeElevation"), wxSize(48, -1)),
|
|
_("Brush with left mouse buttons to raise terrain,\nright mouse button to lower it")), wxSizerFlags().Expand());
|
|
gridSizer->Add(Tooltipped(new ToolButton(scenarioEditor.GetToolManager(), sizer->GetStaticBox(), _("Smooth"), _T("SmoothElevation"), wxSize(48, -1)),
|
|
_("Brush with left mouse button to smooth terrain,\nright mouse button to roughen it")), wxSizerFlags().Expand());
|
|
gridSizer->Add(Tooltipped(new ToolButton(scenarioEditor.GetToolManager(), sizer->GetStaticBox(), _("Flatten"), _T("FlattenElevation"), wxSize(48, -1)),
|
|
_("Brush with left mouse button to flatten terrain")), wxSizerFlags().Expand());
|
|
sizer->Add(gridSizer, wxSizerFlags().Expand().Border(wxALL, 5));
|
|
scrollSizer->Add(sizer, wxSizerFlags().Expand());
|
|
}
|
|
|
|
{
|
|
/////////////////////////////////////////////////////////////////////////
|
|
// Terrain texture
|
|
wxStaticBoxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, scrolledWindow, _("Texture tools"));
|
|
wxSizer* gridSizer = new wxGridSizer(3, 5, 5);
|
|
gridSizer->Add(Tooltipped(new ToolButton(scenarioEditor.GetToolManager(), sizer->GetStaticBox(), _("Paint"), _T("PaintTerrain"), wxSize(48, -1)),
|
|
_("Brush with left mouse button to paint texture dominantly,\nright mouse button to paint submissively.\nShift-left-click for eyedropper tool")), wxSizerFlags().Expand());
|
|
gridSizer->Add(Tooltipped(new ToolButton(scenarioEditor.GetToolManager(), sizer->GetStaticBox(), _("Replace"), _T("ReplaceTerrain"), wxSize(48, -1)),
|
|
_("Replace all of a terrain texture with a new one")), wxSizerFlags().Expand());
|
|
gridSizer->Add(Tooltipped(new ToolButton(scenarioEditor.GetToolManager(), sizer->GetStaticBox(), _("Fill"), _T("FillTerrain"), wxSize(48, -1)),
|
|
_T("Bucket fill a patch of terrain texture with a new one")), wxSizerFlags().Expand());
|
|
sizer->Add(gridSizer, wxSizerFlags().Expand().Border(wxALL, 5));
|
|
scrollSizer->Add(sizer, wxSizerFlags().Expand().Border(wxTOP, 10));
|
|
}
|
|
|
|
{
|
|
/////////////////////////////////////////////////////////////////////////
|
|
// Brush settings
|
|
wxStaticBoxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, scrolledWindow, _("Brush"));
|
|
|
|
m_TexturePreview = new TexturePreviewPanel(sizer->GetStaticBox());
|
|
sizer->Add(m_TexturePreview, wxSizerFlags(1).Expand().Border(wxALL, 5));
|
|
|
|
g_Brush_Elevation.CreateUI(sizer->GetStaticBox(), sizer);
|
|
scrollSizer->Add(sizer, wxSizerFlags().Expand().Border(wxTOP, 10));
|
|
}
|
|
|
|
{
|
|
/////////////////////////////////////////////////////////////////////////
|
|
// Visualise
|
|
wxStaticBoxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, scrolledWindow, _("Visualise"));
|
|
scrollSizer->Add(sizer, wxSizerFlags().Expand().Border(wxTOP, 10));
|
|
|
|
wxFlexGridSizer* visSizer = new wxFlexGridSizer(2, 5, 5);
|
|
visSizer->AddGrowableCol(1);
|
|
sizer->Add(visSizer, wxSizerFlags().Expand().Border(wxALL, 5));
|
|
|
|
wxArrayString defaultChoices;
|
|
defaultChoices.Add(_("(none)"));
|
|
m_PassabilityChoice = new wxChoice(sizer->GetStaticBox(), ID_Passability, wxDefaultPosition, wxDefaultSize, defaultChoices);
|
|
m_PassabilityChoice->SetSelection(0);
|
|
|
|
visSizer->Add(new wxStaticText(sizer->GetStaticBox(), wxID_ANY, _("Passability")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT));
|
|
visSizer->Add(Tooltipped(m_PassabilityChoice,
|
|
_("View passability classes")), wxSizerFlags().Expand());
|
|
|
|
visSizer->Add(new wxStaticText(sizer->GetStaticBox(), wxID_ANY, _("Priorities")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT));
|
|
visSizer->Add(Tooltipped(new wxCheckBox(sizer->GetStaticBox(), ID_ShowPriorities, _("")),
|
|
_("Show terrain texture priorities")));
|
|
}
|
|
|
|
m_BottomBar = new TerrainBottomBar(scenarioEditor, bottomBarContainer);
|
|
}
|
|
|
|
void TerrainSidebar::OnShutdown()
|
|
{
|
|
static_cast<TerrainBottomBar*>(m_BottomBar)->OnShutdown();
|
|
}
|
|
|
|
void TerrainSidebar::OnFirstDisplay()
|
|
{
|
|
AtlasMessage::qGetTerrainPassabilityClasses qry;
|
|
qry.Post();
|
|
std::vector<std::wstring> passClasses = *qry.classNames;
|
|
for (size_t i = 0; i < passClasses.size(); ++i)
|
|
m_PassabilityChoice->Append(passClasses[i].c_str());
|
|
|
|
static_cast<TerrainBottomBar*>(m_BottomBar)->LoadTerrain();
|
|
m_TexturePreview->LoadPreview();
|
|
}
|
|
|
|
void TerrainSidebar::OnPassabilityChoice(wxCommandEvent& evt)
|
|
{
|
|
if (evt.GetSelection() == 0)
|
|
POST_MESSAGE(SetViewParamS, (AtlasMessage::eRenderView::GAME, L"passability", L""));
|
|
else
|
|
POST_MESSAGE(SetViewParamS, (AtlasMessage::eRenderView::GAME, L"passability", (std::wstring)evt.GetString().wc_str()));
|
|
}
|
|
|
|
void TerrainSidebar::OnShowPriorities(wxCommandEvent& evt)
|
|
{
|
|
POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::GAME, L"priorities", evt.IsChecked()));
|
|
}
|
|
|
|
BEGIN_EVENT_TABLE(TerrainSidebar, Sidebar)
|
|
EVT_CHOICE(ID_Passability, TerrainSidebar::OnPassabilityChoice)
|
|
EVT_CHECKBOX(ID_ShowPriorities, TerrainSidebar::OnShowPriorities)
|
|
END_EVENT_TABLE();
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
class TextureNotebookPage : public wxPanel
|
|
{
|
|
private:
|
|
static const int imageWidth = 120;
|
|
static const int imageHeight = 40;
|
|
|
|
public:
|
|
TextureNotebookPage(ScenarioEditor& scenarioEditor, wxWindow* parent, const wxString& name)
|
|
: wxPanel(parent, wxID_ANY), m_ScenarioEditor(scenarioEditor), m_Timer(this), m_Name(name), m_Loaded(false)
|
|
{
|
|
m_ScrolledPanel = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
|
m_ScrolledPanel->SetScrollRate(0, 10);
|
|
|
|
wxSizer* sizer = new wxBoxSizer(wxVERTICAL);
|
|
sizer->Add(m_ScrolledPanel, wxSizerFlags().Proportion(1).Expand());
|
|
SetSizer(sizer);
|
|
|
|
m_ItemSizer = new wxGridSizer(6, 4, 0);
|
|
m_ScrolledPanel->SetSizer(m_ItemSizer);
|
|
}
|
|
|
|
void OnDisplay()
|
|
{
|
|
// Trigger the terrain loading on first display
|
|
|
|
if (m_Loaded)
|
|
return;
|
|
|
|
m_Loaded = true;
|
|
|
|
wxBusyInfo busy (_("Loading terrain previews"));
|
|
|
|
AtlasMessage::qGetTerrainGroupTextures query((std::wstring)m_Name.wc_str());
|
|
query.Post();
|
|
m_Textures = *query.names;
|
|
|
|
LayoutButtons();
|
|
ReloadPreviews();
|
|
}
|
|
|
|
void LayoutButtons()
|
|
{
|
|
Freeze();
|
|
|
|
m_ScrolledPanel->DestroyChildren();
|
|
m_ItemSizer->Clear();
|
|
|
|
m_LastTerrainSelection = nullptr; // clear any reference to deleted button
|
|
|
|
for (const std::wstring& textureName : m_Textures)
|
|
{
|
|
// Construct the wrapped-text label
|
|
wxStaticText* label = new wxStaticText(m_ScrolledPanel, wxID_ANY, FormatTextureName(textureName), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER);
|
|
label->Wrap(imageWidth);
|
|
|
|
wxImage image(imageWidth, imageHeight);
|
|
wxBitmapButton* button = new wxBitmapButton(m_ScrolledPanel, wxID_ANY, wxBitmap(image));
|
|
|
|
// Store the texture name in the clientdata slot
|
|
button->SetClientObject(new wxStringClientData(textureName));
|
|
|
|
wxSizer* imageSizer = new wxBoxSizer(wxVERTICAL);
|
|
imageSizer->Add(button, wxSizerFlags().Center());
|
|
imageSizer->Add(label, wxSizerFlags().Proportion(1).Center());
|
|
m_ItemSizer->Add(imageSizer, wxSizerFlags().Expand());
|
|
|
|
m_PreviewButtons.emplace(textureName, PreviewButton{button, false});
|
|
}
|
|
|
|
m_ScrolledPanel->Fit();
|
|
Layout();
|
|
|
|
Thaw();
|
|
}
|
|
|
|
void ReloadPreviews()
|
|
{
|
|
bool allLoaded = true;
|
|
bool timeout = false;
|
|
const std::chrono::high_resolution_clock::time_point reloadingStart =
|
|
std::chrono::high_resolution_clock::now();
|
|
for (const std::wstring& textureName : m_Textures)
|
|
{
|
|
const auto it = m_PreviewButtons.find(textureName);
|
|
if (it == m_PreviewButtons.end() || it->second.loaded)
|
|
continue;
|
|
|
|
if (timeout)
|
|
{
|
|
// Mark allLoaded only in case we have a real not loaded texture, and not
|
|
// because we have an exceeded timeout.
|
|
allLoaded = false;
|
|
continue;
|
|
}
|
|
|
|
AtlasMessage::qGetTerrainTexturePreview previewQuery(textureName, imageWidth, imageHeight);
|
|
previewQuery.Post();
|
|
AtlasMessage::sTerrainTexturePreview preview = previewQuery.preview;
|
|
|
|
if (!preview.loaded)
|
|
allLoaded = false;
|
|
else
|
|
it->second.loaded = true;
|
|
|
|
if (preview.imageData.GetSize())
|
|
{
|
|
unsigned char* buffer = reinterpret_cast<unsigned char*>(malloc(preview.imageData.GetSize()));
|
|
// imagedata.GetBuffer() gives a Shareable<unsigned char>*, which
|
|
// is stored the same as a unsigned char*, so we can just copy it.
|
|
memcpy(buffer, preview.imageData.GetBuffer(), preview.imageData.GetSize());
|
|
wxImage image(imageWidth, imageHeight, buffer);
|
|
it->second.button->SetBitmap(wxBitmap(image));
|
|
}
|
|
|
|
// We need to load at least one preview so check for timeout inside real
|
|
// loading.
|
|
const std::chrono::high_resolution_clock::time_point now =
|
|
std::chrono::high_resolution_clock::now();
|
|
const std::chrono::duration<float> delta = now - reloadingStart;
|
|
if (delta.count() > PREVIEW_RELOAD_TIMEOUT_THRESHOLD_SECONDS)
|
|
timeout = true;
|
|
}
|
|
|
|
// If not all textures were loaded yet, run a timer to reload the previews
|
|
// every so often until they've all finished.
|
|
if (allLoaded && m_Timer.IsRunning())
|
|
{
|
|
m_Timer.Stop();
|
|
m_PreviewButtons.clear();
|
|
}
|
|
else if (!allLoaded)
|
|
{
|
|
if (timeout)
|
|
{
|
|
// In case we didn't have enough time to load all previews
|
|
// start after a minimum delay to not freeze the whole UI.
|
|
m_Timer.Start(PREVIEW_RELOAD_TIMEOUT_DELAY_MILLISECONDS);
|
|
}
|
|
else
|
|
m_Timer.Start(PREVIEW_RELOAD_DELAY_MILLISECONDS);
|
|
}
|
|
}
|
|
|
|
void OnButton(wxCommandEvent& evt)
|
|
{
|
|
wxButton* button = wxDynamicCast(evt.GetEventObject(), wxButton);
|
|
wxString name = static_cast<wxStringClientData*>(button->GetClientObject())->GetData();
|
|
g_SelectedTexture = name;
|
|
g_SelectedTexture.NotifyObservers();
|
|
|
|
if (m_LastTerrainSelection)
|
|
m_LastTerrainSelection->SetBackgroundColour(wxNullColour);
|
|
|
|
button->SetBackgroundColour(wxColor(255, 255, 0));
|
|
m_LastTerrainSelection = button;
|
|
|
|
// Slight hack: Default to Paint mode because that's probably what the user wanted
|
|
// when they selected a terrain; unless already explicitly in Replace mode, because
|
|
// then the user probably wanted that instead
|
|
if (m_ScenarioEditor.GetToolManager().GetCurrentToolName() != _T("ReplaceTerrain") && m_ScenarioEditor.GetToolManager().GetCurrentToolName() != _T("FillTerrain"))
|
|
m_ScenarioEditor.GetToolManager().SetCurrentTool(_T("PaintTerrain"));
|
|
}
|
|
|
|
void OnSize(wxSizeEvent& evt)
|
|
{
|
|
int numCols = std::max(1, (int)(evt.GetSize().GetWidth() / (imageWidth + 16)));
|
|
m_ItemSizer->SetCols(numCols);
|
|
evt.Skip();
|
|
}
|
|
|
|
void OnTimer(wxTimerEvent& WXUNUSED(evt))
|
|
{
|
|
ReloadPreviews();
|
|
}
|
|
|
|
void OnShutdown()
|
|
{
|
|
if (m_Timer.IsRunning())
|
|
m_Timer.Stop();
|
|
}
|
|
|
|
private:
|
|
ScenarioEditor& m_ScenarioEditor;
|
|
bool m_Loaded;
|
|
wxTimer m_Timer;
|
|
wxString m_Name;
|
|
wxScrolledWindow* m_ScrolledPanel;
|
|
wxGridSizer* m_ItemSizer;
|
|
wxButton* m_LastTerrainSelection; // button that was last selected, so we can undo its coloring
|
|
|
|
std::vector<std::wstring> m_Textures;
|
|
struct PreviewButton
|
|
{
|
|
wxBitmapButton* button;
|
|
bool loaded;
|
|
};
|
|
std::unordered_map<std::wstring, PreviewButton> m_PreviewButtons;
|
|
|
|
DECLARE_EVENT_TABLE();
|
|
};
|
|
|
|
BEGIN_EVENT_TABLE(TextureNotebookPage, wxPanel)
|
|
EVT_BUTTON(wxID_ANY, TextureNotebookPage::OnButton)
|
|
EVT_SIZE(TextureNotebookPage::OnSize)
|
|
EVT_TIMER(wxID_ANY, TextureNotebookPage::OnTimer)
|
|
END_EVENT_TABLE();
|
|
|
|
|
|
class TextureNotebook : public wxNotebook
|
|
{
|
|
public:
|
|
TextureNotebook(ScenarioEditor& scenarioEditor, wxWindow *parent)
|
|
: wxNotebook(parent, wxID_ANY/*, wxDefaultPosition, wxDefaultSize, wxNB_FIXEDWIDTH*/),
|
|
m_ScenarioEditor(scenarioEditor)
|
|
{
|
|
}
|
|
|
|
void LoadTerrain()
|
|
{
|
|
wxBusyInfo busy (_("Loading terrain groups"));
|
|
|
|
DeleteAllPages();
|
|
m_TerrainGroups.Clear();
|
|
|
|
// Get the list of terrain groups from the engine
|
|
AtlasMessage::qGetTerrainGroups qry;
|
|
qry.Post();
|
|
std::vector<std::wstring> groupnames = *qry.groupNames;
|
|
for (std::vector<std::wstring>::iterator it = groupnames.begin(); it != groupnames.end(); ++it)
|
|
m_TerrainGroups.Add(it->c_str());
|
|
|
|
for (size_t i = 0; i < m_TerrainGroups.GetCount(); ++i)
|
|
{
|
|
wxString visibleName = FormatTextureName(m_TerrainGroups[i]);
|
|
AddPage(new TextureNotebookPage(m_ScenarioEditor, this, m_TerrainGroups[i]), visibleName);
|
|
}
|
|
|
|
// On some platforms (wxOSX) there is no initial OnPageChanged event, so it loads with a blank page
|
|
// and setting selection to 0 won't trigger it either, so just force first page to display
|
|
// (this is safe because the sidebar has already been displayed)
|
|
if (GetPageCount() > 0)
|
|
{
|
|
static_cast<TextureNotebookPage*>(GetPage(0))->OnDisplay();
|
|
}
|
|
}
|
|
|
|
void OnShutdown()
|
|
{
|
|
for (size_t index = 0; index < GetPageCount(); ++index)
|
|
static_cast<TextureNotebookPage*>(GetPage(index))->OnShutdown();
|
|
}
|
|
|
|
protected:
|
|
void OnPageChanged(wxNotebookEvent& event)
|
|
{
|
|
if (event.GetSelection() >= 0 && event.GetSelection() < (int)GetPageCount())
|
|
{
|
|
static_cast<TextureNotebookPage*>(GetPage(event.GetSelection()))->OnDisplay();
|
|
}
|
|
event.Skip();
|
|
}
|
|
|
|
private:
|
|
ScenarioEditor& m_ScenarioEditor;
|
|
wxArrayString m_TerrainGroups;
|
|
|
|
DECLARE_EVENT_TABLE();
|
|
};
|
|
|
|
BEGIN_EVENT_TABLE(TextureNotebook, wxNotebook)
|
|
EVT_NOTEBOOK_PAGE_CHANGED(wxID_ANY, TextureNotebook::OnPageChanged)
|
|
END_EVENT_TABLE();
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
TerrainBottomBar::TerrainBottomBar(ScenarioEditor& scenarioEditor, wxWindow* parent) :
|
|
wxPanel(parent, wxID_ANY)
|
|
{
|
|
wxSizer* sizer = new wxBoxSizer(wxVERTICAL);
|
|
m_Textures = new TextureNotebook(scenarioEditor, this);
|
|
sizer->Add(m_Textures, wxSizerFlags().Expand().Proportion(1));
|
|
SetSizer(sizer);
|
|
}
|
|
|
|
void TerrainBottomBar::LoadTerrain()
|
|
{
|
|
m_Textures->LoadTerrain();
|
|
}
|
|
|
|
void TerrainBottomBar::OnShutdown()
|
|
{
|
|
m_Textures->OnShutdown();
|
|
}
|