Browse Source

Component enum & resource editing.
API improvements & fixes.

Lasse Öörni 14 years ago
parent
commit
02ffc5801c

+ 367 - 138
Bin/CoreData/Scripts/EditorComponentWindow.as

@@ -9,11 +9,54 @@ const uint ATTR_EDITOR_BOOL = 1;
 const uint ATTR_EDITOR_VECTOR2 = 2;
 const uint ATTR_EDITOR_VECTOR3 = 3;
 const uint ATTR_EDITOR_VECTOR4 = 4;
+const uint ATTR_EDITOR_ENUM = 64;
+const uint ATTR_EDITOR_RESOURCE = 128;
 const uint MAX_ATTRNAME_LENGTH = 15;
 
-bool inReadAttributeEditor = false;
+class EnumEditorData
+{
+    string componentType;
+    string categoryName;
+    string attributeName;
+    array<string>@ choices;
+
+    EnumEditorData(const string& in componentType_, const string& in categoryName_, const string& in attributeName_, array<string>@ choices_)
+    {
+        componentType = componentType_;
+        categoryName = categoryName_;
+        attributeName = attributeName_;
+        @choices = choices_;
+    }
+}
+
+class ResourceEditorData
+{
+    string componentType;
+    string categoryName;
+    string attributeName;
+    string resourceType;
+    string fileExtension;
+    string lastPath;
+
+    ResourceEditorData(const string& in componentType_, const string& in categoryName_, const string& in attributeName_, const string& in resourceType_, const string& in fileExtension_)
+    {
+        componentType = componentType_;
+        categoryName = categoryName_;
+        attributeName = attributeName_;
+        resourceType = resourceType_;
+        fileExtension = fileExtension_;
+    }
+}
+
+array<EnumEditorData@> enumEditors;
+array<ResourceEditorData@> resourceEditors;
+
+bool inLoadAttributeEditor = false;
 uint lastAttributeCount = 0;
 
+uint resourcePickType = 0;
+string resourcePickEditorName;
+
 void createComponentWindow()
 {
     if (componentWindow !is null)
@@ -21,12 +64,25 @@ void createComponentWindow()
 
     @componentWindow = ui.loadLayout(cache.getResource("XMLFile", "UI/ComponentWindow.xml"), uiStyle);
     uiRoot.addChild(componentWindow);
-    int height = (uiRoot.getHeight() - 80) / 2;
-    componentWindow.setPosition(20, 40 + height);
+    int height = min(uiRoot.getHeight() - 60, 500);
     componentWindow.setSize(300, height);
+    componentWindow.setPosition(uiRoot.getWidth() - 20 - componentWindow.getWidth(), 40);
     componentWindow.setVisible(true);
     updateComponentWindow();
     
+    // Fill enum & resource editor data
+    array<string> lightTypes = {"directional", "spot", "point"};
+    array<string> bodyTypes = {"static", "dynamic", "kinematic"};
+    enumEditors.push(EnumEditorData("Light", "light", "type", lightTypes));
+    enumEditors.push(EnumEditorData("RigidBody", "body", "mode", bodyTypes));
+
+    resourceEditors.push(ResourceEditorData("", "material", "name", "Material", ".xml"));
+    resourceEditors.push(ResourceEditorData("", "model", "name", "Model", ".mdl"));
+    resourceEditors.push(ResourceEditorData("", "animation", "name", "Animation", ".ani"));
+    resourceEditors.push(ResourceEditorData("RigidBody", "collision", "name", "CollisionShape", ".xml"));
+    resourceEditors.push(ResourceEditorData("ScriptInstance", "script", "name", "ScriptFile", ".as"));
+    resourceEditors.push(ResourceEditorData("ParticleEmitter", "emitter", "name", "XMLFile", ".xml"));
+
     subscribeToEvent(componentWindow.getChild("CloseButton", true), "Released", "hideComponentWindow");
     subscribeToEvent(componentWindow.getChild("NameEdit", true), "TextFinished", "editComponentName");
 }
