/* Copyright (C) 2021 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 "Object.h"
#include "Buttons/ToolButton.h"
#include "General/Datafile.h"
#include "ScenarioEditor/ScenarioEditor.h"
#include "ScenarioEditor/Tools/Common/ObjectSettings.h"
#include "ScenarioEditor/Tools/Common/MiscState.h"
#include "VariationControl.h"
#include "GameInterface/Messages.h"
#include "wx/busyinfo.h"
#include "wx/wxcrt.h"
enum
{
ID_ObjectType = 1,
ID_ObjectFilter,
ID_ObjectExactFilter,
ID_PlayerSelect,
ID_SelectObject,
ID_ToggleViewer,
ID_ViewerWireframe,
ID_ViewerMove,
ID_ViewerGround,
ID_ViewerWater,
ID_ViewerShadows,
ID_ViewerPolyCount,
ID_ViewerAnimation,
ID_ViewerBoundingBox,
ID_ViewerAxesMarker,
ID_ViewerPropPoints,
ID_ViewerPlay,
ID_ViewerPause,
ID_ViewerSlow
};
// Helper function for adding tooltips
static wxWindow* Tooltipped(wxWindow* window, const wxString& tip)
{
window->SetToolTip(tip);
return window;
}
class ObjectBottomBar : public wxPanel
{
public:
ObjectBottomBar(
wxWindow* parent,
Observable& objectSettings,
Observable& mapSettings,
ObjectSidebarImpl* p
);
void OnFirstDisplay();
void ShowActorViewer(bool show);
void OnSelectedObjectsChange(const std::vector& selectedObjects);
private:
void OnViewerSetting(wxCommandEvent& evt);
void OnSelectAnim(wxCommandEvent& evt);
void OnSpeed(wxCommandEvent& evt);
bool m_ViewerWireframe;
bool m_ViewerMove;
bool m_ViewerGround;
bool m_ViewerWater;
bool m_ViewerPolyCount;
bool m_ViewerBoundingBox;
bool m_ViewerAxesMarker;
int m_ViewerPropPointsMode; // 0 disabled, 1 for point markers, 2 for point markers + axes
wxPanel* m_ViewerPanel;
wxScrolledWindow* m_TemplateNames;
ObjectSidebarImpl* p;
DECLARE_EVENT_TABLE();
};
struct ObjectSidebarImpl
{
ObjectSidebarImpl(ScenarioEditor& scenarioEditor) :
m_ObjectListBox(NULL), m_ActorViewerActive(false),
m_ActorViewerEntity(L"actor|structures/fndn_1x1.xml"),
m_ActorViewerAnimation("idle"), m_ActorViewerSpeed(0.f),
m_ObjectSettings(scenarioEditor.GetObjectSettings())
{
}
wxListBox* m_ObjectListBox;
std::vector m_Objects;
ObservableScopedConnection m_ToolConn;
bool m_ActorViewerActive;
std::wstring m_ActorViewerEntity;
std::string m_ActorViewerAnimation;
float m_ActorViewerSpeed;
Observable& m_ObjectSettings;
struct SearchItem
{
// Weight of the pattern (higher - better).
size_t weight;
// Index in the original sequence to save the order.
size_t index;
bool operator<(const SearchItem& other) const
{
if (weight != other.weight)
return weight > other.weight;
return index < other.index;
}
};
// Cache the std::vector to prevent frequent reallocations.
std::vector m_SearchItems;
struct SearchItemFilter
{
SearchItemFilter(const wxString& filterName)
{
bool firstCharacterInWord = true;
for (const wxUniChar& ch : filterName)
{
if (wxIsalpha(ch))
{
if (firstCharacterInWord)
m_FilterWords.push_back(wxString());
firstCharacterInWord = false;
m_FilterWords.back().Append(ch);
}
else
firstCharacterInWord = true;
}
}
// Returns positive integer if filter words can present in the name.
// Otherwise returns zero.
size_t CalculateWeight(const wxString& name)
{
if (m_FilterWords.empty() || name.IsEmpty())
return 0;
std::vector filterWordWeights(m_FilterWords.size(), 0);
for (size_t wordBegin = 0; wordBegin < name.Length();)
{
if (!wxIsalpha(name[wordBegin]))
{
++wordBegin;
continue;
}
size_t wordEnd = wordBegin;
while (wordEnd < name.Length() && wxIsalpha(name[wordEnd]))
++wordEnd;
for (size_t i = 0; i < m_FilterWords.size(); ++i)
{
// TODO: we might have a copy here, maybe we can use
// something similar to std::string_view but for wxString.
size_t weight = CalculateLCS(
m_FilterWords[i], name.Mid(wordBegin, wordEnd - wordBegin));
// We use a simple heuristic, if LCS shorter than a half
// of the filter word, then it's not good for us.
if (weight < m_FilterWords[i].size() / 2)
continue;
filterWordWeights[i] = std::max(filterWordWeights[i], weight);
}
wordBegin = wordEnd;
}
size_t totalWeight = 0;
for (size_t weight : filterWordWeights)
{
// If a filter word was't found at all, then we can't accept
// the search item name.
if (!weight)
return 0;
totalWeight += weight;
}
return totalWeight;
}
// We use longest common subsequence to calculate the weight
// between two words: filter and item names.
size_t CalculateLCS(const wxString& filterWord, const wxString& word)
{
if (filterWord.IsEmpty() || word.IsEmpty())
return 0;
m_LCSGrid.resize(filterWord.Length() * word.Length());
for (size_t i = 0; i < filterWord.Length(); ++i)
for (size_t j = 0; j < word.Length(); ++j)
{
#define GRID(x, y) m_LCSGrid[(x) * word.Length() + (y)]
GRID(i, j) = wxTolower(filterWord.GetChar(i)) == wxTolower(word.GetChar(j));
if (i && j)
GRID(i, j) += GRID(i - 1, j - 1);
if (i)
GRID(i, j) = std::max(GRID(i, j), GRID(i - 1, j));
if (j)
GRID(i, j) = std::max(GRID(i, j), GRID(i, j - 1));
#undef GRID
}
return m_LCSGrid.back();
}
std::vector m_FilterWords;
// Cache the std::vector to prevent frequent reallocations.
std::vector m_LCSGrid;
};
void ActorViewerPostToGame()
{
POST_MESSAGE(SetActorViewer, (m_ActorViewerEntity.c_str(), m_ActorViewerAnimation.c_str(), m_ObjectSettings.GetPlayerID(), m_ActorViewerSpeed, false));
}
};
ObjectSidebar::ObjectSidebar(
ScenarioEditor& scenarioEditor,
wxWindow* sidebarContainer,
wxWindow* bottomBarContainer
)
: Sidebar(scenarioEditor, sidebarContainer, bottomBarContainer),
m_Impl(new ObjectSidebarImpl(scenarioEditor))
{
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());
wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(new wxStaticText(scrolledWindow, wxID_ANY, _("Filter")), wxSizerFlags().Align(wxALIGN_CENTER));
sizer->AddSpacer(2);
sizer->Add(
Tooltipped(
new wxTextCtrl(scrolledWindow, ID_ObjectFilter),
_("Enter text to filter object list")
),
wxSizerFlags().Expand().Proportion(1)
);
scrollSizer->Add(sizer, wxSizerFlags().Expand());
scrollSizer->AddSpacer(3);
wxCheckBox* exactSearchCheckBox = new wxCheckBox(
scrolledWindow, ID_ObjectExactFilter, _("Exact Search"));
exactSearchCheckBox->SetValue(true);
scrollSizer->Add(Tooltipped(exactSearchCheckBox,
_("Provides a search with a strict string equality")));
scrollSizer->AddSpacer(3);
// ------------------------------------------------------------------------------------------
wxArrayString strings;
strings.Add(_("Entities"));
strings.Add(_("Actors (all)"));
wxChoice* objectType = new wxChoice(scrolledWindow, ID_ObjectType, wxDefaultPosition, wxDefaultSize, strings);
objectType->SetSelection(0);
scrollSizer->Add(objectType, wxSizerFlags().Expand());
scrollSizer->AddSpacer(3);
// ------------------------------------------------------------------------------------------
m_Impl->m_ObjectListBox = new wxListBox(scrolledWindow, ID_SelectObject, wxDefaultPosition, wxDefaultSize, 0, nullptr, wxLB_SINGLE|wxLB_HSCROLL);
scrollSizer->Add(m_Impl->m_ObjectListBox, wxSizerFlags().Proportion(1).Expand());
scrollSizer->AddSpacer(3);
// ------------------------------------------------------------------------------------------
scrollSizer->Add(new wxButton(scrolledWindow, ID_ToggleViewer, _("Switch to Actor Viewer")), wxSizerFlags().Expand());
// ------------------------------------------------------------------------------------------
m_BottomBar = new ObjectBottomBar(
bottomBarContainer,
scenarioEditor.GetObjectSettings(),
scenarioEditor.GetMapSettings(),
m_Impl
);
m_Impl->m_ToolConn = scenarioEditor.GetToolManager().GetCurrentTool().RegisterObserver(0, &ObjectSidebar::OnToolChange, this);
}
ObjectSidebar::~ObjectSidebar()
{
delete m_Impl;
}
void ObjectSidebar::OnToolChange(ITool* tool)
{
if (wxString(tool->GetClassInfo()->GetClassName()) == _T("ActorViewerTool"))
{
m_Impl->m_ActorViewerActive = true;
m_Impl->ActorViewerPostToGame();
wxDynamicCast(FindWindow(ID_ToggleViewer), wxButton)->SetLabel(_("Return to game view"));
}
else
{
m_Impl->m_ActorViewerActive = false;
wxDynamicCast(FindWindow(ID_ToggleViewer), wxButton)->SetLabel(_("Switch to Actor Viewer"));
}
static_cast(m_BottomBar)->ShowActorViewer(m_Impl->m_ActorViewerActive);
}
void ObjectSidebar::OnFirstDisplay()
{
static_cast(m_BottomBar)->OnFirstDisplay();
wxBusyInfo busy (_("Loading list of objects"));
// Get the list of objects from the game
AtlasMessage::qGetObjectsList qry;
qry.Post();
m_Impl->m_Objects = *qry.objects;
// Display first group of objects
FilterObjects();
}
void ObjectSidebar::FilterObjects()
{
int filterType = wxDynamicCast(FindWindow(ID_ObjectType), wxChoice)->GetSelection();
wxString filterName = wxDynamicCast(FindWindow(ID_ObjectFilter), wxTextCtrl)->GetValue();
m_Impl->m_ObjectListBox->Freeze();
m_Impl->m_ObjectListBox->Clear();
if (wxDynamicCast(FindWindow(ID_ObjectExactFilter), wxCheckBox)->IsChecked() || filterName.IsEmpty())
{
for (std::vector::iterator it = m_Impl->m_Objects.begin(); it != m_Impl->m_Objects.end(); ++it)
{
if (it->type != filterType)
continue;
wxString id = it->id.c_str();
wxString name = it->name.c_str();
if (name.Lower().Find(filterName.Lower()) != wxNOT_FOUND)
m_Impl->m_ObjectListBox->Append(name, new wxStringClientData(id));
}
}
else
{
ObjectSidebarImpl::SearchItemFilter filter(filterName);
for (size_t idx = 0; idx < m_Impl->m_Objects.size(); ++idx)
{
const AtlasMessage::sObjectsListItem& it = m_Impl->m_Objects[idx];
if (it.type != filterType)
continue;
wxString name = it.name.c_str();
size_t weight = filter.CalculateWeight(name);
// We don't want to show items with zero weight.
if (!weight)
continue;
m_Impl->m_SearchItems.push_back({weight, idx});
}
std::sort(m_Impl->m_SearchItems.begin(), m_Impl->m_SearchItems.end());
for (const ObjectSidebarImpl::SearchItem& item : m_Impl->m_SearchItems)
{
const AtlasMessage::sObjectsListItem& it = m_Impl->m_Objects[item.index];
wxString id = it.id.c_str();
wxString name = it.name.c_str();
m_Impl->m_ObjectListBox->Append(name, new wxStringClientData(id));
}
m_Impl->m_SearchItems.clear();
}
m_Impl->m_ObjectListBox->Thaw();
}
void ObjectSidebar::OnToggleViewer(wxCommandEvent& WXUNUSED(evt))
{
if (m_Impl->m_ActorViewerActive)
{
m_ScenarioEditor.GetToolManager().SetCurrentTool(_T(""), NULL);
}
else
{
m_ScenarioEditor.GetToolManager().SetCurrentTool(_T("ActorViewerTool"), NULL);
}
}
void ObjectSidebar::OnSelectType(wxCommandEvent& WXUNUSED(evt))
{
FilterObjects();
}
void ObjectSidebar::OnSelectObject(wxCommandEvent& evt)
{
if (evt.GetInt() < 0)
return;
wxString id = static_cast(evt.GetClientObject())->GetData();
// Always update the actor viewer's state even if it's inactive,
// so it will be correct when first enabled
m_Impl->m_ActorViewerEntity = id;
if (m_Impl->m_ActorViewerActive)
{
m_Impl->ActorViewerPostToGame();
}
else
{
// On selecting an object, enable the PlaceObject tool with this object
m_ScenarioEditor.GetToolManager().SetCurrentTool(_T("PlaceObject"), &id);
}
}
void ObjectSidebar::OnSelectFilter(wxCommandEvent& WXUNUSED(evt))
{
FilterObjects();
}
void ObjectSidebar::OnToggleExactFilter(wxCommandEvent& WXUNUSED(evt))
{
FilterObjects();
}
BEGIN_EVENT_TABLE(ObjectSidebar, Sidebar)
EVT_CHOICE(ID_ObjectType, ObjectSidebar::OnSelectType)
EVT_TEXT(ID_ObjectFilter, ObjectSidebar::OnSelectFilter)
EVT_LISTBOX(ID_SelectObject, ObjectSidebar::OnSelectObject)
EVT_BUTTON(ID_ToggleViewer, ObjectSidebar::OnToggleViewer)
EVT_CHECKBOX(ID_ObjectExactFilter, ObjectSidebar::OnToggleExactFilter)
END_EVENT_TABLE();
//////////////////////////////////////////////////////////////////////////
class PlayerComboBox : public wxComboBox
{
public:
PlayerComboBox(wxWindow* parent, Observable& objectSettings, Observable& mapSettings)
: wxComboBox(parent, ID_PlayerSelect, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY)
, m_ObjectSettings(objectSettings), m_MapSettings(mapSettings)
{
m_ObjectConn = m_ObjectSettings.RegisterObserver(1, &PlayerComboBox::OnObjectSettingsChange, this);
m_MapConn = m_MapSettings.RegisterObserver(1, &PlayerComboBox::OnMapSettingsChange, this);
}
void SetPlayers(wxArrayString& names)
{
m_Players = names;
OnMapSettingsChange(m_MapSettings);
}
private:
ObservableScopedConnection m_ObjectConn;
Observable& m_ObjectSettings;
ObservableScopedConnection m_MapConn;
Observable& m_MapSettings;
wxArrayString m_Players;
void SetSelection(int playerID)
{
// This control may not be loaded yet (before first display)
// or may have less items than we expect, which could cause
// an assertion failure, so handle that here
if ((unsigned int)playerID < GetCount())
{
wxComboBox::SetSelection(playerID);
}
else
{
// Invalid selection
wxComboBox::SetSelection(wxNOT_FOUND);
}
}
void OnObjectSettingsChange(const ObjectSettings& settings)
{
SetSelection(settings.GetPlayerID());
}
void OnMapSettingsChange(const AtObj& settings)
{
// Reload displayed player names
Clear();
size_t numPlayers = settings["PlayerData"]["item"].count();
for (size_t i = 0; i <= numPlayers && i < m_Players.Count(); ++i)
{
Append(m_Players[i]);
}
SetSelection(m_ObjectSettings.GetPlayerID());
}
void OnSelect(wxCommandEvent& evt)
{
m_ObjectSettings.SetPlayerID(evt.GetInt());
m_ObjectSettings.NotifyObserversExcept(m_ObjectConn);
}
DECLARE_EVENT_TABLE();
};
BEGIN_EVENT_TABLE(PlayerComboBox, wxComboBox)
EVT_COMBOBOX(wxID_ANY, PlayerComboBox::OnSelect)
END_EVENT_TABLE();
//////////////////////////////////////////////////////////////////////////
ObjectBottomBar::ObjectBottomBar(
wxWindow* parent,
Observable& objectSettings,
Observable& mapSettings,
ObjectSidebarImpl* p
)
: wxPanel(parent, wxID_ANY), p(p)
{
m_ViewerWireframe = false;
m_ViewerMove = false;
m_ViewerGround = true;
m_ViewerWater = false;
m_ViewerPolyCount = false;
m_ViewerBoundingBox = false;
m_ViewerAxesMarker = false;
m_ViewerPropPointsMode = 0;
wxSizer* mainSizer = new wxBoxSizer(wxHORIZONTAL);
// --- viewer options panel -------------------------------------------------------------------------------
m_ViewerPanel = new wxPanel(this, wxID_ANY);
wxSizer* viewerSizer = new wxBoxSizer(wxHORIZONTAL);
wxSizer* viewerButtonsSizer = new wxStaticBoxSizer(wxHORIZONTAL, m_ViewerPanel, _("Display settings"));
{
wxSizer* viewerButtonsLeft = new wxBoxSizer(wxVERTICAL);
viewerButtonsLeft->SetMinSize(110, -1);
viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerWireframe, _("Wireframe")), _("Toggle wireframe / solid rendering")), wxSizerFlags().Expand());
viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerMove, _("Move")), _("Toggle movement along ground when playing walk/run animations")), wxSizerFlags().Expand());
viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerGround, _("Ground")), _("Toggle the ground plane")), wxSizerFlags().Expand());
// TODO: disabled until https://gitea.wildfiregames.com/0ad/0ad/issues/2692 is fixed
wxButton* waterButton = new wxButton(m_ViewerPanel, ID_ViewerWater, _("Water"));
waterButton->Enable(false);
viewerButtonsLeft->Add(Tooltipped(waterButton, _("Toggle the water plane")), wxSizerFlags().Expand());
viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerShadows, _("Shadows")), _("Toggle shadow rendering")), wxSizerFlags().Expand());
viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerPolyCount, _("Poly count")), _("Toggle polygon-count statistics - turn off ground and shadows for more useful data")), wxSizerFlags().Expand());
wxSizer* viewerButtonsRight = new wxBoxSizer(wxVERTICAL);
viewerButtonsRight->SetMinSize(110,-1);
viewerButtonsRight->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerBoundingBox, _("Bounding Boxes")), _("Toggle bounding boxes")), wxSizerFlags().Expand());
viewerButtonsRight->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerAxesMarker, _("Axes Marker")), _("Toggle the axes marker (R=X, G=Y, B=Z)")), wxSizerFlags().Expand());
viewerButtonsRight->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerPropPoints, _("Prop Points")), _("Toggle prop points (works best in wireframe mode)")), wxSizerFlags().Expand());
viewerButtonsSizer->Add(viewerButtonsLeft, wxSizerFlags().Expand());
viewerButtonsSizer->Add(viewerButtonsRight, wxSizerFlags().Expand());
}
viewerSizer->Add(viewerButtonsSizer, wxSizerFlags().Expand());
viewerSizer->AddSpacer(3);
// --- animations panel -------------------------------------------------------------------------------
wxSizer* viewerAnimSizer = new wxStaticBoxSizer(wxVERTICAL, m_ViewerPanel, _("Animation"));
// TODO: this list should come from the actor
wxArrayString animChoices;
AtObj anims (Datafile::ReadList("animations"));
for (AtIter a = anims["item"]; a.defined(); ++a)
{
animChoices.Add(wxString(*a));
}
wxChoice* viewerAnimSelector = new wxChoice(m_ViewerPanel, ID_ViewerAnimation, wxDefaultPosition, wxDefaultSize, animChoices);
viewerAnimSelector->SetSelection(0);
viewerAnimSizer->Add(viewerAnimSelector, wxSizerFlags().Expand());
wxSizer* viewerAnimSpeedSizer = new wxBoxSizer(wxHORIZONTAL);
viewerAnimSpeedSizer->Add(new wxButton(m_ViewerPanel, ID_ViewerPlay, _("Play"), wxDefaultPosition, wxSize(50, -1)), wxSizerFlags().Expand());
viewerAnimSpeedSizer->Add(new wxButton(m_ViewerPanel, ID_ViewerPause, _("Pause"), wxDefaultPosition, wxSize(50, -1)), wxSizerFlags().Expand());
viewerAnimSpeedSizer->Add(new wxButton(m_ViewerPanel, ID_ViewerSlow, _("Slow"), wxDefaultPosition, wxSize(50, -1)), wxSizerFlags().Expand());
viewerAnimSizer->Add(viewerAnimSpeedSizer);
viewerSizer->Add(viewerAnimSizer, wxSizerFlags().Expand());
// --- add viewer-specific options -------------------------------------------------------------------------------
m_ViewerPanel->SetSizer(viewerSizer);
mainSizer->Add(m_ViewerPanel, wxSizerFlags().Expand());
m_ViewerPanel->Layout(); // prevents strange visibility glitch of the animation buttons on my machine (Vista 32-bit SP1) -- vtsj
m_ViewerPanel->Show(false);
// --- add player/variation selection -------------------------------------------------------------------------------
wxSizer* playerSelectionSizer = new wxBoxSizer(wxHORIZONTAL);
wxSizer* playerVariationSizer = new wxBoxSizer(wxVERTICAL);
// TODO: make this a wxChoice instead
wxComboBox* playerSelect = new PlayerComboBox(this, objectSettings, mapSettings);
playerSelectionSizer->Add(new wxStaticText(this, wxID_ANY, _("Player:")), wxSizerFlags().Align(wxALIGN_CENTER));
playerSelectionSizer->AddSpacer(3);
playerSelectionSizer->Add(playerSelect);
playerVariationSizer->Add(playerSelectionSizer);
playerVariationSizer->AddSpacer(3);
wxWindow* variationSelect = new VariationControl(this, objectSettings);
variationSelect->SetMinSize(wxSize(160, -1));
wxSizer* variationSizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Variation"));
variationSizer->Add(variationSelect, wxSizerFlags().Proportion(1).Expand());
playerVariationSizer->Add(variationSizer, wxSizerFlags().Proportion(1));
mainSizer->AddSpacer(3);
mainSizer->Add(playerVariationSizer, wxSizerFlags().Expand());
// ----------------------------------------------------------------------------------
// --- display template name
wxSizer* displaySizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Selected entities"));
m_TemplateNames = new wxScrolledWindow(this);
m_TemplateNames->SetMinSize(wxSize(250, -1));
m_TemplateNames->SetScrollRate(0, 5);
wxSizer* scrollwindowSizer = new wxBoxSizer(wxVERTICAL);
m_TemplateNames->SetSizer(scrollwindowSizer);
displaySizer->Add(m_TemplateNames, wxSizerFlags().Proportion(1).Expand());
m_TemplateNames->Layout();
mainSizer->AddSpacer(3);
mainSizer->Add(displaySizer, wxSizerFlags().Proportion(1).Expand());
g_SelectedObjects.RegisterObserver(0, &ObjectBottomBar::OnSelectedObjectsChange, this);
SetSizer(mainSizer);
}
static wxControl* CreateTemplateNameObject(wxWindow* parent, const std::string& templateName, int counterTemplate)
{
wxString idTemplate(wxString::FromUTF8(templateName.c_str()));
if (counterTemplate > 1)
idTemplate.Append(wxString::Format(wxT(" (%i)"), counterTemplate));
wxStaticText* templateNameObject = new wxStaticText(parent, wxID_ANY, idTemplate);
return templateNameObject;
}
void ObjectBottomBar::OnSelectedObjectsChange(const std::vector& selectedObjects)
{
Freeze();
wxSizer* sizer = m_TemplateNames->GetSizer();
sizer->Clear(true);
AtlasMessage::qGetSelectedObjectsTemplateNames objectTemplatesName(selectedObjects);
objectTemplatesName.Post();
std::vector names = *objectTemplatesName.names;
int counterTemplate = 0;
std::string lastTemplateName = "";
for (std::vector::const_iterator it = names.begin(); it != names.end(); ++it)
{
if (lastTemplateName == "")
lastTemplateName = (*it);
if (lastTemplateName == (*it))
{
++counterTemplate;
continue;
}
sizer->Add(CreateTemplateNameObject(m_TemplateNames, lastTemplateName, counterTemplate), wxSizerFlags().Align(wxALIGN_LEFT));
lastTemplateName = (*it);
counterTemplate = 1;
}
// Add the remaining template
sizer->Add(CreateTemplateNameObject(m_TemplateNames, lastTemplateName, counterTemplate), wxSizerFlags().Align(wxALIGN_LEFT));
Thaw();
sizer->FitInside(m_TemplateNames);
}
void ObjectBottomBar::OnFirstDisplay()
{
// We use messages here because the simulation is not init'd otherwise (causing a crash)
// Get player names
wxArrayString players;
AtlasMessage::qGetPlayerDefaults qryPlayers;
qryPlayers.Post();
AtObj playerData = AtlasObject::LoadFromJSON(*qryPlayers.defaults);
AtObj playerDefs = *playerData["PlayerData"];
for (AtIter iterator = playerDefs["item"]; iterator.defined(); ++iterator)
players.Add(wxString::FromUTF8(iterator["Name"]));
wxDynamicCast(FindWindow(ID_PlayerSelect), PlayerComboBox)->SetPlayers(players);
// Initialise the game with the default settings
POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"wireframe", m_ViewerWireframe));
POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"walk", m_ViewerMove));
POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"ground", m_ViewerGround));
POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"water", m_ViewerWater));
POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"stats", m_ViewerPolyCount));
POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"bounding_box", m_ViewerBoundingBox));
POST_MESSAGE(SetViewParamI, (AtlasMessage::eRenderView::ACTOR, L"prop_points", m_ViewerPropPointsMode));
}
void ObjectBottomBar::ShowActorViewer(bool show)
{
m_ViewerPanel->Show(show);
Layout();
}
void ObjectBottomBar::OnViewerSetting(wxCommandEvent& evt)
{
switch (evt.GetId())
{
case ID_ViewerWireframe:
m_ViewerWireframe = !m_ViewerWireframe;
POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"wireframe", m_ViewerWireframe));
break;
case ID_ViewerMove:
m_ViewerMove = !m_ViewerMove;
POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"walk", m_ViewerMove));
break;
case ID_ViewerGround:
m_ViewerGround = !m_ViewerGround;
POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"ground", m_ViewerGround));
break;
case ID_ViewerWater:
m_ViewerWater = !m_ViewerWater;
POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"water", m_ViewerWater));
break;
case ID_ViewerShadows:
POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"shadows", true));
break;
case ID_ViewerPolyCount:
m_ViewerPolyCount = !m_ViewerPolyCount;
POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"stats", m_ViewerPolyCount));
break;
case ID_ViewerBoundingBox:
m_ViewerBoundingBox = !m_ViewerBoundingBox;
POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"bounding_box", m_ViewerBoundingBox));
break;
case ID_ViewerAxesMarker:
m_ViewerAxesMarker = !m_ViewerAxesMarker;
POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"axes_marker", m_ViewerAxesMarker));
break;
case ID_ViewerPropPoints:
m_ViewerPropPointsMode = (m_ViewerPropPointsMode+1) % 3;
POST_MESSAGE(SetViewParamI, (AtlasMessage::eRenderView::ACTOR, L"prop_points", m_ViewerPropPointsMode));
break;
}
}
void ObjectBottomBar::OnSelectAnim(wxCommandEvent& evt)
{
p->m_ActorViewerAnimation = evt.GetString();
p->ActorViewerPostToGame();
}
void ObjectBottomBar::OnSpeed(wxCommandEvent& evt)
{
switch (evt.GetId())
{
case ID_ViewerPlay: p->m_ActorViewerSpeed = 1.0f; break;
case ID_ViewerPause: p->m_ActorViewerSpeed = 0.0f; break;
case ID_ViewerSlow: p->m_ActorViewerSpeed = 0.1f; break;
}
p->ActorViewerPostToGame();
}
BEGIN_EVENT_TABLE(ObjectBottomBar, wxPanel)
EVT_BUTTON(ID_ViewerWireframe, ObjectBottomBar::OnViewerSetting)
EVT_BUTTON(ID_ViewerMove, ObjectBottomBar::OnViewerSetting)
EVT_BUTTON(ID_ViewerGround, ObjectBottomBar::OnViewerSetting)
EVT_BUTTON(ID_ViewerWater, ObjectBottomBar::OnViewerSetting)
EVT_BUTTON(ID_ViewerShadows, ObjectBottomBar::OnViewerSetting)
EVT_BUTTON(ID_ViewerPolyCount, ObjectBottomBar::OnViewerSetting)
EVT_CHOICE(ID_ViewerAnimation, ObjectBottomBar::OnSelectAnim)
EVT_BUTTON(ID_ViewerPlay, ObjectBottomBar::OnSpeed)
EVT_BUTTON(ID_ViewerPause, ObjectBottomBar::OnSpeed)
EVT_BUTTON(ID_ViewerSlow, ObjectBottomBar::OnSpeed)
EVT_BUTTON(ID_ViewerBoundingBox, ObjectBottomBar::OnViewerSetting)
EVT_BUTTON(ID_ViewerAxesMarker, ObjectBottomBar::OnViewerSetting)
EVT_BUTTON(ID_ViewerPropPoints, ObjectBottomBar::OnViewerSetting)
END_EVENT_TABLE();