# Converted Atlas's object-settings UI into JavaScript.

(Note: This breaks the Actor Viewer, hopefully temporarily.)

This was SVN commit r6932.
This commit is contained in:
Ykkrosh 2009-06-26 22:43:49 +00:00
parent 9a977c9962
commit 34d0f012e8
19 changed files with 615 additions and 638 deletions

View File

@ -0,0 +1,114 @@
// Define the default state settings.
// (It's done this way so that this script can be dynamically reloaded,
// and won't overwrite the previous runtime state but will still pick
// up any new properties)
var defaults = {
objectSettings: {
view: undefined,
selectedObjects: [],
playerID: 1,
actorSelections: [],
variantGroups: []
}
};
defaults.objectSettings.toSObjectSettings = function () {
return {
player: this.playerID,
selections: this.actorSelections
};
}
defaults.objectSettings.onSelectionChange = function () {
if (! this.selectedObjects.length)
return; // TODO: do something sensible here
// TODO: Support multiple selections
var selection = this.selectedObjects[0];
var settings = Atlas.Message.GetObjectSettings(this.view, selection).settings;
this.playerID = settings.player;
this.actorSelections = settings.selections;
this.variantGroups = settings.variantgroups;
this.notifyObservers();
}
/**
* Returns the current actor variation (as a list of chosen variants), based
* on its variant groups and the selection strings. This is equivalent to the
* variation rendered by the game.
*/
defaults.objectSettings.getActorVariation = function ()
{
var selectionMap = {};
for each (var s in this.actorSelections)
selectionMap[s] = 1;
var variation = [];
GROUP: for each (var group in this.variantGroups) {
for each (var variant in group) {
if (variant in selectionMap) {
variation.push(variant);
continue GROUP;
}
}
// None selected; default to first
variation.push(group[0]);
}
return variation;
}
// Merges the 'defs' tree into 'obj', overwriting any old values with
// the same keys
function setDefaults(defs, obj)
{
for (var k in defs) {
if (k in obj)
setDefaults(defs[k], obj[k]);
else
obj[k] = defaults[k];
}
}
function makeObservable(obj)
{
// If the object has already been set up as observable, don't do any more work.
// (In particular, don't delete any old observers, so they don't get lost when
// this script is reloaded.)
if (typeof obj._observers != 'undefined')
return;
obj._observers = [];
obj.registerObserver = function (callback) {
this._observers.push(callback);
}
obj.unregisterObserver = function (callback) {
this._observers = this._observers.filter(function (o) { return o !== callback });
}
obj.notifyObservers = function () {
for each (var o in this._observers)
o(this);
};
}
function postObjectSettingsToGame(objectSettings)
{
if (objectSettings.selectedObjects.length)
Atlas.Message.SetObjectSettings(objectSettings.view,
objectSettings.selectedObjects[0], objectSettings.toSObjectSettings());
}
function init()
{
setDefaults(defaults, Atlas.State);
makeObservable(Atlas.State.objectSettings);
Atlas.State.objectSettings.registerObserver(postObjectSettingsToGame);
}
function deinit() {
Atlas.State.objectSettings.unregisterObserver(postObjectSettingsToGame);
}

View File

@ -18,24 +18,56 @@ function loadScript(name /*, ...*/)
var script = file.readAll(); // TODO: handle errors
file.close();
var args = [];
for (var i = 1; i < arguments.length; ++i)
args.push(arguments[i])
var script = Atlas.LoadScript(name+'.js', script);
scriptReloader.add(name, args, filename);
script.init.apply(null, args);
// Extract the arguments which the function will actually use
// (Can't use Array.slice since arguments isn't an Array)
// (Have to do this rather than use arguments.length, since we sometimes
// pass unused bottomWindows into init and then destroy the window when realising
// it wasn't used, and then we mustn't send the destroyed window again later)
var args = [];
if (script.init)
for (var i = 1; i < 1+script.init.length; ++i)
args.push(arguments[i]);
scriptReloader.add(name, args, filename, script);
if (script.init)
script.init.apply(null, args);
return script;
}
/**
* Helper function for C++ to easily set dot-separated names
*/
function setValue(name, value)
{
var obj = global;
var props = name.split(".");
for (var i = 0; i < props.length-1; ++i)
obj = obj[props[i]];
obj[props[props.length-1]] = value;
}
/**
* Helper function for C++ to easily get dot-separated names
*/
function getValue(name)
{
var obj = global;
var props = name.split(".");
for (var i = 0; i < props.length; ++i)
obj = obj[props[i]];
return obj;
}
function loadXML(name)
{
var relativePath = 'tools/atlas/' + name + '.xml';
var filename = new wxFileName(relativePath, wxPathFormat.UNIX);
filename.normalize(wxPathNormalize.DOTS | wxPathNormalize.ABSOLUTE | wxPathNormalize.TILDE,
Atlas.GetDataDirectory()); // equivalent to MakeAbsolute(dir);
Atlas.GetDataDirectory()); // equivalent to MakeAbsolute(dir);
var file = new wxFFile(filename.fullPath);
var xml = file.readAll(); // TODO: handle errors
@ -46,11 +78,10 @@ function loadXML(name)
return new XML(xml);
}
function init() { /* dummy function to make the script reloader happy */ }
// Automatically reload scripts from disk when they have been modified
var scriptReloader = {
timer: new wxTimer(),
scripts: [], // [ [filename,window], ... ]
scripts: [], // [ {name, args, filename, mtime, window, script}, ... ]
notify: function ()
{
for each (var script in scriptReloader.scripts)
@ -59,13 +90,18 @@ var scriptReloader = {
if (mtime - script.mtime != 0)
{
print('*** Modifications detected - reloading "' + script.name + '"...\n');
script.mtime = mtime;
if (script.script && script.script.deinit)
script.script.deinit();
if (script.name == 'main')
{
// Special case for this file to reload itself
var obj = loadScript(script.name, null);
script.script = loadScript(script.name, null);
// Copy the important state into the new version of this file
obj.scriptReloader.scripts = scriptReloader.scripts;
script.script.scriptReloader.scripts = scriptReloader.scripts;
// Stop this one
scriptReloader.timer.stop();
}
@ -74,25 +110,30 @@ var scriptReloader = {
// TODO: know which arguments are really windows that should be regenerated
for each (var window in script.args)
window.destroyChildren();
loadScript.apply(null, [script.name].concat(script.args));
script.script = loadScript.apply(null, [script.name].concat(script.args));
for each (var window in script.args)
window.layout();
}
}
}
},
add: function (name, args, filename)
add: function (name, args, filename, script)
{
for each (var script in this.scripts)
if (script.name == name)
for each (var s in this.scripts)
if (s.name == name)
return; // stop if this is already loaded
this.scripts.push({ name:name, args:args, filename:filename, mtime:filename.modificationTime });
this.scripts.push({ name:name, args:args, filename:filename, mtime:filename.modificationTime, script:script });
}
};
scriptReloader.timer.onNotify = scriptReloader.notify;
scriptReloader.timer.start(1000);
scriptReloader.add('main', null, getScriptFilename('main'));
loadScript('editorstate');
// Export global functions:
global.loadScript = loadScript;
global.loadXML = loadXML;
global.setValue = setValue;
global.getValue = getValue;

View File