@@ -44,6 +100,9 @@ void showComponentWindow()
 
 void updateComponentWindow()
 {
+    // If a resource pick was in progress, it cannot be completed now, as component was changed
+    pickResourceCanceled();
+
     Text@ title = componentWindow.getChild("WindowTitle", true);
     LineEdit@ nameEdit = componentWindow.getChild("NameEdit", true);
     ListView@ list = componentWindow.getChild("AttributeList", true);
@@ -82,6 +141,7 @@ void updateComponentAttributes()
     // Save component to XML, then inspect the result
     XMLElement rootElem = componentData.createRootElement("component");
     selectedComponent.saveXML(rootElem);
+    Node@ node = cast<Node>(selectedComponent);
 
     // Check amount of attributes. If has changed, do full refresh. Otherwise just refresh values
     XMLElement categoryElem = rootElem.getChildElement();
@@ -97,6 +157,9 @@ void updateComponentAttributes()
 
     if (attributeCount != lastAttributeCount)
     {
+        // If a resource pick was in progress, it cannot be completed now, as component structure was changed
+        pickResourceCanceled();
+
         IntVector2 listOldPos = list.getViewPosition();
 
         list.removeAllItems();
@@ -120,22 +183,18 @@ void updateComponentAttributes()
             array<string> attrs = categoryElem.getAttributeNames();
 
             // Do not make the parent node reference editable. It is handled via scene window drag and drop instead
-            if (category == "parent")
+            if ((category == "parent") && (node !is null))
             {
-                Node@ node = cast<Node>(selectedComponent);
-                if (node !is null)
-                {
-                    Node@ parentNode = node.getParent();
-
-                    Text@ attrName = Text();
-                    attrName.setStyle(uiStyle, "EditorAttributeText");
-                    if (parentNode !is null)
-                        attrName.setText(" " + parentNode.getTypeName() + " in " + getEntityTitle(parentNode.getEntity()));
-                    else
-                        attrName.setText(" No parent");
-                       
-                    list.addItem(attrName);
-                }
+                Node@ parentNode = node.getParent();
+
+                Text@ attrName = Text();
+                attrName.setStyle(uiStyle, "EditorAttributeText");
+                if (parentNode !is null)
+                    attrName.setText(" " + parentNode.getTypeName() + " in " + getEntityTitle(parentNode.getEntity()));
+                else
+                    attrName.setText(" No parent");
+
+                list.addItem(attrName);
             }
             else
             {
@@ -150,15 +209,24 @@ void updateComponentAttributes()
                     bar.setFixedHeight(18);
                     list.addItem(bar);
 
-                    Text@ attrName = Text();
-                    attrName.setStyle(uiStyle, "EditorAttributeText");
-                    attrName.setText(" " + name);
-                    attrName.setFixedWidth(120);
-                    bar.addChild(attrName);
-    
                     uint type = getAttributeEditorType(selectedComponent, category, attrs[i], categoryElem.getAttribute(attrs[i]));
+
+                    UIElement@ spacer = UIElement();
+                    spacer.setFixedWidth(8);
+                    bar.addChild(spacer);
+
+                    // Do not create the name for resource editors
+                    if (type < ATTR_EDITOR_RESOURCE)
+                    {
+                        Text@ attrName = Text();
+                        attrName.setStyle(uiStyle, "EditorAttributeText");
+                        attrName.setText(name);
+                        attrName.setFixedWidth(110);
+                        bar.addChild(attrName);
+                    }
+
                     createAttributeEditor(bar, type, categoryElem, index, attrs[i]);
-                    readAttributeEditor(type, categoryElem, index, attrs[i]);
+                    loadAttributeEditor(type, categoryElem, index, attrs[i]);
                 }
             }
 
@@ -188,9 +256,9 @@ void updateComponentAttributes()
             {
                 string category = categoryElem.getName();
                 uint type = getAttributeEditorType(selectedComponent, category, attrs[i], categoryElem.getAttribute(attrs[i]));
-                readAttributeEditor(type, categoryElem, index, attrs[i]);
+                loadAttributeEditor(type, categoryElem, index, attrs[i]);
             }
-        
+
             categoryElem = categoryElem.getNextElement();
             ++index;
         }
