示例#1
0
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(m_ScenarioEditor.GetScriptInterface().GetContext(), *qryPlayers.defaults);
	AtObj playerDefs = *playerData["PlayerData"];
	for (AtIter p = playerDefs["item"]; p.defined(); ++p)
	{
		players.Add(wxString(p["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"shadows", m_ViewerShadows));
	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));
}
示例#2
0
文件: Map.cpp 项目: krichter722/0ad
void MapSidebar::OnFirstDisplay()
{
	// We do this here becase messages are used which requires simulation to be init'd
	m_MapSettingsCtrl->CreateWidgets();
	m_MapSettingsCtrl->ReadFromEngine();

	// Load the map sizes list
	AtlasMessage::qGetMapSizes qrySizes;
	qrySizes.Post();
	AtObj sizes = AtlasObject::LoadFromJSON(*qrySizes.sizes);
	wxChoice* sizeChoice = wxDynamicCast(FindWindow(ID_RandomSize), wxChoice);
	for (AtIter s = sizes["Data"]["item"]; s.defined(); ++s)
	{
		long tiles = 0;
		wxString(s["Tiles"]).ToLong(&tiles);
		sizeChoice->Append(wxString(s["Name"]), (void*)(intptr_t)tiles);
	}
	sizeChoice->SetSelection(0);

	// Load the RMS script list
	AtlasMessage::qGetRMSData qry;
	qry.Post();
	std::vector<std::string> scripts = *qry.data;
	wxChoice* scriptChoice = wxDynamicCast(FindWindow(ID_RandomScript), wxChoice);
	scriptChoice->Clear();
	for (size_t i = 0; i < scripts.size(); ++i)
	{
		AtObj data = AtlasObject::LoadFromJSON(scripts[i]);
		wxString name(data["settings"]["Name"]);
		scriptChoice->Append(name, new AtObjClientData(*data["settings"]));
	}
	scriptChoice->SetSelection(0);

	Layout();
}
示例#3
0
文件: Terrain.cpp 项目: Marlinc/0ad
void TerrainSidebar::OnResizeMap(wxCommandEvent& WXUNUSED(evt))
{
	wxArrayString sizeNames;
	std::vector<size_t> sizeTiles;

	// Load the map sizes list
	AtlasMessage::qGetMapSizes qrySizes;
	qrySizes.Post();
	AtObj sizes = AtlasObject::LoadFromJSON(m_ScenarioEditor.GetScriptInterface().GetContext(), *qrySizes.sizes);
	for (AtIter s = sizes["Sizes"]["item"]; s.defined(); ++s)
	{
		long tiles = 0;
		wxString(s["Tiles"]).ToLong(&tiles);
		sizeNames.Add(wxString(s["Name"]));
		sizeTiles.push_back((size_t)tiles);
	}

	// TODO: set default based on current map size

	wxSingleChoiceDialog dlg(this, _("Select new map size. WARNING: This probably only works reliably on blank maps."),
			_("Resize map"), sizeNames);

	if (dlg.ShowModal() != wxID_OK)
		return;

	size_t tiles = sizeTiles.at(dlg.GetSelection());
	POST_COMMAND(ResizeMap, (tiles));
}
示例#4
0
void ActorEditorListCtrl::DoImport(AtObj& in)
{
	DeleteData();

	for (AtIter group = in["group"]; group.defined(); ++group)
	{
		for (AtIter variant = group["variant"]; variant.defined(); ++variant)
			AddRow(variant);

		AtObj blank;
		AddRow(blank);
	}

	UpdateDisplay();
}
示例#5
0
文件: Map.cpp 项目: krichter722/0ad
void MapSettingsControl::ReadFromEngine()
{
	AtlasMessage::qGetMapSettings qry;
	qry.Post();
	if (!(*qry.settings).empty())
	{
		// Prevent error if there's no map settings to parse
		m_MapSettings = AtlasObject::LoadFromJSON(*qry.settings);
	}

	// map name
	wxDynamicCast(FindWindow(ID_MapName), wxTextCtrl)->ChangeValue(wxString(m_MapSettings["Name"]));

	// map description
	wxDynamicCast(FindWindow(ID_MapDescription), wxTextCtrl)->ChangeValue(wxString(m_MapSettings["Description"]));

	// map preview
	wxDynamicCast(FindWindow(ID_MapPreview), wxTextCtrl)->ChangeValue(wxString(m_MapSettings["Preview"]));

	// reveal map
	wxDynamicCast(FindWindow(ID_MapReveal), wxCheckBox)->SetValue(wxString(m_MapSettings["RevealMap"]) == L"true");

	// game type / victory conditions
	if (m_MapSettings["GameType"].defined())
		wxDynamicCast(FindWindow(ID_MapType), wxChoice)->SetStringSelection(wxString(m_MapSettings["GameType"]));
	else
		wxDynamicCast(FindWindow(ID_MapType), wxChoice)->SetSelection(0);

	// lock teams
	wxDynamicCast(FindWindow(ID_MapTeams), wxCheckBox)->SetValue(wxString(m_MapSettings["LockTeams"]) == L"true");

	// keywords
	{
		m_MapSettingsKeywords.clear();
		for (AtIter keyword = m_MapSettings["Keywords"]["item"]; keyword.defined(); ++keyword)
			m_MapSettingsKeywords.insert(std::wstring(keyword));

		wxDynamicCast(FindWindow(ID_MapKW_Demo), wxCheckBox)->SetValue(m_MapSettingsKeywords.count(L"demo") != 0);
		wxDynamicCast(FindWindow(ID_MapKW_Naval), wxCheckBox)->SetValue(m_MapSettingsKeywords.count(L"naval") != 0);
	}
}
示例#6
0
void PlayerSettingsControl::ReadFromEngine()
{
	AtlasMessage::qGetMapSettings qry;
	qry.Post();

	if (!(*qry.settings).empty())
	{
		// Prevent error if there's no map settings to parse
		m_MapSettings = AtlasObject::LoadFromJSON(*qry.settings);
	}
	else
	{
		// Use blank object, it will be created next
		m_MapSettings = AtObj();
	}

	AtIter player = m_MapSettings["PlayerData"]["item"];
	if (!m_MapSettings.defined() || !player.defined() || player.count() == 0)
	{
		// Player data missing - set number of players to max
		m_NumPlayers = MAX_NUM_PLAYERS;
	}
	else
		m_NumPlayers = player.count();

	wxASSERT(m_NumPlayers <= MAX_NUM_PLAYERS && m_NumPlayers != 0);

	// To prevent recursion, don't handle GUI events right now
	m_InGUIUpdate = true;

	wxDynamicCast(FindWindow(ID_NumPlayers), wxSpinCtrl)->SetValue(m_NumPlayers);

	// Remove / add extra player pages as needed
	m_Players->ResizePlayers(m_NumPlayers);

	// Update player controls with player data
	AtIter playerDefs = m_PlayerDefaults["item"];
	if (playerDefs.defined())
		++playerDefs;	// skip gaia

	for (size_t i = 0; i < MAX_NUM_PLAYERS; ++i)
	{
		const PlayerPageControls& controls = m_PlayerControls[i];

		// name
		wxString name(_("Unknown"));
		bool defined = player["Name"].defined();
		if (defined)
			name = wxString(player["Name"]);
		else if (playerDefs["Name"].defined())
			name = wxString(playerDefs["Name"]);

		controls.name->SetValue(name);
		wxDynamicCast(FindWindowById(ID_DefaultName, controls.page), DefaultCheckbox)->SetValue(defined);

		// civ
		wxChoice* choice = controls.civ;
		wxString civCode;
		defined = player["Civ"].defined();
		if (defined)
			civCode = wxString(player["Civ"]);
		else
			civCode = wxString(playerDefs["Civ"]);

		for (size_t j = 0; j < choice->GetCount(); ++j)
		{
			wxStringClientData* str = dynamic_cast<wxStringClientData*>(choice->GetClientObject(j));
			if (str->GetData() == civCode)
			{
				choice->SetSelection(j);
				break;
			}
		}
		wxDynamicCast(FindWindowById(ID_DefaultCiv, controls.page), DefaultCheckbox)->SetValue(defined);

		// colour
		wxColour colour;
		AtObj clrObj = *player["Colour"];
		defined = clrObj.defined();
		if (!defined)
			clrObj = *playerDefs["Colour"];
		colour = wxColor((*clrObj["r"]).getInt(), (*clrObj["g"]).getInt(), (*clrObj["b"]).getInt());
		controls.colour->SetBackgroundColour(colour);
		wxDynamicCast(FindWindowById(ID_DefaultColour, controls.page), DefaultCheckbox)->SetValue(defined);

		// player type
		wxString aiID;
		defined = player["AI"].defined();
		if (defined)
			aiID = wxString(player["AI"]);
		else
			aiID = wxString(playerDefs["AI"]);

		choice = controls.ai;
		if (!aiID.empty())
		{
			// AI
			for (size_t j = 0; j < choice->GetCount(); ++j)
			{
				wxStringClientData* str = dynamic_cast<wxStringClientData*>(choice->GetClientObject(j));
				if (str->GetData() == aiID)
				{
					choice->SetSelection(j);
					break;
				}
			}
		}
		else // Human
			choice->SetSelection(0);
		wxDynamicCast(FindWindowById(ID_DefaultAI, controls.page), DefaultCheckbox)->SetValue(defined);

		// resources
		AtObj resObj = *player["Resources"];
		defined = resObj.defined() && resObj["food"].defined();
		if (defined)
			controls.food->SetValue(wxString(resObj["food"]));
		else
			controls.food->SetValue(0);
		wxDynamicCast(FindWindowById(ID_DefaultFood, controls.page), DefaultCheckbox)->SetValue(defined);

		defined = resObj.defined() && resObj["wood"].defined();
		if (defined)
			controls.wood->SetValue(wxString(resObj["wood"]));
		else
			controls.wood->SetValue(0);
		wxDynamicCast(FindWindowById(ID_DefaultWood, controls.page), DefaultCheckbox)->SetValue(defined);

		defined = resObj.defined() && resObj["metal"].defined();
		if (defined)
			controls.metal->SetValue(wxString(resObj["metal"]));
		else
			controls.metal->SetValue(0);
		wxDynamicCast(FindWindowById(ID_DefaultMetal, controls.page), DefaultCheckbox)->SetValue(defined);

		defined = resObj.defined() && resObj["stone"].defined();
		if (defined)
			controls.stone->SetValue(wxString(resObj["stone"]));
		else
			controls.stone->SetValue(0);
		wxDynamicCast(FindWindowById(ID_DefaultStone, controls.page), DefaultCheckbox)->SetValue(defined);

		// population limit
		defined = player["PopulationLimit"].defined();
		if (defined)
			controls.pop->SetValue(wxString(player["PopulationLimit"]));
		else
			controls.pop->SetValue(0);
		wxDynamicCast(FindWindowById(ID_DefaultPop, controls.page), DefaultCheckbox)->SetValue(defined);

		// team
		defined = player["Team"].defined();
		if (defined)
			controls.team->SetSelection((*player["Team"]).getInt() + 1);
		else
			controls.team->SetSelection(0);
		wxDynamicCast(FindWindowById(ID_DefaultTeam, controls.page), DefaultCheckbox)->SetValue(defined);

		// camera
		if (player["StartingCamera"].defined())
		{
			sCameraInfo info;
			// Don't use wxAtof because it depends on locales which
			//	may cause problems with decimal points
			//	see: http://www.wxwidgets.org/docs/faqgtk.htm#locale
			AtObj camPos = *player["StartingCamera"]["Position"];
			info.pX = (float)(*camPos["x"]).getDouble();
			info.pY = (float)(*camPos["y"]).getDouble();
			info.pZ = (float)(*camPos["z"]).getDouble();
			AtObj camRot = *player["StartingCamera"]["Rotation"];
			info.rX = (float)(*camRot["x"]).getDouble();
			info.rY = (float)(*camRot["y"]).getDouble();
			info.rZ = (float)(*camRot["z"]).getDouble();
			
			controls.page->SetCamera(info, true);
		}
		else
			controls.page->SetCamera(sCameraInfo(), false);

		// Only increment AtIters if they are defined
		if (player.defined())
			++player;
		if (playerDefs.defined())
			++playerDefs;
	}

	// Send default properties to engine, since they might not be set
	SendToEngine();

	m_InGUIUpdate = false;
}
示例#7
0
void PlayerSettingsControl::CreateWidgets()
{
	// To prevent recursion, don't handle GUI events right now
	m_InGUIUpdate = true;

	// Load default civ and player data
	wxArrayString civNames;
	wxArrayString civCodes;
	AtlasMessage::qGetCivData qryCiv;
	qryCiv.Post();
	std::vector<std::string> civData = *qryCiv.data;
	for (size_t i = 0; i < civData.size(); ++i)
	{
		AtObj civ = AtlasObject::LoadFromJSON(civData[i]);
		civNames.Add(wxString(civ["Name"]));
		civCodes.Add(wxString(civ["Code"]));
	}

	// Load AI data
	ArrayOfAIData ais(AIData::CompareAIData);
	AtlasMessage::qGetAIData qryAI;
	qryAI.Post();
	AtObj aiData = AtlasObject::LoadFromJSON(*qryAI.data);
	for (AtIter a = aiData["AIData"]["item"]; a.defined(); ++a)
	{
		ais.Add(new AIData(wxString(a["id"]), wxString(a["data"]["name"])));
	}

	// Create player pages
	AtIter playerDefs = m_PlayerDefaults["item"];
	if (playerDefs.defined())
		++playerDefs;	// Skip gaia
	
	for (size_t i = 0; i < MAX_NUM_PLAYERS; ++i)
	{
		// Create new player tab and get controls
		wxString name(_("Unknown"));
		if (playerDefs["Name"].defined())
			name = playerDefs["Name"];
		
		PlayerPageControls controls = m_Players->AddPlayer(name, i);
		m_PlayerControls.push_back(controls);

		// Populate civ choice box
		wxChoice* civChoice = controls.civ;
		for (size_t j = 0; j < civNames.Count(); ++j)
			civChoice->Append(civNames[j], new wxStringClientData(civCodes[j]));
		civChoice->SetSelection(0);

		// Populate ai choice box
		wxChoice* aiChoice = controls.ai;
		aiChoice->Append(_("<None>"), new wxStringClientData());
		for (size_t j = 0; j < ais.Count(); ++j)
			aiChoice->Append(ais[j]->GetName(), new wxStringClientData(ais[j]->GetID()));
		aiChoice->SetSelection(0);

		// Only increment AtIters if they are defined
		if (playerDefs.defined())
			++playerDefs;
	}

	m_InGUIUpdate = false;
}
示例#8
0
ObjectBottomBar::ObjectBottomBar(
	wxWindow* parent,
	ScenarioEditor& scenarioEditor,
	Observable<ObjectSettings>& objectSettings,
	Observable<AtObj>& mapSettings,
	ObjectSidebarImpl* p
)
	: wxPanel(parent, wxID_ANY), p(p), m_ScenarioEditor(scenarioEditor)
{
	m_ViewerWireframe = false;
	m_ViewerMove = false;
	m_ViewerGround = true;
	m_ViewerShadows = true;
	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());
		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());
		viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerBoundingBox, _("Bounding Boxes")), _("Toggle bounding boxes")), wxSizerFlags().Expand());

		wxSizer* viewerButtonsRight = new wxBoxSizer(wxVERTICAL);
		viewerButtonsRight->SetMinSize(110,-1);
		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());

	// ----------------------------------------------------------------------------------

	SetSizer(mainSizer);
}
示例#9
0
ObjectBottomBar::ObjectBottomBar(wxWindow* parent, ScenarioEditor& scenarioEditor,  Observable<ObjectSettings>& objectSettings, Observable<AtObj>& mapSettings, ObjectSidebarImpl* p)
    : wxPanel(parent, wxID_ANY), p(p), m_ScenarioEditor(scenarioEditor)
{
    m_ViewerWireframe = false;
    m_ViewerMove = false;
    m_ViewerGround = true;
    m_ViewerShadows = true;
    m_ViewerPolyCount = false;

    wxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);


    m_ViewerPanel = new wxPanel(this, wxID_ANY);
    wxSizer* viewerSizer = new wxBoxSizer(wxHORIZONTAL);

    wxSizer* viewerButtonsSizer = new wxStaticBoxSizer(wxVERTICAL, m_ViewerPanel, _("Display settings"));
    viewerButtonsSizer->SetMinSize(140, -1);
    viewerButtonsSizer->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerWireframe, _("Wireframe")), _("Toggle wireframe / solid rendering")), wxSizerFlags().Expand());
    viewerButtonsSizer->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerMove, _("Move")), _("Toggle movement along ground when playing walk/run animations")), wxSizerFlags().Expand());
    viewerButtonsSizer->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerGround, _("Ground")), _("Toggle the ground plane")), wxSizerFlags().Expand());
    viewerButtonsSizer->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerShadows, _("Shadows")), _("Toggle shadow rendering")), wxSizerFlags().Expand());
    viewerButtonsSizer->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());
    viewerSizer->Add(viewerButtonsSizer, wxSizerFlags().Expand());

    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());

    m_ViewerPanel->SetSizer(viewerSizer);
    sizer->Add(m_ViewerPanel, wxSizerFlags().Expand());

    m_ViewerPanel->Show(false);


    wxSizer* playerVariationSizer = new wxBoxSizer(wxVERTICAL);

    // TODO: make this a wxChoice instead
    wxComboBox* playerSelect = new PlayerComboBox(this, objectSettings, mapSettings);
    playerVariationSizer->Add(playerSelect);

    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));

    sizer->Add(playerVariationSizer, wxSizerFlags().Expand());

    SetSizer(sizer);
}