@ -0,0 +1,120 @@
function setObjectFilter(objectList, objects, type)
{
objectList.freeze();
objectList.clear();
var ids = [];
for each (var object in objects) {
if (object.type == type) {
ids.push(object.id);
objectList.append(object.name);
}
}
objectList.objectIDs = ids;
objectList.thaw();
}
var g_observer;
function init(window, bottomWindow)
{
window.sizer = new wxBoxSizer(wxOrientation.VERTICAL);
var objects = Atlas.Message.GetObjectsList().objects;
var objectList = new wxListBox(window, -1, wxDefaultPosition, wxDefaultSize, [],
wxListBox.SINGLE | wxListBox.HSCROLL);
objectList.onListBox = function (evt) {
if (evt.selection == -1)
return;
var id = objectList.objectIDs[evt.selection];
Atlas.SetCurrentToolWith('PlaceObject', id);
};
setObjectFilter(objectList, objects, 0);
var groupSelector = new wxChoice(window, -1, wxDefaultPosition, wxDefaultSize,
["Entities", "Actors (all)"]
);
groupSelector.onChoice = function (evt) {
setObjectFilter(objectList, objects, evt.selection);
};
window.sizer.add(groupSelector, 0, wxStretch.EXPAND);
window.sizer.add(objectList, 1, wxStretch.EXPAND);
bottomWindow.sizer = new wxBoxSizer(wxOrientation.VERTICAL);
var playerSelector = new wxChoice(bottomWindow, -1, wxDefaultPosition, wxDefaultSize,
["Gaia", "Player 1", "Player 2", "Player 3", "Player 4", "Player 5", "Player 6", "Player 7", "Player 8"]
);
bottomWindow.sizer.add(playerSelector);
playerSelector.selection = Atlas.State.objectSettings.playerID;
playerSelector.onChoice = function (evt) {
Atlas.State.objectSettings.playerID = evt.selection;
Atlas.State.objectSettings.notifyObservers();
};
function updatePlayerSelector() {
playerSelector.selection = Atlas.State.objectSettings.playerID;
}
var variationControl = new wxScrolledWindow(bottomWindow, -1);
variationControl.setScrollRate(0, 5);
variationControl.sizer = new wxBoxSizer(wxOrientation.VERTICAL);
var variationControlBox = new wxStaticBoxSizer(new wxStaticBox(bottomWindow, -1, "Actor Variation"), wxOrientation.VERTICAL);
variationControl.sizer.minSize = new wxSize(160, -1);
variationControlBox.add(variationControl, 1);
bottomWindow.sizer.add(variationControlBox, 1);
function onVariationSelect() {
// It's possible for a variant name to appear in multiple groups.
// If so, assume that all the names in each group are the same, so
// we don't have to worry about some impossible combinations (e.g.
// one group "a,b", a second "b,c", and a third "c,a", where's there's
// no set of selections that matches one (and only one) of each group).
//
// So... When a combo box is changed from 'a' to 'b', add 'b' to the new
// selections and make sure any other combo boxes containing both 'a' and
// 'b' no longer contain 'a'.
var sel = this.stringSelection;
var selections = [ sel ];
for each (var c in variationControl.children)
if (c.findString(sel) == wxNOT_FOUND)
selections.push(c.stringSelection);
Atlas.State.objectSettings.actorSelections = selections;
Atlas.State.objectSettings.notifyObservers();
}
function updateVariationControl() {
variationControl.sizer.clear(true);
var settings = Atlas.State.objectSettings;
var variation = settings.getActorVariation();
for (var i = 0; i < settings.variantGroups.length; ++i) {
var choice = new wxChoice(variationControl, -1, wxDefaultPosition, new wxSize(80, -1),
settings.variantGroups[i]);
choice.onChoice = onVariationSelect;
choice.stringSelection = variation[i];
variationControl.sizer.add(choice, 0, wxStretch.EXPAND);
}
variationControlBox.layout();
variationControl.sizer.layout();
bottomWindow.sizer.layout();
}
g_observer = function() {
updatePlayerSelector();
updateVariationControl();
};
Atlas.State.objectSettings.registerObserver(g_observer);
// Initialise the controls
g_observer();
}
function deinit()
{
Atlas.State.objectSettings.unregisterObserver(g_observer);
}

View File

@ -94,6 +94,8 @@ TerrainPreviewPage.prototype = {
itemSizer.cols = numCols;
};
// TODO: Do something clever like load the preview images asynchronously,
// to avoid the annoying freeze when switching tabs
var previews = Atlas.Message.GetTerrainGroupPreviews(this.name, w, h).previews;
var i = 0;
var names = [];
@ -113,6 +115,8 @@ TerrainPreviewPage.prototype = {
itemSizer.add(imgSizer, 0, wxAlignment.CENTRE | wxStretch.EXPAND);
}
// TODO: fix keyboard navigation of the terrain previews
this.panel.layout();
this.loaded = true;
@ -124,10 +128,9 @@ function init(window, bottomWindow)
window.sizer = new wxBoxSizer(wxOrientation.VERTICAL);
var tools = [
/* text label; internal tool name; button */
[ 'Modify', 'AlterElevation', undefined ],
[ 'Flatten', 'FlattenElevation', undefined ],
[ 'Paint', 'PaintTerrain', undefined ]
{ label: 'Modify', name: 'AlterElevation' },
{ label: 'Flatten', name: 'FlattenElevation' },
{ label: 'Paint', name: 'PaintTerrain' },
];
var selectedTool = null; // null if none selected, else an element of 'tools'
@ -135,9 +138,9 @@ function init(window, bottomWindow)
window.sizer.add(toolSizer, 0, wxStretch.EXPAND);
for each (var tool in tools)
{
var button = new wxButton(window, -1, tool[0]);
var button = new wxButton(window, -1, tool.label);
toolSizer.add(button, 1);
tool[2] = button;
tool.button = button;
// Explicitly set the background to the default colour, so that the button
// is always owner-drawn (by the wxButton code), rather than initially using the
@ -157,15 +160,16 @@ function init(window, bottomWindow)
{
// Disable the old tool
if (selectedTool)
selectedTool[2].backgroundColour = wxSystemSettings.getColour(wxSystemSettings.COLOUR_BTNFACE);
selectedTool.button.backgroundColour = wxSystemSettings.getColour(wxSystemSettings.COLOUR_BTNFACE);
// Enable the new one
selectedTool = tool;
this.backgroundColour = new wxColour(0xEE, 0xCC, 0x55);
Atlas.SetCurrentTool(tool[1]);
Atlas.SetCurrentTool(tool.name);
brush.send();
}
};
})(tool);
// TODO: Need to make this interact properly with Tools.cpp/RegisterToolButton so all the buttons are in sync
}
var brushSizer = new wxStaticBoxSizer(new wxStaticBox(window, -1, 'Brush'), wxOrientation.VERTICAL);

View File