@@ -199,14 +267,19 @@ void updateComponentAttributes()
 
 void editComponentAttribute(StringHash eventType, VariantMap& eventData)
 {
-    if (selectedComponent is null)
-        return;
-        
     // Changing elements programmatically may cause events to be sent. Stop possible infinite loop in that case.
-    if (inReadAttributeEditor)
+    if ((selectedComponent is null) || (inLoadAttributeEditor))
         return;
 
     UIElement@ attrEdit = eventData["Element"].getUIElement();
+    editComponentAttribute(attrEdit);
+}
+
+void editComponentAttribute(UIElement@ attrEdit)
+{
+    if ((selectedComponent is null) || (inLoadAttributeEditor))
+        return;
+
     XMLElement rootElem = componentData.getRootElement();
     XMLElement categoryElem = rootElem.getChildElement();
     uint index = 0;
@@ -214,7 +287,7 @@ void editComponentAttribute(StringHash eventType, VariantMap& eventData)
     {
         if (index == uint(attrEdit.userData["Index"].getInt()))
         {
-            writeAttributeEditor(attrEdit.userData["Type"].getInt(), categoryElem, index, attrEdit.userData["Attribute"].getString());
+            storeAttributeEditor(attrEdit.userData["Type"].getInt(), categoryElem, index, attrEdit.userData["Attribute"].getString());
 
             uint id = selectedComponent.getEntity().getID();
             beginModify(id);
@@ -230,29 +303,115 @@ void editComponentAttribute(StringHash eventType, VariantMap& eventData)
     }
 }
 
+void pickResource(StringHash eventType, VariantMap& eventData)
+{
+    if (uiFileSelector !is null)
+        return;
+
+    UIElement@ button = eventData["Element"].getUIElement();
+    LineEdit@ attrEdit = button.getParent().getChild(1);
+    uint type = uint(attrEdit.userData["Type"].getInt());
+
+    ResourceEditorData@ data = resourceEditors[type - ATTR_EDITOR_RESOURCE];
+
+    array<string> filters;
+    filters.push("*" + data.fileExtension);
+    string lastPath = data.lastPath;
+    if (lastPath.empty())
+        lastPath = sceneResourcePath;
+    createFileSelector("Pick " + data.resourceType, "OK", "Cancel", lastPath, filters, 0);
+    subscribeToEvent(uiFileSelector, "FileSelected", "pickResourceDone");
+
+    resourcePickType = type;
+    resourcePickEditorName = attrEdit.getName();
+}
+
+void openResource(StringHash eventType, VariantMap& eventData)
+{
+    UIElement@ button = eventData["Element"].getUIElement();
+    LineEdit@ attrEdit = button.getParent().getChild(1);
+    systemOpenFile(sceneResourcePath + attrEdit.getText(), "edit");
+}
+
+void pickResourceDone(StringHash eventType, VariantMap& eventData)
+{
+    closeFileSelector();
+
+    if (!eventData["OK"].getBool())
+    {
+        resourcePickType = 0;
+        return;
+    }
+
+    // Check if another component was selected in the meanwhile
+    if (resourcePickType == 0)
+        return;
+
+    ResourceEditorData@ data = resourceEditors[resourcePickType - ATTR_EDITOR_RESOURCE];
+    resourcePickType = 0;
+
+    ListView@ list = componentWindow.getChild("AttributeList", true);
+    LineEdit@ attrEdit = list.getChild(resourcePickEditorName, true);
+    if (attrEdit is null)
+        return;
+
+    // Validate the resource. It must come from within the scene resource directory, and be loaded successfully
+    string resourceName = eventData["FileName"].getString();
+    if (resourceName.find(sceneResourcePath) != 0)
+        return;
+    data.lastPath = getPath(resourceName);
+
+    resourceName = resourceName.substr(sceneResourcePath.length());
+
+    Resource@ res = cache.getResource(data.resourceType, resourceName);
+    if (res is null)
+        return;
+
+    attrEdit.setText(resourceName);
+    editComponentAttribute(attrEdit);
+}
+
+void pickResourceCanceled()
+{
+    if (resourcePickType != 0)
+    {
+        resourcePickType = 0;
+        closeFileSelector();
+    }
+}
+
 int getAttributeEditorType(Component@ component, const string& in category, const string& in attribute, const string& in value)
 {
-    // Note: we always use valid, ie. just serialized data for this
-    if (cast<Node>(component) !is null)
+    for (uint i = 0; i < enumEditors.size(); ++i)
+    {
+        if ((category == enumEditors[i].categoryName) && (attribute == enumEditors[i].attributeName) && ((enumEditors[i].componentType.empty())
+            || (component.getTypeName() == enumEditors[i].componentType)))
+            return ATTR_EDITOR_ENUM + i;
+    }
+
+    for (uint i = 0; i < resourceEditors.size(); ++i)
     {
-        if (attribute.find("name") >= 0)
-            return ATTR_EDITOR_STRING;
-        else if ((category == "animation") && (attribute == "startbone"))
-            return ATTR_EDITOR_STRING;
-        else if (category == "parent")
-            return ATTR_EDITOR_STRING; //! \todo Parent node selector needs to be done separately
-        else if ((value == "true") || (value == "false"))
-            return ATTR_EDITOR_BOOL;
-
-        uint coords = value.split(' ').size();
-        if (coords == 2)
-            return ATTR_EDITOR_VECTOR2;
-        else if (coords == 3)
-            return ATTR_EDITOR_VECTOR3;
-        else if (coords == 4)
-            return ATTR_EDITOR_VECTOR4;
+        if ((category == resourceEditors[i].categoryName) && (attribute == resourceEditors[i].attributeName) && 
+            ((resourceEditors[i].componentType.empty()) || (component.getTypeName() == resourceEditors[i].componentType)))
+            return ATTR_EDITOR_RESOURCE + i;
     }
 
+    // Note: we always use valid, ie. just serialized data for this, not own edited values
+    if ((category == "animation") && (attribute == "startbone"))
+        return ATTR_EDITOR_STRING;
+    else if ((category == "script") && (attribute == "class"))
+        return ATTR_EDITOR_STRING;
+    else if ((value == "true") || (value == "false"))
+        return ATTR_EDITOR_BOOL;
+
+    uint coords = value.split(' ').size();
+    if (coords == 2)
+        return ATTR_EDITOR_VECTOR2;
+    else if (coords == 3)
+        return ATTR_EDITOR_VECTOR3;
+    else if (coords == 4)
+        return ATTR_EDITOR_VECTOR4;
+
     return ATTR_EDITOR_STRING;
 }
 
@@ -263,37 +422,32 @@ string getAttributeEditorName(uint index, const string& in attribute)
 
 void createAttributeEditor(UIElement@ bar, uint type, XMLElement categoryElem, uint index, const string& in attribute)
 {
-    switch (type)
+    if (type == ATTR_EDITOR_STRING)
     {
-    case ATTR_EDITOR_STRING:
-        {
-            LineEdit@ attrEdit = LineEdit(getAttributeEditorName(index, attribute));
-            attrEdit.setStyle(uiStyle, "EditorAttributeEdit");
-            attrEdit.userData["Type"] = type;
-            attrEdit.userData["Index"] = index;
-            attrEdit.userData["Attribute"] = attribute;
-            attrEdit.setFixedHeight(16);
-            bar.addChild(attrEdit);
-            subscribeToEvent(attrEdit, "TextFinished", "editComponentAttribute");
-        }
-        break;
+        LineEdit@ attrEdit = LineEdit(getAttributeEditorName(index, attribute));
+        attrEdit.setStyle(uiStyle, "EditorAttributeEdit");
+        attrEdit.userData["Type"] = type;
+        attrEdit.userData["Index"] = index;
+        attrEdit.userData["Attribute"] = attribute;
+        attrEdit.setFixedHeight(16);
+        bar.addChild(attrEdit);
+        subscribeToEvent(attrEdit, "TextFinished", "editComponentAttribute");
+    }
 
-    case ATTR_EDITOR_BOOL:
-        {
-            CheckBox@ attrEdit = CheckBox(getAttributeEditorName(index, attribute));
-            attrEdit.setStyleAuto(uiStyle);
-            attrEdit.userData["Type"] = type;
-            attrEdit.userData["Index"] = index;
-            attrEdit.userData["Attribute"] = attribute;
-            attrEdit.setFixedSize(16, 16);
-            bar.addChild(attrEdit);
-            subscribeToEvent(attrEdit, "Toggled", "editComponentAttribute");
-        }
-        break;
+    if (type == ATTR_EDITOR_BOOL)
+    {
+        CheckBox@ attrEdit = CheckBox(getAttributeEditorName(index, attribute));
+        attrEdit.setStyleAuto(uiStyle);
+        attrEdit.userData["Type"] = type;
+        attrEdit.userData["Index"] = index;
+        attrEdit.userData["Attribute"] = attribute;
+        attrEdit.setFixedSize(16, 16);
+        bar.addChild(attrEdit);
+        subscribeToEvent(attrEdit, "Toggled", "editComponentAttribute");
+    }
 
-    case ATTR_EDITOR_VECTOR2:
-    case ATTR_EDITOR_VECTOR3:
-    case ATTR_EDITOR_VECTOR4:
+    if ((type >= ATTR_EDITOR_VECTOR2) && (type <= ATTR_EDITOR_VECTOR4))
+    {
         for (uint i = 0; i < type; ++i)
         {
             LineEdit@ attrEdit = LineEdit(getAttributeEditorName(index, attribute) + "_" + toString(i));
@@ -305,90 +459,165 @@ void createAttributeEditor(UIElement@ bar, uint type, XMLElement categoryElem, u
             bar.addChild(attrEdit);
             subscribeToEvent(attrEdit, "TextFinished", "editComponentAttribute");
         }
-        break;
+    }
+    
+    if ((type >= ATTR_EDITOR_ENUM) && (type < ATTR_EDITOR_RESOURCE))
+    {
+        DropDownList@ attrEdit = DropDownList(getAttributeEditorName(index, attribute));
+        attrEdit.setStyleAuto(uiStyle);
+        attrEdit.userData["Type"] = type;
+        attrEdit.userData["Index"] = index;
+        attrEdit.userData["Attribute"] = attribute;
+        attrEdit.setFixedHeight(16);
+        attrEdit.setResizePopup(true);
+        
+        EnumEditorData@ data = enumEditors[type - ATTR_EDITOR_ENUM];
+        for (uint i = 0; i < data.choices.size(); ++i)
+        {
+            Text@ choice = Text();
+            choice.setStyle(uiStyle, "EditorEnumAttributeText");
+            choice.setText(data.choices[i]);
+            attrEdit.addItem(choice);
+        }
+        bar.addChild(attrEdit);
+        
+        subscribeToEvent(attrEdit, "ItemSelected", "editComponentAttribute");
+    }
+    
+    if (type >= ATTR_EDITOR_RESOURCE)
+    {
+        LineEdit@ attrEdit = LineEdit(getAttributeEditorName(index, attribute));
+        attrEdit.setStyle(uiStyle, "EditorAttributeEdit");
+        attrEdit.userData["Type"] = type;
+        attrEdit.userData["Index"] = index;
+        attrEdit.userData["Attribute"] = attribute;
+        attrEdit.setFixedHeight(16);
+        bar.addChild(attrEdit);
+        subscribeToEvent(attrEdit, "TextFinished", "editComponentAttribute");
+
+        Button@ pickButton = Button(getAttributeEditorName(index, attribute) + "_Pick");
+        pickButton.setStyleAuto(uiStyle);
+        pickButton.userData["Type"] = type;
+        pickButton.userData["Index"] = index;
+        pickButton.userData["Attribute"] = attribute;
+        pickButton.setFixedSize(36, 16);
+        Text@ pickButtonText = Text();
+        pickButtonText.setStyle(uiStyle, "EditorAttributeText");
+        pickButtonText.setAlignment(HA_CENTER, VA_CENTER);
+        pickButtonText.setText("Pick");
+        pickButton.addChild(pickButtonText);
+        bar.addChild(pickButton);
+        subscribeToEvent(pickButton, "Released", "pickResource");
+
+        Button@ openButton = Button(getAttributeEditorName(index, attribute) + "_Edit");
+        openButton.setStyleAuto(uiStyle);
+        openButton.userData["Type"] = type;
+        openButton.userData["Index"] = index;
+        openButton.userData["Attribute"] = attribute;
+        openButton.setFixedSize(36, 16);
+        Text@ openButtonText = Text();
+        openButtonText.setStyle(uiStyle, "EditorAttributeText");
+        openButtonText.setAlignment(HA_CENTER, VA_CENTER);
+        openButtonText.setText("Open");
+        openButton.addChild(openButtonText);
+        bar.addChild(openButton);
+        subscribeToEvent(openButton, "Released", "openResource");
     }
 }
 
-void readAttributeEditor(uint type, XMLElement categoryElem, int index, const string& in attribute)
+void loadAttributeEditor(uint type, XMLElement categoryElem, int index, const string& in attribute)
 {
-    inReadAttributeEditor = true;
+    inLoadAttributeEditor = true;
 
     ListView@ list = componentWindow.getChild("AttributeList", true);
-    
-    switch (type)
+    string value = categoryElem.getAttribute(attribute);
+
+    if ((type == ATTR_EDITOR_STRING) || (type >= ATTR_EDITOR_RESOURCE))
     {
-    case ATTR_EDITOR_STRING:
-        {
-            LineEdit@ attrEdit = list.getChild(getAttributeEditorName(index, attribute), true);
-            attrEdit.setText(categoryElem.getAttribute(attribute));
-        }
-        break;
+        LineEdit@ attrEdit = list.getChild(getAttributeEditorName(index, attribute), true);
+        if (attrEdit is null)
+            return;
+        attrEdit.setText(value);
+    }
 
-    case ATTR_EDITOR_BOOL:
+    if (type == ATTR_EDITOR_BOOL)
+    {
+        CheckBox@ attrEdit = list.getChild(getAttributeEditorName(index, attribute), true);
+        if (attrEdit is null)
+            return;            
+        attrEdit.setChecked(value.toBool());
+    }
+
+    if ((type >= ATTR_EDITOR_VECTOR2) && (type <= ATTR_EDITOR_VECTOR4))
+    {
+        string baseName = getAttributeEditorName(index, attribute);
+        array<string> coords = value.split(' ');
+        for (uint i = 0; (i < coords.size()) && (i < type); ++i)
         {
-            CheckBox@ attrEdit = list.getChild(getAttributeEditorName(index, attribute), true);
-            attrEdit.setChecked(categoryElem.getAttribute(attribute).toBool());
+            LineEdit@ attrEdit = list.getChild(baseName + "_" + toString(i), true);
+            if (attrEdit !is null)
+                attrEdit.setText(coords[i]);
+            else
+                break;
         }
-        break;
+    }
 
-    case ATTR_EDITOR_VECTOR2:
-    case ATTR_EDITOR_VECTOR3:
-    case ATTR_EDITOR_VECTOR4:
+    if ((type >= ATTR_EDITOR_ENUM) && (type < ATTR_EDITOR_RESOURCE))
+    {
+        DropDownList@ attrEdit = list.getChild(getAttributeEditorName(index, attribute), true);
+        if (attrEdit is null)
+            return;
+        EnumEditorData@ data = enumEditors[type - ATTR_EDITOR_ENUM];
+        string value = categoryElem.getAttribute(attribute);
+        for (uint i = 0; i < data.choices.size(); ++i)
         {
-            string baseName = getAttributeEditorName(index, attribute);
-            array<string> coords = categoryElem.getAttribute(attribute).split(' ');
-            for (uint i = 0; (i < coords.size()) && (i < type); ++i)
+            if (value.toLower() == data.choices[i])
             {
-                LineEdit@ attrEdit = list.getChild(baseName + "_" + toString(i), true);
-                if (attrEdit !is null)
-                    attrEdit.setText(coords[i]);
-                else
-                    break;
+                attrEdit.setSelection(i);
+                break;
             }
         }
-        break;
     }
-    
-    inReadAttributeEditor = false;
+
+    inLoadAttributeEditor = false;
 }
 
-void writeAttributeEditor(uint type, XMLElement categoryElem, int index, const string& in attribute)
+void storeAttributeEditor(uint type, XMLElement categoryElem, int index, const string& in attribute)
 {
     ListView@ list = componentWindow.getChild("AttributeList", true);
-    
-    switch (type)
+
+    if ((type == ATTR_EDITOR_STRING) || (type >= ATTR_EDITOR_RESOURCE))
     {
-    case ATTR_EDITOR_STRING:
-        {
-            LineEdit@ attrEdit = list.getChild(getAttributeEditorName(index, attribute), true);
-            categoryElem.setAttribute(attribute, attrEdit.getText());
-        }
-        break;
+        LineEdit@ attrEdit = list.getChild(getAttributeEditorName(index, attribute), true);
+        categoryElem.setAttribute(attribute, attrEdit.getText());
+    }
 
-    case ATTR_EDITOR_BOOL:
-        {
-            CheckBox@ attrEdit = list.getChild(getAttributeEditorName(index, attribute), true);
-            categoryElem.setAttribute(attribute, toString(attrEdit.isChecked()));
-        }
-        break;
+    if (type == ATTR_EDITOR_BOOL)
+    {
+        CheckBox@ attrEdit = list.getChild(getAttributeEditorName(index, attribute), true);
+        categoryElem.setAttribute(attribute, toString(attrEdit.isChecked()));
+    }
 
-    case ATTR_EDITOR_VECTOR2:
-    case ATTR_EDITOR_VECTOR3:
-    case ATTR_EDITOR_VECTOR4:
+    if ((type >= ATTR_EDITOR_VECTOR2) && (type <= ATTR_EDITOR_VECTOR4))
+    {
+        string baseName = getAttributeEditorName(index, attribute);
+        string value;
+        for (uint i = 0; i < type; ++i)
         {
-            string baseName = getAttributeEditorName(index, attribute);
-            string value;
-            for (uint i = 0; i < type; ++i)
-            {
-                LineEdit@ attrEdit = list.getChild(baseName + "_" + toString(i), true);
-                if (attrEdit is null)
-                    break;
-                if (i != 0)
-                    value += " ";
-                value += attrEdit.getText();
-            }
-            categoryElem.setAttribute(attribute, value);
+            LineEdit@ attrEdit = list.getChild(baseName + "_" + toString(i), true);
+            if (attrEdit is null)
+                break;
+            if (i != 0)
+                value += " ";
+            value += attrEdit.getText();
         }
-        break;
+        categoryElem.setAttribute(attribute, value);
+    }
+    
+    if ((type >= ATTR_EDITOR_ENUM) && (type < ATTR_EDITOR_RESOURCE))
+    {
+        DropDownList@ attrEdit = list.getChild(getAttributeEditorName(index, attribute), true);
+        EnumEditorData@ data = enumEditors[type - ATTR_EDITOR_ENUM];
+        categoryElem.setAttribute(attribute, data.choices[attrEdit.getSelection()]);
     }
 }

+ 3 - 2
Bin/CoreData/Scripts/EditorScene.as

@@ -20,6 +20,7 @@ void createScene()
     editorScene.setPaused(true);
     
     subscribeToEvent("PostRenderUpdate", "sceneRaycast");
+    subscribeToEvent("UIMouseClick", "sceneRaycast");
 }
 
 void setResourcePath(string newPath)
@@ -165,7 +166,7 @@ string getComponentTitle(Component@ component, int indent)
         return indentStr + component.getTypeName() + " (" + name + ")";
 }
 
-void sceneRaycast()
+void sceneRaycast(StringHash eventType, VariantMap& eventData)
 {
     DebugRenderer@ debug = editorScene.getDebugRenderer();
     IntVector2 pos = ui.getCursorPosition();
@@ -195,7 +196,7 @@ void sceneRaycast()
             @node = result[0].node;
             node.drawDebugGeometry(debug, false);
         }
-        if (input.getMouseButtonPress(MOUSEB_LEFT))
+        if ((eventType == StringHash("UIMouseClick")) && (eventData["Buttons"].getInt() == MOUSEB_LEFT))
             selectComponent(node);
     }
 }

+ 2 - 5
Bin/CoreData/Scripts/EditorSceneWindow.as

@@ -14,9 +14,9 @@ void createSceneWindow()
 
     @sceneWindow = ui.loadLayout(cache.getResource("XMLFile", "UI/SceneWindow.xml"), uiStyle);
     uiRoot.addChild(sceneWindow);
-    sceneWindow.setPosition(20, 40);
-    int height = (uiRoot.getHeight() - 80) / 2;
+    int height = min(uiRoot.getHeight() - 60, 500);
     sceneWindow.setSize(300, height);
+    sceneWindow.setPosition(20, 40);
     updateSceneWindow(false);
 
     subscribeToEvent(sceneWindow.getChild("CloseButton", true), "Released", "hideSceneWindow");