@ -18,6 +18,10 @@
#include "ScriptInterface.h"
#include <cassert>
#ifndef _WIN32
# include <typeinfo>
# include <cxxabi.h>
#endif
#include "js/jsapi.h"
@ -37,30 +41,49 @@
#include "GameInterface/Shareable.h"
#include "GameInterface/Messages.h"
#include <boost/preprocessor/punctuation/comma_if.hpp>
#include <boost/preprocessor/repetition/repeat.hpp>
#include <valgrind/valgrind.h>
#define FAIL(msg) do { JS_ReportError(cx, msg); return false; } while (false)
const int RUNTIME_SIZE = 1024*1024; // TODO: how much memory is needed?
const int RUNTIME_SIZE = 4*1024*1024; // TODO: how much memory is needed?
const int STACK_CHUNK_SIZE = 8192;
SubmitCommand g_SubmitCommand; // TODO: globals are ugly
////////////////////////////////////////////////////////////////
namespace
{
template<typename T>
void ReportError(JSContext* cx, const char* title)
{
// TODO: SetPendingException turns the error into a JS-catchable exception,
// but the error report doesn't say anything useful like the line number,
// so I'm just using ReportError instead for now (and failures are uncatchable
// and will terminate the whole script)
//JS_SetPendingException(cx, STRING_TO_JSVAL(JS_NewStringCopyZ(cx, "%s: Unhandled type", title)));
#ifdef _WIN32
JS_ReportError(cx, "%s: Unhandled type", title);
#else
// Give a more informative message on GCC
int status;
char* name = abi::__cxa_demangle(typeid(T).name(), 0, 0, &status);
JS_ReportError(cx, "%s: Unhandled type '%s'", title, name);
free(name);
#endif
}
// Use templated structs instead of functions, so that we can use partial specialisation:
template<typename T> struct FromJSVal
{
static bool Convert(JSContext* cx, jsval WXUNUSED(v), T& WXUNUSED(out))
{
JS_ReportError(cx, "Unrecognised argument type");
// TODO: SetPendingException turns the error into a JS-catchable exception,
// but the error report doesn't say anything useful like the line number,
// so I'm just using ReportError instead for now (and failures are uncatchable
// and will terminate the whole script)
//JS_SetPendingException(cx, STRING_TO_JSVAL(JS_NewStringCopyZ(cx, "Unrecognised argument type")));
ReportError<T>(cx, "FromJSVal");
return false;
}
};
@ -92,7 +115,18 @@ namespace
static bool Convert(JSContext* cx, jsval v, int& out)
{
int32 ret;
if (! JS_ValueToInt32(cx, v, &ret)) return false;
if (! JS_ValueToECMAInt32(cx, v, &ret)) return false;
out = ret;
return true;
}
};
template<> struct FromJSVal<size_t>
{
static bool Convert(JSContext* cx, jsval v, size_t& out)
{
uint32 ret;
if (! JS_ValueToECMAUint32(cx, v, &ret)) return false;
out = ret;
return true;
}
@ -163,12 +197,13 @@ namespace
};
////////////////////////////////////////////////////////////////
// Primitive types:
template<typename T> struct ToJSVal
{
static jsval Convert(JSContext* cx, const T& WXUNUSED(val))
{
JS_ReportError(cx, "Unrecognised query return type");
ReportError<T>(cx, "ToJSVal");
return JSVAL_VOID;
}
};
@ -191,6 +226,14 @@ namespace
}
};
template<> struct ToJSVal<size_t>
{
static jsval Convert(JSContext* WXUNUSED(cx), const size_t& val)
{
return INT_TO_JSVAL(val);
}
};
template<> struct ToJSVal<wxString>
{
static jsval Convert(JSContext* cx, const wxString& val)
@ -221,6 +264,9 @@ namespace
}
};
////////////////////////////////////////////////////////////////
// Compound types:
template<typename T> struct ToJSVal<std::vector<T> >
{
static jsval Convert(JSContext* cx, const std::vector<T>& val)
@ -247,6 +293,7 @@ namespace
};
////////////////////////////////////////////////////////////////
// AtlasMessage structures:
template<> struct ToJSVal<AtlasMessage::sTerrainGroupPreview>
{
@ -269,6 +316,65 @@ namespace
}
};
template<> struct ToJSVal<AtlasMessage::sObjectsListItem>
{
static jsval Convert(JSContext* cx, const AtlasMessage::sObjectsListItem& val)
{
JSObject* obj = JS_NewObject(cx, NULL, NULL, NULL);
if (! obj) return JSVAL_VOID;
JS_AddRoot(cx, &obj);
JS_DefineProperty(cx, obj, "id", ToJSVal<std::wstring>::Convert(cx, *val.id), NULL, NULL, JSPROP_ENUMERATE);
JS_DefineProperty(cx, obj, "name", ToJSVal<std::wstring>::Convert(cx, *val.name), NULL, NULL, JSPROP_ENUMERATE);
JS_DefineProperty(cx, obj, "type", ToJSVal<int>::Convert(cx, val.type), NULL, NULL, JSPROP_ENUMERATE);
JS_RemoveRoot(cx, &obj);
return OBJECT_TO_JSVAL(obj);
}
};
template<> struct ToJSVal<AtlasMessage::sObjectSettings>
{
static jsval Convert(JSContext* cx, const AtlasMessage::sObjectSettings& val)
{
JSObject* obj = JS_NewObject(cx, NULL, NULL, NULL);
if (! obj) return JSVAL_VOID;
JS_AddRoot(cx, &obj);
JS_DefineProperty(cx, obj, "player", ToJSVal<size_t>::Convert(cx, val.player), NULL, NULL, JSPROP_ENUMERATE);
JS_DefineProperty(cx, obj, "selections", ToJSVal<std::vector<std::wstring> >::Convert(cx, *val.selections), NULL, NULL, JSPROP_ENUMERATE);
JS_DefineProperty(cx, obj, "variantgroups", ToJSVal<std::vector<std::vector<std::wstring> > >::Convert(cx, *val.variantgroups), NULL, NULL, JSPROP_ENUMERATE);
JS_RemoveRoot(cx, &obj);
return OBJECT_TO_JSVAL(obj);
}
};
template<> struct FromJSVal<AtlasMessage::sObjectSettings>
{
static bool Convert(JSContext* cx, jsval v, AtlasMessage::sObjectSettings& out)
{
JSObject* obj;
if (! JS_ValueToObject(cx, v, &obj) || obj == NULL)
FAIL("Argument must be an array");
jsval val;
int player;
if (! JS_GetProperty(cx, obj, "player", &val))
FAIL("Failed to get 'player'");
if (! ScriptInterface::FromJSVal(cx, val, player))
FAIL("Failed to convert 'player'");
out.player = player;
std::vector<std::wstring> selections;
if (! JS_GetProperty(cx, obj, "selections", &val))
FAIL("Failed to get 'selections'");
if (! ScriptInterface::FromJSVal(cx, val, selections))
FAIL("Failed to convert 'selections'");
out.selections = selections;
// variantgroups is only used in engine-to-editor, so we don't
// bother converting it here
return true;
}
};
}
template<typename T> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, T& out)
@ -284,10 +390,10 @@ template<typename T> jsval ScriptInterface::ToJSVal(JSContext* cx, const T& v)
// Explicit instantiation of functions that would otherwise be unused in this file
// but are required for linking with other files
template bool ScriptInterface::FromJSVal<wxString>(JSContext*, jsval, wxString&);
template bool ScriptInterface::FromJSVal<float>(JSContext*, jsval, float&);
template jsval ScriptInterface::ToJSVal<wxString>(JSContext*, wxString const&);
template jsval ScriptInterface::ToJSVal<int>(JSContext*, int const&);
template jsval ScriptInterface::ToJSVal<std::vector<int> >(JSContext*, std::vector<int> const&);
////////////////////////////////////////////////////////////////
@ -327,6 +433,7 @@ namespace
wxLogWarning(_T("%s"), logMessage.c_str());
else
wxLogError(_T("%s"), logMessage.c_str());
VALGRIND_PRINTF_BACKTRACE("->");
wxPrintf(_T("wxJS %s: %s\n--------\n"), isWarning ? _T("warning") : _T("error"), logMessage.c_str());
}
@ -377,7 +484,8 @@ ScriptInterface_impl::ScriptInterface_impl()
m_cx = JS_NewContext(m_rt, STACK_CHUNK_SIZE);
assert(m_cx);
JS_BeginRequest(m_cx); // if you get linker errors, see the comment in the .h about JS_THREADSAFE
JS_BeginRequest(m_cx); // if you get linker errors, see the comment in ScriptInterface.h about JS_THREADSAFE
// (TODO: are we using requests correctly? (Probably not; how much does it matter?))
JS_SetContextPrivate(m_cx, NULL);
@ -400,9 +508,10 @@ ScriptInterface_impl::ScriptInterface_impl()
JS_DefineFunction(m_cx, m_glob, "print", ::print, 0, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT);
m_atlas = JS_DefineObject(m_cx, m_glob, "Atlas", NULL, NULL, JSPROP_READONLY|JSPROP_PERMANENT);
m_atlas = JS_DefineObject(m_cx, m_glob, "Atlas", NULL, NULL, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT);
JS_DefineFunction(m_cx, m_atlas, "ForceGC", ::ForceGC, 0, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT);
JS_DefineFunction(m_cx, m_atlas, "LoadScript", ::LoadScript, 2, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT);
JS_DefineObject(m_cx, m_atlas, "State", NULL, NULL, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT);
RegisterMessages(m_atlas);
}
@ -435,9 +544,10 @@ void ScriptInterface_impl::Register(const char* name, JSNative fptr, uintN nargs
}
ScriptInterface::ScriptInterface()
: m(new ScriptInterface_impl)
ScriptInterface::ScriptInterface(SubmitCommand submitCommand)
: m(new ScriptInterface_impl())
{
g_SubmitCommand = submitCommand;
}
ScriptInterface::~ScriptInterface()
@ -459,6 +569,55 @@ void ScriptInterface::Register(const char* name, JSNative fptr, size_t nargs)
m->Register(name, fptr, (uintN)nargs);
}
JSContext* ScriptInterface::GetContext()
{
return m->m_cx;
}
void ScriptInterface::AddRoot(void* ptr)
{
JS_AddRoot(m->m_cx, ptr);
}
void ScriptInterface::RemoveRoot(void* ptr)
{
JS_RemoveRoot(m->m_cx, ptr);
}
void ScriptInterface::SetValue_(const wxString& name, jsval val)
{
jsval jsName = ToJSVal(m->m_cx, name);
const uintN argc = 2;
jsval argv[argc] = { jsName, val };
jsval rval;
JSBool ok = JS_CallFunctionName(m->m_cx, m->m_glob, "setValue", argc, argv, &rval); // TODO: error checking
}
bool ScriptInterface::GetValue_(const wxString& name, jsval& ret)
{
jsval jsName = ToJSVal(m->m_cx, name);
const uintN argc = 1;
jsval argv[argc] = { jsName };
return JS_CallFunctionName(m->m_cx, m->m_glob, "getValue", argc, argv, &ret);
}
void ScriptInterface::Eval(const wxString& script)
{
jsval rval;
JSBool ok = JS_EvaluateScript(m->m_cx, m->m_glob,
script.mb_str(), script.length(), NULL, 0, &rval);
// TODO: error checking
}
bool ScriptInterface::Eval_(const wxString& script, jsval& rval)
{
JSBool ok = JS_EvaluateScript(m->m_cx, m->m_glob,
script.mb_str(), script.length(), NULL, 0, &rval);
return ok;
}
void ScriptInterface::LoadScript(const wxString& filename, const wxString& code)
{
size_t codeLength;
@ -540,6 +699,15 @@ std::pair<wxPanel*, wxPanel*> ScriptInterface::LoadScriptAsSidebar(const wxStrin
return JS_TRUE; \
}
#define COMMAND(name, merge, vals) \
JSBool call_##name(JSContext* cx, JSObject* WXUNUSED(obj), uintN WXUNUSED(argc), jsval* argv, jsval* WXUNUSED(rval)) \
{ \
(void)cx; (void)argv; /* avoid 'unused parameter' warnings */ \
BOOST_PP_SEQ_FOR_EACH_I(CONVERT_ARGS, ~, vals) \
g_SubmitCommand(new AtlasMessage::m##name (AtlasMessage::d##name ( BOOST_PP_SEQ_FOR_EACH_I(ARG_LIST, ~, vals) ))); \
return JS_TRUE; \
}
#define QUERY(name, in_vals, out_vals) \
JSBool call_##name(JSContext* cx, JSObject* WXUNUSED(obj), uintN WXUNUSED(argc), jsval* argv, jsval* rval) \
{ \
@ -554,8 +722,6 @@ std::pair<wxPanel*, wxPanel*> ScriptInterface::LoadScriptAsSidebar(const wxStrin
return JS_TRUE; \
}
#define COMMAND(name, merge, vals)
#define MESSAGES_SKIP_SETUP
#define MESSAGES_SKIP_STRUCTS
@ -570,6 +736,7 @@ namespace
}
#undef MESSAGE
#undef COMMAND
#undef QUERY
void ScriptInterface_impl::RegisterMessages(JSObject* parent)
@ -583,13 +750,20 @@ void ScriptInterface_impl::RegisterMessages(JSObject* parent)
ret = JS_DefineFunction(m_cx, obj, #name, call_##name, BOOST_PP_SEQ_SIZE((~)vals)-1, \
JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT);
#define COMMAND(name, merge, vals) \
ret = JS_DefineFunction(m_cx, obj, #name, call_##name, BOOST_PP_SEQ_SIZE((~)vals)-1, \
JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT);
#define QUERY(name, in_vals, out_vals) \
ret = JS_DefineFunction(m_cx, obj, #name, call_##name, BOOST_PP_SEQ_SIZE((~)in_vals)-1, \
JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT);
// TODO: #define COMMAND(name, merge, vals) ...
#undef INCLUDED_MESSAGES
#include "GameInterface/Messages.h"
#undef MESSAGE
#undef COMMAND
#undef QUERY
}

View File

@ -37,15 +37,23 @@ class wxWindow;
class wxString;
class wxPanel;
namespace AtlasMessage { struct mWorldCommand; }
typedef void (*SubmitCommand)(AtlasMessage::mWorldCommand* command);
struct ScriptInterface_impl;
class ScriptInterface
{
public:
ScriptInterface();
ScriptInterface(SubmitCommand submitCommand);
~ScriptInterface();
void SetCallbackData(void* cbdata);
static void* GetCallbackData(JSContext* cx);
template <typename T> void SetValue(const wxString& name, const T& val);
template <typename T> bool GetValue(const wxString& name, T& ret);
void Eval(const wxString& script);
template <typename T> bool Eval(const wxString& script, T& ret);
// Defined elsewhere:
// template <TR, T0..., TR (*fptr) (void* cbdata, T0...)>
// void RegisterFunction(const char* functionName);
@ -59,8 +67,43 @@ public:
template <typename T> static bool FromJSVal(JSContext* cx, jsval val, T& ret);
template <typename T> static jsval ToJSVal(JSContext* cx, const T& val);
private:
JSContext* GetContext();
void AddRoot(void* ptr);
void RemoveRoot(void* ptr);
void SetValue_(const wxString& name, jsval val);
bool GetValue_(const wxString& name, jsval& ret);
bool Eval_(const wxString& name, jsval& ret);
void Register(const char* name, JSNative fptr, size_t nargs);
std::auto_ptr<ScriptInterface_impl> m;
// The nasty macro/template bits are split into a separate file so you don't have to look at them
#include "NativeWrapper.inl"
template <typename T>
void ScriptInterface::SetValue(const wxString& name, const T& val)
{
return SetValue_(name, ToJSVal(GetContext(), val));
}
template <typename T>
bool ScriptInterface::GetValue(const wxString& name, T& ret)
{
jsval jsRet;
if (! GetValue_(name, jsRet)) return false;
AddRoot(&jsRet); // root it while we do some more work (TODO: is this really necessary?)
bool ok = FromJSVal(GetContext(), jsRet, ret);
RemoveRoot(&jsRet);
return ok;
}
template <typename T>
bool ScriptInterface::Eval(const wxString& script, T& ret)
{
jsval jsRet;
if (! Eval_(script, jsRet)) return false;
AddRoot(&jsRet); // root it while we do some more work
bool ok = FromJSVal(GetContext(), jsRet, ret);
RemoveRoot(&jsRet);
return ok;
}

View File

@ -27,7 +27,7 @@
#include "ScenarioEditor/Tools/Common/Tools.h"
#include "ScenarioEditor/ScenarioEditor.h"
#include "ScenarioEditor/Sections/Environment/LightControl.h"
#include "ScenarioEditor/Sections/Object/VariationControl.h"
//#include "ScenarioEditor/Sections/Object/VariationControl.h"
#include "GameInterface/Messages.h"
@ -182,15 +182,20 @@ static void SendToGame(const AtlasMessage::sEnvironmentSettings& settings)
POST_COMMAND(SetEnvironmentSettings, (settings));
}
ActorViewer::ActorViewer(wxWindow* parent)
ActorViewer::ActorViewer(wxWindow* parent, ScriptInterface& scriptInterface)
: wxFrame(parent, wxID_ANY, _("Actor Viewer"), wxDefaultPosition, wxSize(800, 600)),
m_CurrentSpeed(0.f), m_BackgroundColour(wxColour(255, 255, 255)),
m_ToggledWalking(false), m_ToggledWireframe(false), m_ToggledGround(true),
m_ToggledShadows(true), m_ToggledStats(false),
m_ObjectSettings(m_ObjectSelection, AtlasMessage::eRenderView::ACTOR)
m_ScriptInterface(scriptInterface),
m_ObjectSettings(m_ObjectSelection, m_ScriptInterface)
{
SetIcon(wxIcon(_T("ICON_ActorEditor")));
// XXX: need to init m_ScriptInterface
m_ObjectSettings.Init(AtlasMessage::eRenderView::ACTOR);
SnapSplitterWindow* splitter = new SnapSplitterWindow(this, 0);
splitter->SetDefaultSashPosition(250);
@ -301,7 +306,7 @@ ActorViewer::ActorViewer(wxWindow* parent)
wxSizer* bottomRightSizer = new wxBoxSizer(wxVERTICAL);
wxSizer* playButtonSizer = new wxBoxSizer(wxHORIZONTAL);
wxSizer* optionButtonSizer = new wxBoxSizer(wxVERTICAL);
wxSizer* variationSizer = new wxStaticBoxSizer(wxVERTICAL, sidePanel, _("Variation"));
// wxSizer* variationSizer = new wxStaticBoxSizer(wxVERTICAL, sidePanel, _("Variation"));
playButtonSizer->Add(new wxButton(sidePanel, ID_Play, _("Play")), wxSizerFlags().Proportion(1));
playButtonSizer->Add(new wxButton(sidePanel, ID_Pause, _("Pause")), wxSizerFlags().Proportion(1));
@ -315,7 +320,7 @@ ActorViewer::ActorViewer(wxWindow* parent)
optionButtonSizer->Add(Tooltipped(new wxButton(sidePanel, ID_ToggleShadows, _("Shadows")), _("Toggle shadow rendering")), wxSizerFlags().Expand());
optionButtonSizer->Add(Tooltipped(new wxButton(sidePanel, ID_ToggleStats, _("Poly count")), _("Toggle polygon-count statistics - turn off ground and shadows for more useful data")), wxSizerFlags().Expand());
variationSizer->Add(new VariationControl(sidePanel, m_ObjectSettings), wxSizerFlags().Expand().Proportion(1));
// variationSizer->Add(new VariationControl(sidePanel, m_ObjectSettings), wxSizerFlags().Expand().Proportion(1));
mainSizer->Add(m_TreeCtrl, wxSizerFlags().Expand().Proportion(1));
mainSizer->Add(bottomSizer, wxSizerFlags().Expand());
@ -328,7 +333,7 @@ ActorViewer::ActorViewer(wxWindow* parent)
bottomRightSizer->Add(m_AnimationBox, wxSizerFlags().Expand());
bottomRightSizer->Add(playButtonSizer, wxSizerFlags().Expand());
bottomRightSizer->Add(variationSizer, wxSizerFlags().Expand().Proportion(1));
// bottomRightSizer->Add(variationSizer, wxSizerFlags().Expand().Proportion(1));
sidePanel->SetSizer(mainSizer);

View File

@ -25,11 +25,12 @@
class wxTreeCtrl;
class wxTreeEvent;
class ScriptInterface;
class ActorViewer : public wxFrame
{
public:
ActorViewer(wxWindow* parent);
ActorViewer(wxWindow* parent, ScriptInterface& scriptInterface);
private:
void SetActorView(bool flushCache = false);
@ -49,8 +50,10 @@ private:
wxString m_CurrentActor;
float m_CurrentSpeed;
ScriptInterface& m_ScriptInterface;
Observable<std::vector<AtlasMessage::ObjectID> > m_ObjectSelection;
Observable<ObjectSettings> m_ObjectSettings;
ObjectSettings m_ObjectSettings;
wxColour m_BackgroundColour;
bool m_ToggledWireframe, m_ToggledWalking, m_ToggledGround, m_ToggledShadows, m_ToggledStats;

View File

@ -53,7 +53,7 @@ public:
template <typename T1>
explicit Observable(const T1& a1) : T(a1) {}
template <typename T1, typename T2>
explicit Observable(T1& a1, T2 a2) : T(a1, a2) {}
explicit Observable(T1& a1, T2& a2) : T(a1, a2) {}
template<typename C> ObservableConnection RegisterObserver(int order, void (C::*callback) (const T&), C* obj)
{

View File

@ -155,6 +155,10 @@ ATLASDLLIMPEXP void Atlas_ReportError()
///ReportError(); // janwas: disabled until ErrorReporter.cpp compiles
}
void ScenarioEditorSubmitCommand(AtlasMessage::mWorldCommand* command)
{
ScenarioEditor::GetCommandProc().Submit(new WorldCommand(command));
}
class AtlasDLLApp : public wxApp
{
@ -188,7 +192,6 @@ public:
wxFrame* frame;
#define MAYBE(t) if (g_InitialWindowType == _T(#t)) frame = new t(NULL); else
MAYBE(ActorEditor)
MAYBE(ActorViewer)
MAYBE(ColourTester)
#ifdef USE_AOE3ED
MAYBE(ArchiveViewer)
@ -198,9 +201,14 @@ public:
// else
if (g_InitialWindowType == _T("ScenarioEditor"))
{
m_ScriptInterface = new ScriptInterface();
m_ScriptInterface = new ScriptInterface(&ScenarioEditorSubmitCommand);
frame = new ScenarioEditor(NULL, *m_ScriptInterface);
}
else if (g_InitialWindowType == _T("ActorViewer"))
{
m_ScriptInterface = new ScriptInterface(&ScenarioEditorSubmitCommand);
frame = new ActorViewer(NULL, *m_ScriptInterface);
}
else
{
wxFAIL_MSG(_("Internal error: invalid window type"));

View File

@ -30,8 +30,8 @@
#include "General/AtlasEventLoop.h"
#include "General/Datafile.h"
#include "HighResTimer/HighResTimer.h"
#include "Buttons/ToolButton.h"
#include "CustomControls/HighResTimer/HighResTimer.h"
#include "CustomControls/Buttons/ToolButton.h"
#include "CustomControls/Canvas/Canvas.h"
#include "GameInterface/MessagePasser.h"
@ -295,11 +295,18 @@ AtlasWindowCommandProc& ScenarioEditor::GetCommandProc() { return g_CommandProc;
namespace
{
// Wrapper function because SetCurrentTool takes an optional argument, which JS doesn't like
void SetCurrentTool_script(void* cbdata, wxString name)
// Wrapper functions for scripts
void SetCurrentTool_(void* cbdata, wxString name)
{
static_cast<ScenarioEditor*>(cbdata)->GetToolManager().SetCurrentTool(name);
}
void SetCurrentToolWith(void* cbdata, wxString name, wxString arg)
{
static_cast<ScenarioEditor*>(cbdata)->GetToolManager().SetCurrentTool(name, &arg);
}
wxString GetDataDirectory(void*)
{
return Datafile::GetDataDirectory();
@ -319,7 +326,7 @@ namespace
ScenarioEditor::ScenarioEditor(wxWindow* parent, ScriptInterface& scriptInterface)
: wxFrame(parent, wxID_ANY, _T(""), wxDefaultPosition, wxSize(1024, 768))
, m_FileHistory(_T("Scenario Editor")), m_ScriptInterface(scriptInterface)
, m_ObjectSettings(g_SelectedObjects, AtlasMessage::eRenderView::GAME)
, m_ObjectSettings(g_SelectedObjects, m_ScriptInterface)
, m_ToolManager(this)
{
// Global application initialisation:
@ -338,7 +345,8 @@ ScenarioEditor::ScenarioEditor(wxWindow* parent, ScriptInterface& scriptInterfac
// Script interface functions
GetScriptInterface().SetCallbackData(static_cast<void*>(this));
GetScriptInterface().RegisterFunction<wxString, GetDataDirectory>("GetDataDirectory");
GetScriptInterface().RegisterFunction<void, wxString, SetCurrentTool_script>("SetCurrentTool");
GetScriptInterface().RegisterFunction<void, wxString, SetCurrentTool_>("SetCurrentTool");
GetScriptInterface().RegisterFunction<void, wxString, wxString, SetCurrentToolWith>("SetCurrentToolWith");
GetScriptInterface().RegisterFunction<void, float, SetBrushStrength>("SetBrushStrength");
GetScriptInterface().RegisterFunction<void, wxString, SetSelectedTexture>("SetSelectedTexture");
@ -353,6 +361,9 @@ ScenarioEditor::ScenarioEditor(wxWindow* parent, ScriptInterface& scriptInterfac
GetScriptInterface().LoadScript(filename.GetFullName(), script);
}
// Initialise things that rely on scripts
m_ObjectSettings.Init(AtlasMessage::eRenderView::GAME);
//////////////////////////////////////////////////////////////////////////
// Menu

View File

@ -58,7 +58,7 @@ public:
static float GetSpeedModifier();
ScriptInterface& GetScriptInterface() const { return m_ScriptInterface; }
Observable<ObjectSettings>& GetObjectSettings() { return m_ObjectSettings; }
ObjectSettings& GetObjectSettings() { return m_ObjectSettings; }
ToolManager& GetToolManager() { return m_ToolManager; }

View File

@ -22,13 +22,12 @@
#include "SectionLayout.h"
#include "SnapSplitterWindow/SnapSplitterWindow.h"
#include "CustomControls/SnapSplitterWindow/SnapSplitterWindow.h"
#include "ScenarioEditor.h"
#include "AtlasScript/ScriptInterface.h"
#include "Sections/Terrain/Terrain.h"
#include "Sections/Object/Object.h"
#include "Sections/Environment/Environment.h"
#include "Sections/Cinematic/Cinematic.h"
#include "Sections/Trigger/Trigger.h"
@ -317,7 +316,7 @@ void SectionLayout::Build(ScenarioEditor& scenarioEditor)
ADD_SIDEBAR_SCRIPT(_T("map"), _T("map.png"), _("Map"));
ADD_SIDEBAR_SCRIPT(_T("terrain"), _T("terrain.png"), _("Terrain"));
//ADD_SIDEBAR(TerrainSidebar, _T("terrain.png"), _("Terrain"));
ADD_SIDEBAR(ObjectSidebar, _T("object.png"), _("Object"));
ADD_SIDEBAR_SCRIPT(_T("object"), _T("object.png"), _("Object"));
ADD_SIDEBAR(EnvironmentSidebar, _T("environment.png"), _("Environment"));
#ifndef ATLAS_PUBLIC_RELEASE

View File

@ -1,208 +0,0 @@
/* Copyright (C) 2009 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 "Object.h"
#include "Buttons/ToolButton.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"
class ObjectSelectListBox : public wxListBox
{
public:
ObjectSelectListBox(wxWindow* parent, ToolManager& toolManager)
: wxListBox(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, NULL, wxLB_SINGLE|wxLB_HSCROLL)
, m_ToolManager(toolManager)
{
}
void OnSelect(wxCommandEvent& evt)
{
// On selecting an object, enable the PlaceObject tool with this object
wxString id = static_cast<wxStringClientData*>(evt.GetClientObject())->GetData();
m_ToolManager.SetCurrentTool(_T("PlaceObject"), &id);
}
private:
ToolManager& m_ToolManager;
DECLARE_EVENT_TABLE();
};
BEGIN_EVENT_TABLE(ObjectSelectListBox, wxListBox)
EVT_LISTBOX(wxID_ANY, ObjectSelectListBox::OnSelect)
END_EVENT_TABLE();
class ObjectChoiceCtrl : public wxChoice
{
public:
ObjectChoiceCtrl(wxWindow* parent, const wxArrayString& strings, ObjectSidebar& sidebar)
: wxChoice(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, strings),
m_Sidebar(sidebar)
{
SetSelection(0);
}
void OnSelect(wxCommandEvent& evt)
{
// Switch between displayed lists of objects (e.g. entities vs actors)
m_Sidebar.SetObjectFilter(evt.GetSelection());
}
private:
ObjectSidebar& m_Sidebar;
DECLARE_EVENT_TABLE();
};
BEGIN_EVENT_TABLE(ObjectChoiceCtrl, wxChoice)
EVT_CHOICE(wxID_ANY, ObjectChoiceCtrl::OnSelect)
END_EVENT_TABLE();
//////////////////////////////////////////////////////////////////////////
class ObjectBottomBar : public wxPanel
{
public:
ObjectBottomBar(wxWindow* parent, Observable<ObjectSettings>& objectSettings);
};
struct ObjectSidebarImpl
{
ObjectSidebarImpl() : m_BottomBar(NULL), m_ObjectListBox(NULL) { }
wxWindow* m_BottomBar;
wxListBox* m_ObjectListBox;
std::vector<AtlasMessage::sObjectsListItem> m_Objects;
};
ObjectSidebar::ObjectSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebarContainer, wxWindow* bottomBarContainer)
: Sidebar(scenarioEditor, sidebarContainer, bottomBarContainer), p(new ObjectSidebarImpl())
{
wxArrayString strings;
strings.Add(_("Entities"));
strings.Add(_("Actors (all)"));
m_MainSizer->Add(new ObjectChoiceCtrl(this, strings, *this), wxSizerFlags().Expand());
p->m_ObjectListBox = new ObjectSelectListBox(this, scenarioEditor.GetToolManager());
m_MainSizer->Add(p->m_ObjectListBox, wxSizerFlags().Proportion(1).Expand());
m_BottomBar = new ObjectBottomBar(bottomBarContainer, scenarioEditor.GetObjectSettings());
}
ObjectSidebar::~ObjectSidebar()
{
delete p;
}
void ObjectSidebar::OnFirstDisplay()
{
wxBusyInfo busy (_("Loading list of objects"));
// Get the list of objects from the game
AtlasMessage::qGetObjectsList qry;
qry.Post();
p->m_Objects = *qry.objects;
// Display first group of objects
SetObjectFilter(0);
}
void ObjectSidebar::SetObjectFilter(int type)
{
p->m_ObjectListBox->Freeze();
p->m_ObjectListBox->Clear();
for (std::vector<AtlasMessage::sObjectsListItem>::iterator it = p->m_Objects.begin(); it != p->m_Objects.end(); ++it)
{
if (it->type == type)
{
wxString id = it->id.c_str();
wxString name = it->name.c_str();
p->m_ObjectListBox->Append(name, new wxStringClientData(id));
}
}
p->m_ObjectListBox->Thaw();
}
//////////////////////////////////////////////////////////////////////////
class PlayerComboBox : public wxComboBox
{
public:
PlayerComboBox(wxWindow* parent, wxArrayString& choices, Observable<ObjectSettings>& objectSettings)
: wxComboBox(parent, -1, choices[objectSettings.GetPlayerID()], wxDefaultPosition, wxDefaultSize, choices, wxCB_READONLY)
, m_ObjectSettings(objectSettings)
{
m_Conn = m_ObjectSettings.RegisterObserver(1, &PlayerComboBox::OnObjectSettingsChange, this);
}
private:
ObservableScopedConnection m_Conn;
Observable<ObjectSettings>& m_ObjectSettings;
void OnObjectSettingsChange(const ObjectSettings& settings)
{
SetSelection((long)settings.GetPlayerID());
}
void OnSelect(wxCommandEvent& evt)
{
m_ObjectSettings.SetPlayerID(evt.GetInt());
m_ObjectSettings.NotifyObserversExcept(m_Conn);
}
DECLARE_EVENT_TABLE();
};
BEGIN_EVENT_TABLE(PlayerComboBox, wxComboBox)
EVT_COMBOBOX(wxID_ANY, PlayerComboBox::OnSelect)
END_EVENT_TABLE();
//////////////////////////////////////////////////////////////////////////
ObjectBottomBar::ObjectBottomBar(wxWindow* parent, Observable<ObjectSettings>& objectSettings)
: wxPanel(parent, wxID_ANY)
{
wxSizer* sizer = new wxBoxSizer(wxVERTICAL);
wxArrayString players;
// TODO: get proper player names
players.Add(_("Gaia"));
players.Add(_("Player 1"));
players.Add(_("Player 2"));
players.Add(_("Player 3"));
players.Add(_("Player 4"));
players.Add(_("Player 5"));
players.Add(_("Player 6"));
players.Add(_("Player 7"));
players.Add(_("Player 8"));
wxComboBox* playerSelect = new PlayerComboBox(this, players, objectSettings);
sizer->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());
sizer->Add(variationSizer, wxSizerFlags().Proportion(1));
SetSizer(sizer);
}

View File

@ -1,33 +0,0 @@
/* Copyright (C) 2009 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 "../Common/Sidebar.h"
struct ObjectSidebarImpl;
class ObjectSidebar : public Sidebar
{
public:
ObjectSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebarContainer, wxWindow* bottomBarContainer);
~ObjectSidebar();
void SetObjectFilter(int type);
protected:
virtual void OnFirstDisplay();
private:
ObjectSidebarImpl* p;
};

View File

@ -1,146 +0,0 @@
/* Copyright (C) 2009 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 "VariationControl.h"
#include "ScenarioEditor/Tools/Common/ObjectSettings.h"
VariationControl::VariationControl(wxWindow* parent, Observable<ObjectSettings>& objectSettings)
: wxScrolledWindow(parent, -1),
m_ObjectSettings(objectSettings)
{
m_Conn = m_ObjectSettings.RegisterObserver(1, &VariationControl::OnObjectSettingsChange, this);
SetScrollRate(0, 5);
m_Sizer = new wxBoxSizer(wxVERTICAL);
SetSizer(m_Sizer);
}
// Event handler shared by all the combo boxes created by this window
void VariationControl::OnSelect(wxCommandEvent& evt)
{
std::set<wxString> selections;
// It's possible for a variant name to appear in multiple groups.
// If so, assume that all the names in each group are the same, so
// we don't have to worry about some impossible combinations (e.g.
// one group "a,b", a second "b,c", and a third "c,a", where's there's
// no set of selections that matches one (and only one) of each group).
//
// So... When a combo box is changed from 'a' to 'b', add 'b' to the new
// selections and make sure any other combo boxes containing both 'a' and
// 'b' no longer contain 'a'.
wxComboBox* thisComboBox = wxDynamicCast(evt.GetEventObject(), wxComboBox);
wxCHECK(thisComboBox != NULL, );
wxString newValue = thisComboBox->GetValue();
selections.insert(newValue);
for (size_t i = 0; i < m_ComboBoxes.size(); ++i)
{
wxComboBox* comboBox = m_ComboBoxes[i];
// If our newly selected value is used in another combobox, we want
// that combobox to use the new value, so don't add its old value
// to the list of selections
if (comboBox->FindString(newValue) == wxNOT_FOUND)
selections.insert(comboBox->GetValue());
}
m_ObjectSettings.SetActorSelections(selections);
m_ObjectSettings.NotifyObserversExcept(m_Conn);
RefreshObjectSettings();
}
void VariationControl::OnObjectSettingsChange(const ObjectSettings& settings)
{
Freeze();
const std::vector<ObjectSettings::Group>& variation = settings.GetActorVariation();
// Creating combo boxes seems to be pretty expensive - so we create as
// few as possible, by never deleting any.
size_t oldCount = m_ComboBoxes.size();
size_t newCount = variation.size();
// If we have too many combo boxes, hide the excess ones
for (size_t i = newCount; i < oldCount; ++i)
{
m_ComboBoxes[i]->Show(false);
}
for (size_t i = 0; i < variation.size(); ++i)
{
const ObjectSettings::Group& group = variation[i];
if (i < oldCount)
{
// Already got enough boxes available, so use an old one
wxComboBox* comboBox = m_ComboBoxes[i];
// Replace the contents of the old combobox with the new data
comboBox->Freeze();
comboBox->Clear();
comboBox->Append(group.variants);
comboBox->SetValue(group.chosen);
comboBox->Show(true);
comboBox->Thaw();
}
else
{
// Create an initially empty combobox, because we can fill it
// quicker than the default constructor can
wxComboBox* combo = new wxComboBox(this, -1, wxEmptyString, wxDefaultPosition,
wxSize(80, wxDefaultCoord), wxArrayString(), wxCB_READONLY);
// Freeze it before adding all the values
combo->Freeze();
combo->Append(group.variants);
combo->SetValue(group.chosen);
combo->Thaw();
// Add the on-select event handler
combo->Connect(wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED,
wxCommandEventHandler(VariationControl::OnSelect), NULL, this);
// Add box to sizer and list
m_Sizer->Add(combo, wxSizerFlags().Expand());
m_ComboBoxes.push_back(combo);
}
}
Layout();
Thaw();
// Make the scrollbars appear when appropriate
FitInside();
}
void VariationControl::RefreshObjectSettings()
{
const std::vector<ObjectSettings::Group>& variation = m_ObjectSettings.GetActorVariation();
// For each group, set the corresponding combobox's value to the chosen one
size_t i = 0;
for (std::vector<ObjectSettings::Group>::const_iterator group = variation.begin();
group != variation.end() && i < m_ComboBoxes.size();
++group, ++i)
{
m_ComboBoxes[i]->SetValue(group->chosen);
}
}

View File

@ -1,42 +0,0 @@
/* Copyright (C) 2009 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/>.
*/
#ifndef INCLUDED_VARIATIONCONTROL
#define INCLUDED_VARIATIONCONTROL
#include "General/Observable.h"
class ObjectSettings;
class VariationControl : public wxScrolledWindow
{
public:
VariationControl(wxWindow* parent, Observable<ObjectSettings>& objectSettings);
private:
void OnSelect(wxCommandEvent& evt);
void OnObjectSettingsChange(const ObjectSettings& settings);
void RefreshObjectSettings();
ObservableScopedConnection m_Conn;
Observable<ObjectSettings>& m_ObjectSettings;
std::vector<wxComboBox*> m_ComboBoxes;
wxSizer* m_Sizer;
};
#endif // INCLUDED_VARIATIONCONTROL

View File

@ -21,133 +21,42 @@
#include "GameInterface/Messages.h"
#include "ScenarioEditor/ScenarioEditor.h"
#include "AtlasScript/ScriptInterface.h"
ObjectSettings::ObjectSettings(Observable<std::vector<AtlasMessage::ObjectID> >& selectedObjects, int view)
: m_PlayerID(0), m_SelectedObjects(selectedObjects), m_View(view)
ObjectSettings::ObjectSettings(Observable<std::vector<AtlasMessage::ObjectID> >& selectedObjects, ScriptInterface& scriptInterface)
: m_ScriptInterface(scriptInterface)
{
m_Conn = m_SelectedObjects.RegisterObserver(0, &ObjectSettings::OnSelectionChange, this);
m_Conn = selectedObjects.RegisterObserver(0, &ObjectSettings::OnSelectionChange, this);
}
size_t ObjectSettings::GetPlayerID() const
void ObjectSettings::Init(int view)
{
return m_PlayerID;
m_ScriptInterface.SetValue(_T("Atlas.State.objectSettings.view"), view);
}
void ObjectSettings::SetPlayerID(int playerID)
{
m_PlayerID = playerID;
PostToGame();
}
const std::set<wxString>& ObjectSettings::GetActorSelections() const
{
return m_ActorSelections;
}
void ObjectSettings::SetActorSelections(const std::set<wxString>& selections)
{
m_ActorSelections = selections;
PostToGame();
}
const std::vector<ObjectSettings::Group> ObjectSettings::GetActorVariation() const
{
std::vector<Group> variation;
for (std::vector<wxArrayString>::const_iterator grp = m_VariantGroups.begin();
grp != m_VariantGroups.end();
++grp)
{
Group group;
group.variants = *grp;
// Variant choice method, as used by the game: Choose the first variant
// which matches any of the selections
size_t chosen = 0; // default to first
for (size_t i = 0; i < grp->GetCount(); ++i)
{
if (m_ActorSelections.find(grp->Item(i)) != m_ActorSelections.end())
{
chosen = i;
break;
}
}
group.chosen = grp->Item(chosen);
variation.push_back(group);
}
return variation;
m_ScriptInterface.SetValue(_T("Atlas.State.objectSettings.playerID"), playerID);
}
AtlasMessage::sObjectSettings ObjectSettings::GetSettings() const
{
AtlasMessage::sObjectSettings settings;
settings.player = m_PlayerID;
// Copy selections from set into vector
std::vector<std::wstring> selections;
for (std::set<wxString>::const_iterator it = m_ActorSelections.begin();
it != m_ActorSelections.end();
++it)
{
selections.push_back(it->c_str());
}
settings.selections = selections;
bool ok = m_ScriptInterface.Eval(_T("Atlas.State.objectSettings.toSObjectSettings()"), settings);
wxASSERT(ok);
return settings;
}
void ObjectSettings::OnSelectionChange(const std::vector<AtlasMessage::ObjectID>& selection)
{
// TODO: what would be the sensible action if nothing's selected?
// and if multiple objects are selected?
// Convert to ints so they can be passed to JS
std::vector<int> objs (selection.begin(), selection.end());
if (selection.empty())
return;
AtlasMessage::qGetObjectSettings qry (m_View, selection[0]);
qry.Post();
m_PlayerID = qry.settings->player;
m_ActorSelections.clear();
m_VariantGroups.clear();
std::vector<std::vector<std::wstring> > variation = *qry.settings->variantgroups;
for (std::vector<std::vector<std::wstring> >::iterator grp = variation.begin();
grp != variation.end();
++grp)
{
wxArrayString variants;
for (std::vector<std::wstring>::iterator it = grp->begin();
it != grp->end();
++it)
{
variants.Add(it->c_str());
}
m_VariantGroups.push_back(variants);
}
std::vector<std::wstring> selections = *qry.settings->selections;
for (std::vector<std::wstring>::iterator sel = selections.begin();
sel != selections.end();
++sel)
{
m_ActorSelections.insert(sel->c_str());
}
static_cast<Observable<ObjectSettings>*>(this)->NotifyObservers();
m_ScriptInterface.SetValue(_T("Atlas.State.objectSettings.selectedObjects"), objs);
m_ScriptInterface.Eval(_T("Atlas.State.objectSettings.onSelectionChange()"));
}
void ObjectSettings::PostToGame()
void ObjectSettings::NotifyObservers()
{
if (m_SelectedObjects.empty())
return;
POST_COMMAND(SetObjectSettings, (m_View, m_SelectedObjects[0], GetSettings()));
m_ScriptInterface.Eval(_T("Atlas.State.objectSettings.notifyObservers()"));
}

View File

@ -19,62 +19,37 @@
#define INCLUDED_OBJECTSETTINGS
#include <vector>
#include <set>
#include "ScenarioEditor/Tools/Common/MiscState.h"
class ScriptInterface;
namespace AtlasMessage
{
struct sObjectSettings;
}
// Various settings to be applied to newly created units, or to the currently
// selected unit. If a unit is selected or being previewed, it should match
// these settings.
// This class is now just an interface to the JS Atlas.State.objectSettings,
// for old C++ code that hasn't been ported to JS yet.
class ObjectSettings
{
public:
ObjectSettings(Observable<std::vector<AtlasMessage::ObjectID> >& selectedObjects, int view);
ObjectSettings(Observable<std::vector<AtlasMessage::ObjectID> >& selectedObjects, ScriptInterface& scriptInterface);
void Init(int view);
size_t GetPlayerID() const;
void SetPlayerID(int playerID);
struct Group
{
wxArrayString variants;
wxString chosen;
};
const std::vector<Group> GetActorVariation() const;
const std::set<wxString>& GetActorSelections() const;
void SetActorSelections(const std::set<wxString>& selections);
// Constructs new sObjectSettings object from settings
AtlasMessage::sObjectSettings GetSettings() const;
void NotifyObservers();
private:
Observable<std::vector<AtlasMessage::ObjectID> >& m_SelectedObjects;
int m_View;
// 0 = gaia, 1..inf = normal players
size_t m_PlayerID;
// Set of user-chosen actor selections, potentially a superset of any single
// actor's possible variants (since it doesn't get reset if you select
// a new actor, and will accumulate variant names)
std::set<wxString> m_ActorSelections;
// List of actor variant groups (each a list of variant names)
std::vector<wxArrayString> m_VariantGroups;
ScriptInterface& m_ScriptInterface;
// Observe changes to unit selection
ObservableScopedConnection m_Conn;
void OnSelectionChange(const std::vector<AtlasMessage::ObjectID>& selection);
// Transfer current settings to the currently selected unit (if any)
void PostToGame();
};
#endif // INCLUDED_OBJECTSETTINGS