@@ -287,9 +287,6 @@ void selectComponent(Component@ component)
         list.setChildItemsVisible(entityItem, true);
         // This causes an event to be sent, in response we set selectedComponent & selectedEntity, and refresh editors
         list.setSelection(componentItem);
-        // Focus the list if visible
-        if (sceneWindow.isVisible())
-            ui.setFocusElement(list);
     }
     else
         list.clearSelection();

+ 19 - 22
Bin/CoreData/Scripts/EditorUI.as

@@ -83,7 +83,7 @@ Menu@ createMenuItem(const string& in title, int accelKey, int accelQual)
     if (accelKey != 0)
         menu.setAccelerator(accelKey, accelQual);
 
-    Text@ menuText = Text(title + "_Text");
+    Text@ menuText = Text();
     menuText.setStyle(uiStyle, "EditorMenuText");
     menuText.setText(title);
     menu.addChild(menuText);
@@ -105,7 +105,7 @@ Menu@ createMenuSpacer()
 
 Window@ createPopup(Menu@ baseMenu)
 {
-    Window@ popup = Window(baseMenu.getName() + "_Popup");
+    Window@ popup = Window();
     popup.setStyleAuto(uiStyle);
     popup.setLayout(LM_VERTICAL, uiSpacing, uiSpacingRect);
     baseMenu.setPopup(popup);
@@ -117,6 +117,7 @@ Window@ createPopup(Menu@ baseMenu)
 Menu@ createMenu(const string& in title)
 {
     Menu@ menu = createMenuItem(title, 0, 0);
+    menu.setName("");
     menu.setFixedWidth(menu.getWidth());
     createPopup(menu);
 
@@ -172,6 +173,14 @@ void handleMenuSelected(StringHash eventType, VariantMap& eventData)
         return;
 
     string action = menu.getName();
+    if (action.empty())
+        return;
+        
+    // Close the menu now
+    UIElement@ menuParent = menu.getParent();
+    Menu@ topLevelMenu = menuParent.userData["Origin"].getUIElement();
+    if (topLevelMenu !is null)
+        topLevelMenu.showPopup(false);
 
     if (uiFileSelector is null)
     {
@@ -182,10 +191,7 @@ void handleMenuSelected(StringHash eventType, VariantMap& eventData)
         }
 
         if (action == "Save scene")
-        {
-            ui.setFocusElement(null); // Close the menu
             saveScene(sceneFileName);
-        }
 
         if (action == "Save scene as")
         {
@@ -193,36 +199,27 @@ void handleMenuSelected(StringHash eventType, VariantMap& eventData)
             uiFileSelector.setFileName(getFileNameAndExtension(sceneFileName));
             subscribeToEvent(uiFileSelector, "FileSelected", "handleSaveSceneFile");
         }
-        
+
         if (action == "Set resource path")
         {
             createFileSelector("Set resource path", "Set", "Cancel", sceneResourcePath, uiAllFilter, 0);
             uiFileSelector.setDirectoryMode(true);
             subscribeToEvent(uiFileSelector, "FileSelected", "handleResourcePath");
         }
-
-        if (action == "Reload resources")
-        {
-            ui.setFocusElement(null); // Close the menu
-            reloadResources();
-        }
     }
-    
+
+    if (action == "Reload resources")
+        reloadResources();
+
     if (action == "Scene hierarchy")
-    {
-        ui.setFocusElement(null); // Close the menu
         showSceneWindow();
-    }
+
     if (action == "Component edit")
-    {
-        ui.setFocusElement(null); // Close the menu
         showComponentWindow();
-    }
+
     if (action == "Camera settings")
-    {
-        ui.setFocusElement(null); // Close the menu
         showCameraDialog();
-    }
+        
     if (menu.getName() == "Exit")
         engine.exit();
 }

+ 6 - 4
Bin/CoreData/UI/DefaultStyle.xml

@@ -41,10 +41,12 @@
         <pressedoffset value="16 0" />
         <hoveroffset value="0 16" />
         <labeloffset value="-1 1" />
+        <layout mode="horizontal" border="4 2 4 2" />
         <popup>
             <texture name="Textures/UI.png" />
             <imagerect value="48 0 64 16" />
             <border value="3 3 3 3" />
+            <layout border="4 2 4 2" />
         </popup>
         <listview>
             <horizontalscrollbar>
@@ -349,11 +351,7 @@
     </element>
     <element type="FileSelectorFilterList">
         <fixedwidth value="64" />
-        <layout mode="horizontal" border="4 2 4 2" />
         <resizepopup enable="true" />
-        <popup>
-            <layout border="4 4 4 4" />
-        </popup>
     </element>
     <element type="FileSelectorFilterText">
         <font name="Cour.ttf" size="10" />
@@ -381,6 +379,10 @@
     <element type="EditorAttributeText">
         <font name="cour.ttf" size="9" />
     </element>
+    <element type="EditorEnumAttributeText">
+        <font name="Cour.ttf" size="9" />
+        <hovercolor value="0.45 0.70 0.45" />
+    </element>
     <element type="EditorAttributeEdit">
         <texture name="Textures/UI.png" />
         <imagerect value="112 0 128 16" />

+ 1 - 1
Bin/Data/TestLevel.xml

@@ -22,7 +22,7 @@
             <shadowcascade lambda="0.5" maxrange="5000" faderange="0.2" splits="2" />
             <shadowfocus enable="true" nonuniform="true" zoomout="true" quantize="50" minview="900" />
             <ramptexture name="" />
-            <spottexture name="" />
+            <shapetexture name="" />
         </component>
         <component type="Skybox" netflags="0">
             <transform pos="0 3000 0" rot="1 0 0 0" scale="30000 1 30000" />

+ 2 - 1
Engine/Engine/RegisterRenderer.cpp

@@ -815,7 +815,8 @@ static void registerRenderer(asIScriptEngine* engine)
     engine->RegisterObjectBehaviour("Renderer", asBEHAVE_ADDREF, "void f()", asMETHOD(Renderer, addRef), asCALL_THISCALL);
     engine->RegisterObjectBehaviour("Renderer", asBEHAVE_RELEASE, "void f()", asMETHOD(Renderer, releaseRef), asCALL_THISCALL);
     engine->RegisterObjectMethod("Renderer", "void setWindowTitle(const string& in)", asMETHOD(Renderer, setWindowTitle), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Renderer", "void setMode(RenderMode, int, int, bool, bool, int)", asMETHOD(Renderer, setMode), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Renderer", "void setMode(RenderMode, int, int, bool, bool, int)", asMETHODPR(Renderer, setMode, (RenderMode, int, int, bool, bool, int), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Renderer", "void setMode(int, int)", asMETHODPR(Renderer, setMode, (int, int), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("Renderer", "void toggleFullscreen()", asMETHOD(Renderer, toggleFullscreen), asCALL_THISCALL);
     engine->RegisterObjectMethod("Renderer", "void close()", asMETHOD(Renderer, close), asCALL_THISCALL);
     engine->RegisterObjectMethod("Renderer", "bool takeScreenShot(Image@+)", asMETHOD(Renderer, takeScreenShot), asCALL_THISCALL);

+ 6 - 2
Engine/Renderer/BillboardSet.cpp

@@ -139,8 +139,10 @@ void BillboardSet::saveXML(XMLElement& dest)
     GeometryNode::saveXML(dest);
     
     // Write BillboardSet properties
+    XMLElement materialElem = dest.createChildElement("material");
+    materialElem.setString("name", getResourceName(mMaterial));
+    
     XMLElement billboardsElem = dest.createChildElement("billboards");
-    billboardsElem.setString("materialname", getResourceName(mMaterial));
     billboardsElem.setBool("relative", mBillboardsRelative);
     billboardsElem.setBool("sort", mBillboardsSorted);
     billboardsElem.setBool("scale", mScaleBillboards);
@@ -173,8 +175,10 @@ void BillboardSet::loadXML(const XMLElement& source, ResourceCache* cache)
     GeometryNode::loadXML(source, cache);
     
     // Read BillboardSet properties
+    XMLElement materialElem = source.getChildElement("material");
+    mMaterial = cache->getResource<Material>(materialElem.getString("name"));
+    
     XMLElement billboardsElem = source.getChildElement("billboards");
-    mMaterial = cache->getResource<Material>(billboardsElem.getString("materialname"));
     mBillboardsRelative = billboardsElem.getBool("relative");
     mBillboardsSorted = billboardsElem.getBool("sort");
     mScaleBillboards = billboardsElem.getBool("scale");

+ 12 - 2
Engine/Renderer/Light.cpp

@@ -189,6 +189,9 @@ void Light::load(Deserializer& source, ResourceCache* cache)
             mShapeTexture = cache->getResource<TextureCube>(shapeTexture);
     }
     
+    // Validate textures by setting light type again
+    setLightType(mLightType);
+    
     mShadowBias.validate();
     mShadowCascade.validate();
     mShadowFocus.validate();
@@ -239,7 +242,7 @@ void Light::saveXML(XMLElement& dest)
     XMLElement rampElem = dest.createChildElement("ramptexture");
     rampElem.setString("name", getResourceName(mRampTexture));
     
-    XMLElement spotElem = dest.createChildElement("spottexture");
+    XMLElement spotElem = dest.createChildElement("shapetexture");
     spotElem.setString("name", getResourceName(mShapeTexture));
 }
 
@@ -292,13 +295,16 @@ void Light::loadXML(const XMLElement& source, ResourceCache* cache)
     else
         mRampTexture = cache->getResource<Texture2D>(name);
     
-    XMLElement spotElem = source.getChildElement("spottexture");
+    XMLElement spotElem = source.getChildElement("shapetexture");
     name = spotElem.getString("name");
     if (getExtension(name) == ".xml")
         mShapeTexture = cache->getResource<TextureCube>(name);
     else
         mShapeTexture = cache->getResource<Texture2D>(name);
     
+    // Validate textures by setting light type again
+    setLightType(mLightType);
+    
     mShadowBias.validate();
     mShadowCascade.validate();
     mShadowFocus.validate();
@@ -448,6 +454,10 @@ void Light::readNetUpdate(Deserializer& source, ResourceCache* cache, const NetU
     }
     readFloatDelta(mShadowNearFarRatio, source, bits2 & 64);
     
+    // Validate textures by setting light type again
+    if (bits & 1)
+        setLightType(mLightType);
+    
     if (bits)
         markDirty();
 }

+ 5 - 0
Engine/Renderer/Renderer.cpp

@@ -351,6 +351,11 @@ void Renderer::setMode(RenderMode mode, int width, int height, bool fullscreen,
         " multisample " + toString(multiSample));
 }
 
+void Renderer::setMode(int width, int height)
+{
+    setMode(mMode, width, height, mFullscreen, mVsync, mMultiSample);
+}
+
 void Renderer::toggleFullscreen()
 {
     setMode(mMode, mWidth, mHeight, !mFullscreen, mVsync, mMultiSample);

+ 2 - 0
Engine/Renderer/Renderer.h

@@ -69,6 +69,8 @@ public:
     void setWindowTitle(const std::string& windowTitle);
     //! Set screen mode. In deferred rendering modes multisampling means edge filtering instead of MSAA
     void setMode(RenderMode mode, int width, int height, bool fullscreen, bool vsync, int multiSample);
+    //! Set screen resolution only
+    void setMode(int width, int height);
     //! Toggle between full screen and windowed mode
     void toggleFullscreen();
     //! Close the window

+ 26 - 0
Engine/Script/RegisterStdString.cpp

@@ -72,6 +72,30 @@ static int StringFind(const std::string& rhs, const std::string& str)
     return str.find(rhs);
 }
 
+std::string StringSubString1Param(unsigned start, const std::string& str)
+{
+    try
+    {
+        return str.substr(start);
+    }
+    catch (...)
+    {
+        return std::string();
+    }
+}
+
+std::string StringSubString2Params(unsigned start, unsigned length, const std::string& str)
+{
+    try
+    {
+        return str.substr(start, length);
+    }
+    catch (...)
+    {
+        return std::string();
+    }
+}
+
 static void ConstructStringInt(int value, std::string* ptr)
 {
     new(ptr) std::string();
@@ -203,6 +227,8 @@ void registerStdString(asIScriptEngine *engine)
     engine->RegisterObjectMethod("string", "uint8 &opIndex(uint)", asFUNCTION(StringCharAt), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("string", "const uint8 &opIndex(uint) const", asFUNCTION(StringCharAt), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("string", "int find(const string& in) const", asFUNCTION(StringFind), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("string", "string substr(uint) const", asFUNCTION(StringSubString1Param), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("string", "string substr(uint, uint) const", asFUNCTION(StringSubString2Params), asCALL_CDECL_OBJLAST);
     
     // Register automatic conversion functions for convenience
     engine->RegisterObjectBehaviour("string", asBEHAVE_CONSTRUCT, "void f(int)", asFUNCTION(ConstructStringInt), asCALL_CDECL_OBJLAST);

+ 1 - 1
Engine/Script/ScriptInstance.cpp

@@ -163,9 +163,9 @@ void ScriptInstance::saveXML(XMLElement& dest)
     Component::saveXML(dest);
     
     XMLElement scriptElem = dest.createChildElement("script");
-    scriptElem.setBool("enabled", mEnabled);
     scriptElem.setString("name", getResourceName(mScriptFile));
     scriptElem.setString("class", mClassName);
+    scriptElem.setBool("enabled", mEnabled);
     scriptElem.setInt("fps", mFixedUpdateFps);
     scriptElem.setFloat("timeacc", mFixedUpdateTimer);
     

+ 1 - 1
Engine/UI/DropDownList.cpp

@@ -215,5 +215,5 @@ void DropDownList::handleItemSelected(StringHash eventType, VariantMap& eventDat
     VariantMap newEventData;
     newEventData[P_ELEMENT] = (void*)this;
     newEventData[P_SELECTION] = getSelection();
-    sendEvent(EVENT_ITEMSELECTED, eventData);
+    sendEvent(EVENT_ITEMSELECTED, newEventData);
 }

+ 6 - 2
Engine/UI/Menu.cpp

@@ -28,7 +28,7 @@
 
 #include "DebugNew.h"
 
-static const ShortStringHash originHash("origin");
+static const ShortStringHash originHash("Origin");
 
 Menu::Menu(const std::string& name) :
     Button(name),
@@ -170,7 +170,11 @@ void Menu::handleFocusChanged(StringHash eventType, VariantMap& eventData)
     UIElement* element = static_cast<UIElement*>(eventData[P_ELEMENT].getPtr());
     UIElement* root = getRootElement();
     
-    // If clicked emptiness, hide the popup
+    // If another element was focused due to the menu button being clicked, do not hide the popup
+    if ((eventType == EVENT_FOCUSCHANGED) && (static_cast<UIElement*>(eventData[P_ORIGINALELEMENT].getPtr())))
+        return;
+    
+    // If clicked emptiness or defocused, hide the popup
     if (!element)
     {
         showPopup(false);

+ 5 - 3
Engine/UI/UI.cpp

@@ -110,6 +110,11 @@ void UI::setCursor(Cursor* cursor)
 
 void UI::setFocusElement(UIElement* element)
 {
+    using namespace FocusChanged;
+    
+    VariantMap eventData;
+    eventData[P_ORIGINALELEMENT] = (void*)element;
+    
     if (element)
     {
         // Return if already has focus
@@ -140,9 +145,6 @@ void UI::setFocusElement(UIElement* element)
     if (element)
         element->setFocus(true);
     
-    using namespace FocusChanged;
-    
-    VariantMap eventData;
     eventData[P_ELEMENT] = (void*)element;
     sendEvent(EVENT_FOCUSCHANGED, eventData);
 }

+ 1 - 0
Engine/UI/UIEvents.h

@@ -57,6 +57,7 @@ DEFINE_EVENT(EVENT_DRAGDROPFINISH, DragDropFinish)
 DEFINE_EVENT(EVENT_FOCUSCHANGED, FocusChanged)
 {
     EVENT_PARAM(P_ELEMENT, Element);            // UIElement pointer
+    EVENT_PARAM(P_ORIGINALELEMENT, Element);    // UIElement pointer
 }
 
 //! UI element resized