Browse Source

Started work on UI element editor, pardon the dust. Added 'traversalMode' property to UIElement and exposed it to ScriptAPI to control the children UI batches generation. Enhanced Scene to safe keep the filename used in last serialization, fixed to perform a Clear() before Load()/LoadXML(). Exposed String's Clear() method to Script API. Fixed ListView to insert multiple top-level items in hierarchy mode correctly.

Wei Tjong Yao 12 years ago
parent
commit
75df330fe3

+ 8 - 0
Bin/Data/Scripts/Editor.as

@@ -2,6 +2,7 @@
 
 #include "Scripts/Editor/EditorView.as"
 #include "Scripts/Editor/EditorScene.as"
+#include "Scripts/Editor/EditorUIElement.as"
 #include "Scripts/Editor/EditorGizmo.as"
 #include "Scripts/Editor/EditorSettings.as"
 #include "Scripts/Editor/EditorPreferences.as"
@@ -32,6 +33,7 @@ void Start()
     }
 
     SubscribeToEvent("Update", "HandleUpdate");
+    
     // Enable console commands from the editor script
     script.defaultScriptFile = scriptFile;
     // Enable automatic resource reloading
@@ -39,9 +41,15 @@ void Start()
     // Use OS mouse without grabbing it
     input.mouseVisible = true;
 
+    // Create root scene node
     CreateScene();
+    // Load editor settings and preferences
     LoadConfig();
+    // Create user interface for the editor
     CreateUI();
+    // Create root UI element where all 'editable' UI elements would be parented to
+    CreateUIElement();
+    // Load the initial scene if provided
     ParseArguments();
 }
 

+ 7 - 10
Bin/Data/Scripts/Editor/AttributeEditor.as

@@ -9,14 +9,11 @@ const uint MIN_NODE_ATTRIBUTES = 4;
 const uint MAX_NODE_ATTRIBUTES = 8;
 const int ATTRNAME_WIDTH = 150;
 const int ATTR_HEIGHT = 19;
-bool inLoadAttributeEditor = false;
+const StringHash TEXT_CHANGED_EVENT_TYPE("TextChanged");
 
+bool inLoadAttributeEditor = false;
 bool showNonEditableAttribute = false;
 
-const ShortStringHash textType("Text");
-const ShortStringHash containerType("UIElement");
-const StringHash textChangedEventType("TextChanged");
-
 Color normalTextColor(1.0f, 1.0f, 1.0f);
 Color modifiedTextColor(1.0f, 0.8f, 0.5f);
 Color nonEditableTextColor(0.7f, 0.7f, 0.7f);
@@ -398,15 +395,15 @@ void LoadAttributeEditor(ListView@ list, Array<Serializable@>@ serializables, co
     inLoadAttributeEditor = false;
 }
 
-void LoadAttributeEditor(UIElement@ parent, Variant value, const AttributeInfo&in info, bool editable, bool sameValue, const Array<Variant>&in values)
+void LoadAttributeEditor(UIElement@ parent, const Variant&in value, const AttributeInfo&in info, bool editable, bool sameValue, const Array<Variant>&in values)
 {
     uint index = parent.vars["Index"].GetUInt();
 
     // Assume the first child is always a text label element or a container that containing a text label element
     UIElement@ label = parent.children[0];
-    if (label.type == containerType && label.numChildren > 0)
+    if (label.type == UI_ELEMENT_TYPE && label.numChildren > 0)
         label = label.children[0];
-    if (label.type == textType)
+    if (label.type == TEXT_TYPE)
     {
         bool modified;
         if (info.defaultValue.type == VAR_NONE || info.defaultValue.type == VAR_RESOURCEREFLIST)
@@ -612,7 +609,7 @@ void StoreAttributeEditor(UIElement@ parent, Array<Serializable@>@ serializables
     }
 }
 
-void FillValue(Array<Variant>& values, Variant value)
+void FillValue(Array<Variant>& values, const Variant&in value)
 {
     for (uint i = 0; i < values.length; ++i)
         values[i] = value;
@@ -748,7 +745,7 @@ void EditAttribute(StringHash eventType, VariantMap& eventData)
     uint index = attrEdit.vars["Index"].GetUInt();
     uint subIndex = attrEdit.vars["SubIndex"].GetUInt();
     uint coordinate = attrEdit.vars["Coordinate"].GetUInt();
-    bool intermediateEdit = eventType == textChangedEventType;
+    bool intermediateEdit = eventType == TEXT_CHANGED_EVENT_TYPE;
 
     StoreAttributeEditor(parent, serializables, index, subIndex, coordinate);
     for (uint i = 0; i < serializables.length; ++i)

+ 2 - 4
Bin/Data/Scripts/Editor/EditorImport.as

@@ -87,7 +87,7 @@ void ImportScene(const String&in fileName)
     else
     {
         // Export scene to a temp file, then load and delete it if successful
-        String tempSceneName = sceneResourcePath + "_tempscene_.xml";
+        String tempSceneName = sceneResourcePath + TEMP_SCENE_NAME;
         Array<String> args;
         args.Push("scene");
         args.Push("\"" + fileName + "\"");
@@ -100,10 +100,8 @@ void ImportScene(const String&in fileName)
 
         if (fileSystem.SystemRun(fileSystem.programDir + "AssetImporter", args) == 0)
         {
-            String currentFileName = sceneFileName;
             LoadScene(tempSceneName);
             fileSystem.Delete(tempSceneName);
-            sceneFileName = currentFileName;
             UpdateWindowTitle();
         }
     }
@@ -338,7 +336,7 @@ void ImportTundraScene(const String&in fileName)
             childNode.parent = parentNode;
     }
 
-    UpdateSceneWindow();
+    UpdateHierarchyWindowItem(editorScene, true);
     UpdateWindowTitle();
     assetMappings.Clear();
 }

+ 1 - 1
Bin/Data/Scripts/Editor/EditorNodeWindow.as

@@ -64,7 +64,7 @@ void ShowNodeWindow()
 
 void AdjustListViewChild(ListView@ list)
 {
-	// At the moment, only 'Is Enabled' container (place-holder + check box) is being created as child of the list view instead of as list item
+    // At the moment, only 'Is Enabled' container (place-holder + check box) is being created as child of the list view instead of as list item
     int width = list.width;
     for (uint i = 0; i < list.numChildren; ++i)
     {

+ 4 - 4
Bin/Data/Scripts/Editor/EditorPreferences.as

@@ -99,8 +99,8 @@ void EditUIMinOpacity(StringHash eventType, VariantMap& eventData)
     LineEdit@ edit = eventData["Element"].GetUIElement();
     uiMinOpacity = edit.text.ToFloat();
     edit.text = String(uiMinOpacity);
-    HideUI();
-    UnhideUI();
+    FadeUI();
+    UnfadeUI();
 }
 
 void EditUIMaxOpacity(StringHash eventType, VariantMap& eventData)
@@ -108,8 +108,8 @@ void EditUIMaxOpacity(StringHash eventType, VariantMap& eventData)
     LineEdit@ edit = eventData["Element"].GetUIElement();
     uiMaxOpacity = edit.text.ToFloat();
     edit.text = String(uiMaxOpacity);
-    HideUI();
-    UnhideUI();
+    FadeUI();
+    UnfadeUI();
 }
 
 void ToggleShowNonEditableAttribute(StringHash eventType, VariantMap& eventData)

+ 38 - 29
Bin/Data/Scripts/Editor/EditorScene.as

@@ -11,7 +11,6 @@ const int MAX_PICK_MODES = 4;
 
 Scene@ editorScene;
 
-String sceneFileName;
 String instantiateFileName;
 CreateMode instantiateMode = REPLICATED;
 bool sceneModified = false;
@@ -27,9 +26,10 @@ uint numEditableComponentsPerNode = 1;
 Array<XMLFile@> copyBuffer;
 bool copyBufferLocal = false;
 
+bool suppressSceneChanges = false;
 bool inSelectionModify = false;
 
-void ClearSelection()
+void ClearSceneSelection()
 {
     selectedNodes.Clear();
     selectedComponents.Clear();
@@ -58,26 +58,24 @@ void CreateScene()
 
 void ResetScene()
 {
-    ClearSelection();
-
     suppressSceneChanges = true;
 
     // Create a scene with default values, these will be overridden when loading scenes
     editorScene.Clear();
-    editorScene.name = "";
     Octree@ octree = editorScene.CreateComponent("Octree");
     PhysicsWorld@ physicsWorld = editorScene.CreateComponent("PhysicsWorld");
     octree.Resize(BoundingBox(-1000.0, 1000.0), 8);
     editorScene.CreateComponent("DebugRenderer");
 
-    UpdateSceneWindow();
+    sceneModified = false;
+    runUpdate = false;
+
+    UpdateWindowTitle();
+    UpdateHierarchyWindowItem(editorScene, true);
     UpdateNodeWindow();
     
     suppressSceneChanges = false;
-
-    runUpdate = false;
-    sceneFileName = "";
-    UpdateWindowTitle();
+    
     ResetCamera();
     CreateGizmo();
 }
@@ -124,15 +122,11 @@ bool LoadScene(const String&in fileName)
     if (!file.open)
         return false;
 
-    // Clear the old scene
-    suppressSceneChanges = true;
-
-    ClearSelection();
-    editorScene.Clear();
-
     // Add the new resource path
     SetResourcePath(GetPath(fileName));
 
+    suppressSceneChanges = true;
+
     String extension = GetExtension(fileName);
     bool loaded;
     if (extension != ".xml")
@@ -143,11 +137,11 @@ bool LoadScene(const String&in fileName)
     // Always pause the scene, and do updates manually
     editorScene.updateEnabled = false;
 
-    sceneFileName = fileName;
     sceneModified = false;
     runUpdate = false;
+    
     UpdateWindowTitle();
-    UpdateSceneWindow();
+    UpdateHierarchyWindowItem(editorScene, true);
     UpdateNodeWindow();
 
     suppressSceneChanges = false;
@@ -163,6 +157,8 @@ void SaveScene(const String&in fileName)
     if (fileName.empty || GetFileName(fileName).empty)
         return;
 
+    ui.cursor.shape = CS_BUSY;
+    
     // Unpause when saving so that the scene will work properly when loaded outside the editor
     editorScene.updateEnabled = true;
 
@@ -175,7 +171,6 @@ void SaveScene(const String&in fileName)
 
     editorScene.updateEnabled = false;
 
-    sceneFileName = fileName;
     sceneModified = false;
     UpdateWindowTitle();
 }
@@ -195,6 +190,8 @@ void LoadNode(const String&in fileName)
     if (!file.open)
         return;
 
+    ui.cursor.shape = CS_BUSY;
+    
     // Before instantiating, set resource path if empty
     if (sceneResourcePath.empty)
         SetResourcePath(GetPath(fileName));
@@ -220,6 +217,8 @@ void SaveNode(const String&in fileName)
     if (fileName.empty || GetFileName(fileName).empty)
         return;
 
+    ui.cursor.shape = CS_BUSY;
+    
     if (selectedNodes.length == 1)
     {
         File file(fileName, FILE_WRITE);
@@ -268,14 +267,16 @@ void EndSelectionModify()
 {
     // The large operation on selected nodes has ended. Update node/component selection now
     inSelectionModify = false;
-    HandleSceneWindowSelectionChange();
+    HandleHierarchyListSelectionChange();
 }
 
 bool SceneDelete()
 {
-    if (!CheckSceneWindowFocus() || (selectedComponents.empty && selectedNodes.empty))
+    if (!CheckHierarchyWindowFocus() || (selectedComponents.empty && selectedNodes.empty))
         return false;
 
+    ui.cursor.shape = CS_BUSY;
+    
     BeginSelectionModify();
     
     // Clear the selection now to prevent repopulation of selectedNodes and selectedComponents combo
@@ -289,7 +290,7 @@ bool SceneDelete()
             continue; // Root or already deleted
 
         uint id = node.id;
-        uint nodeIndex = GetNodeListIndex(node);
+        uint nodeIndex = GetListIndex(node);
 
         BeginModify(id);
         node.Remove();
@@ -309,7 +310,7 @@ bool SceneDelete()
             continue; // Already deleted
 
         uint index = GetComponentListIndex(component);
-        uint nodeIndex = GetNodeListIndex(node);
+        uint nodeIndex = GetListIndex(node);
         if (index == NO_ITEM || nodeIndex == NO_ITEM)
             continue;
 
@@ -342,13 +343,15 @@ bool SceneCut()
 
 bool SceneCopy()
 {
-    if ((selectedNodes.empty && selectedComponents.empty) || !CheckSceneWindowFocus())
+    if ((selectedNodes.empty && selectedComponents.empty) || !CheckHierarchyWindowFocus())
         return false;
 
     // Must have either only components, or only nodes
     if (!selectedNodes.empty && !selectedComponents.empty)
         return false;
 
+    ui.cursor.shape = CS_BUSY;
+    
     copyBuffer.Clear();
 
     // Copy components
@@ -388,9 +391,11 @@ bool SceneCopy()
 
 bool ScenePaste()
 {
-    if (editNode is null || !CheckSceneWindowFocus() || copyBuffer.empty)
+    if (editNode is null || !CheckHierarchyWindowFocus() || copyBuffer.empty)
         return false;
 
+    ui.cursor.shape = CS_BUSY;
+    
     bool pasteComponents = false;
 
     for (uint i = 0; i < copyBuffer.length; ++i)
@@ -438,9 +443,11 @@ bool ScenePaste()
 
 void SceneUnparent()
 {
-    if (!CheckSceneWindowFocus() || !selectedComponents.empty || selectedNodes.empty)
+    if (!CheckHierarchyWindowFocus() || !selectedComponents.empty || selectedNodes.empty)
         return;
 
+    ui.cursor.shape = CS_BUSY;
+    
     // Parent selected nodes to root
     Array<Node@> changedNodes;
     for (uint i = 0; i < selectedNodes.length; ++i)
@@ -456,14 +463,16 @@ void SceneUnparent()
 
     // Reselect the changed nodes at their new position in the list
     for (uint i = 0; i < changedNodes.length; ++i)
-        hierarchyList.AddSelection(GetNodeListIndex(changedNodes[i]));
+        hierarchyList.AddSelection(GetListIndex(changedNodes[i]));
 }
 
 void SceneToggleEnable()
 {
-    if (!CheckSceneWindowFocus())
+    if (!CheckHierarchyWindowFocus())
         return;
 
+    ui.cursor.shape = CS_BUSY;
+    
     // Toggle enabled state of nodes recursively
     for (uint i = 0; i < selectedNodes.length; ++i)
     {
@@ -534,7 +543,7 @@ void SceneSelectAll()
         Array<Node@> rootLevelNodes = editorScene.GetChildren();
         Array<uint> indices;
         for (uint i = 0; i < rootLevelNodes.length; ++i)
-            indices.Push(GetNodeListIndex(rootLevelNodes[i]));
+            indices.Push(GetListIndex(rootLevelNodes[i]));
         hierarchyList.SetSelections(indices);
         EndSelectionModify();
     }

+ 171 - 144
Bin/Data/Scripts/Editor/EditorSceneWindow.as

@@ -3,29 +3,32 @@
 const int ITEM_NONE = 0;
 const int ITEM_NODE = 1;
 const int ITEM_COMPONENT = 2;
+const int ITEM_UI_ELEMENT = 3;
 const uint NO_ITEM = M_MAX_UNSIGNED;
+const ShortStringHash SCENE_TYPE("Scene");
+const ShortStringHash NODE_TYPE("Node");
+const String TITLE_NO_CHANGE(uint8(0));
 
-Window@ sceneWindow;
+Window@ hierarchyWindow;
 ListView@ hierarchyList;
 
-bool suppressSceneChanges = false;
-
-void CreateSceneWindow()
+void CreateHierarchyWindow()
 {
-    if (sceneWindow !is null)
+    if (hierarchyWindow !is null)
         return;
 
-    sceneWindow = ui.LoadLayout(cache.GetResource("XMLFile", "UI/EditorSceneWindow.xml"));
-    hierarchyList = sceneWindow.GetChild("NodeList");
-    ui.root.AddChild(sceneWindow);
+    hierarchyWindow = ui.LoadLayout(cache.GetResource("XMLFile", "UI/EditorSceneWindow.xml"));
+    hierarchyList = hierarchyWindow.GetChild("NodeList");
+    ui.root.AddChild(hierarchyWindow);
     int height = Min(ui.root.height - 60, 500);
-    sceneWindow.SetSize(300, height);
-    sceneWindow.SetPosition(20, 40);
-    sceneWindow.opacity = uiMaxOpacity;
-    sceneWindow.BringToFront();
-    UpdateSceneWindow();
+    hierarchyWindow.SetSize(300, height);
+    hierarchyWindow.SetPosition(20, 40);
+    hierarchyWindow.opacity = uiMaxOpacity;
+    hierarchyWindow.BringToFront();
+
+    UpdateHierarchyWindowItem(editorScene);
 
-    DropDownList@ newNodeList = sceneWindow.GetChild("NewNodeList", true);
+    DropDownList@ newNodeList = hierarchyWindow.GetChild("NewNodeList", true);
     Array<String> newNodeChoices = {"Replicated", "Local"};
     for (uint i = 0; i < newNodeChoices.length; ++i)
     {
@@ -35,7 +38,7 @@ void CreateSceneWindow()
         newNodeList.AddItem(choice);
     }
 
-    DropDownList@ newComponentList = sceneWindow.GetChild("NewComponentList", true);
+    DropDownList@ newComponentList = hierarchyWindow.GetChild("NewComponentList", true);
     Array<String> componentTypes = GetAvailableComponents();
     for (uint i = 0; i < componentTypes.length; ++i)
     {
@@ -51,12 +54,11 @@ void CreateSceneWindow()
     hierarchyList.contentElement.dragDropMode = DD_TARGET;
     hierarchyList.scrollPanel.dragDropMode = DD_TARGET;
 
-    SubscribeToEvent(sceneWindow.GetChild("CloseButton", true), "Released", "HideSceneWindow");
-    SubscribeToEvent(sceneWindow.GetChild("ExpandButton", true), "Released", "ExpandCollapseHierarchy");
-    SubscribeToEvent(sceneWindow.GetChild("CollapseButton", true), "Released", "ExpandCollapseHierarchy");
-    SubscribeToEvent(hierarchyList, "SelectionChanged", "HandleSceneWindowSelectionChange");
-    SubscribeToEvent(hierarchyList, "ItemDoubleClicked", "HandleSceneWindowItemDoubleClick");
-    SubscribeToEvent(hierarchyList, "UnhandledKey", "HandleSceneWindowKey");
+    SubscribeToEvent(hierarchyWindow.GetChild("CloseButton", true), "Released", "HideHierarchyWindow");
+    SubscribeToEvent(hierarchyWindow.GetChild("ExpandButton", true), "Released", "ExpandCollapseHierarchy");
+    SubscribeToEvent(hierarchyWindow.GetChild("CollapseButton", true), "Released", "ExpandCollapseHierarchy");
+    SubscribeToEvent(hierarchyList, "SelectionChanged", "HandleHierarchyListSelectionChange");
+    SubscribeToEvent(hierarchyList, "ItemDoubleClicked", "HandleHierarchyListItemDoubleClick");
     SubscribeToEvent(newNodeList, "ItemSelected", "HandleCreateNode");
     SubscribeToEvent(newComponentList, "ItemSelected", "HandleCreateComponent");
     SubscribeToEvent("DragDropTest", "HandleDragDropTest");
@@ -70,22 +72,24 @@ void CreateSceneWindow()
     SubscribeToEvent(editorScene, "ComponentEnabledChanged", "HandleComponentEnabledChanged");
 }
 
-void ShowSceneWindow()
+void ShowHierarchyWindow()
 {
-    sceneWindow.visible = true;
-    sceneWindow.BringToFront();
+    hierarchyWindow.visible = true;
+    hierarchyWindow.BringToFront();
 }
 
-void HideSceneWindow()
+void HideHierarchyWindow()
 {
-    sceneWindow.visible = false;
+    hierarchyWindow.visible = false;
 }
 
 void ExpandCollapseHierarchy(StringHash eventType, VariantMap& eventData)
 {
     Button@ button = eventData["Element"].GetUIElement();
     bool enable = button.name == "ExpandButton";
-    bool all = cast<CheckBox>(sceneWindow.GetChild("AllCheckBox", true)).checked;
+    CheckBox@ checkBox = cast<CheckBox>(hierarchyWindow.GetChild("AllCheckBox", true));
+    bool all = checkBox.checked;
+    checkBox.checked = false;	// Auto-reset
 
     Array<uint> selections = hierarchyList.selections;
     for (uint i = 0; i < selections.length; ++i)
@@ -97,39 +101,48 @@ void EnableExpandCollapseButtons(bool enable)
     String[] buttons = { "ExpandButton", "CollapseButton", "AllCheckBox" };
     for (uint i = 0; i < buttons.length; ++i)
     {
-        UIElement@ element = sceneWindow.GetChild(buttons[i], true);
+        UIElement@ element = hierarchyWindow.GetChild(buttons[i], true);
         element.enabled = enable;
         element.children[0].color = enable ? normalTextColor : nonEditableTextColor;
     }
 }
 
-void ClearSceneWindow()
+void UpdateHierarchyWindowItem(Serializable@ serializable, bool clear = false)
 {
-    if (sceneWindow is null)
-        return;
-
-    hierarchyList.RemoveAllItems();
-}
+    if (clear)
+    {
+        // Remove the current selection before updating the list item (in turn trigger an update on the attribute editor)
+        hierarchyList.ClearSelection();
 
-void UpdateSceneWindow()
-{
-    ClearSceneWindow();
-    UpdateSceneWindowNode(0, editorScene, null);
+        // Clear copybuffer when whole window refreshed
+        copyBuffer.Clear();
+    }
 
-    // Clear copybuffer when whole window refreshed
-    copyBuffer.Clear();
+    // In case of item's parent is not found in the hierarchy list then the item will be inserted at the list root level
+    Serializable@ parent;
+    if (serializable.type == NODE_TYPE || serializable.type == SCENE_TYPE)
+        parent = cast<Node>(serializable).parent;
+    else if (serializable !is editorUIElement)
+        parent = cast<UIElement>(serializable).parent;
+    UIElement@ parentItem = hierarchyList.items[GetListIndex(parent)];
+    UpdateHierarchyWindowItem(GetListIndex(serializable), serializable, parentItem);
 }
 
-uint UpdateSceneWindowNode(uint itemIndex, Node@ node, UIElement@ parentItem)
+uint UpdateHierarchyWindowItem(uint itemIndex, Serializable@ serializable, UIElement@ parentItem)
 {
     // Whenever we're updating, disable layout update to optimize speed
     hierarchyList.contentElement.DisableLayoutUpdate();
 
+    String idVar;
+    Variant id;
+    int itemType = ITEM_NONE;
+    if (serializable !is null)
+        GetID(serializable, idVar, id, itemType);
+    
     // Remove old item if exists
-    if (itemIndex < hierarchyList.numItems && (node is null || (hierarchyList.items[itemIndex].vars["Type"].GetInt() == ITEM_NODE &&
-        hierarchyList.items[itemIndex].vars["NodeID"].GetUInt() == node.id)))
+    if (itemIndex < hierarchyList.numItems && (serializable is null || MatchID(hierarchyList.items[itemIndex], idVar, id, itemType)))
         hierarchyList.RemoveItem(itemIndex);
-    if (node is null)
+    if (serializable is null)
     {
         hierarchyList.contentElement.EnableLayoutUpdate();
         hierarchyList.contentElement.UpdateLayout();
@@ -138,38 +151,61 @@ uint UpdateSceneWindowNode(uint itemIndex, Node@ node, UIElement@ parentItem)
 
     Text@ text = Text();
     text.SetStyle(uiStyle, "FileSelectorListText");
-    text.vars["Type"] = ITEM_NODE;
-    text.vars["NodeID"] = node.id;
-    text.text = GetNodeTitle(node);
+    SetID(text, serializable);
 
-    // Nodes can be moved by drag and drop. The root node (scene) can not.
-    if (node.typeName == "Node")
-        text.dragDropMode = DD_SOURCE_AND_TARGET;
-    else
+    // The root node (scene) cannot be moved by drag and drop.
+    if (serializable.type == SCENE_TYPE)
         text.dragDropMode = DD_TARGET;
+    else
+        text.dragDropMode = DD_SOURCE_AND_TARGET;
 
     hierarchyList.InsertItem(itemIndex, text, parentItem);
-    IconizeUIElement(text, node.typeName);
-    SetIconEnabledColor(text, node.enabled);
 
-    // Advance the index for the child components and/or nodes
+    // Advance the index for the child items
     if (itemIndex == M_MAX_UNSIGNED)
         itemIndex = hierarchyList.numItems;
     else
         ++itemIndex;
 
-    // Update components first
-    for (uint i = 0; i < node.numComponents; ++i)
+    String iconType = serializable.typeName;
+    if (serializable is editorUIElement)
+        iconType = "Root" + iconType;
+    IconizeUIElement(text, iconType);
+    
+    if (serializable.type == NODE_TYPE || serializable.type == SCENE_TYPE)
     {
-        Component@ component = node.components[i];
-        AddComponentToSceneWindow(component, itemIndex++, text);
-    }
+        Node@ node = cast<Node>(serializable);
+        
+        text.text = GetNodeTitle(node);
+        SetIconEnabledColor(text, node.enabled);
 
-    // Then update child nodes recursively
-    for (uint i = 0; i < node.numChildren; ++i)
+        // Update components first
+        for (uint i = 0; i < node.numComponents; ++i)
+        {
+            Component@ component = node.components[i];
+            AddComponentItem(itemIndex++, component, text);
+        }
+    
+        // Then update child nodes recursively
+        for (uint i = 0; i < node.numChildren; ++i)
+        {
+            Node@ childNode = node.children[i];
+            itemIndex = UpdateHierarchyWindowItem(itemIndex, childNode, text);
+        }
+    }
+    else
     {
-        Node@ childNode = node.children[i];
-        itemIndex = UpdateSceneWindowNode(itemIndex, childNode, text);
+        UIElement@ element = cast<UIElement>(serializable);
+        
+        text.text = GetUIElementTitle(element);
+        SetIconEnabledColor(text, element.visible);
+
+        // Then update child elements recursively
+        for (uint i = 0; i < element.numChildren; ++i)
+        {
+            UIElement@ childElement = element.children[i];
+            itemIndex = UpdateHierarchyWindowItem(itemIndex, childElement, text);
+        }
     }
 
     // Re-enable layout update (and do manual layout) now
@@ -179,33 +215,19 @@ uint UpdateSceneWindowNode(uint itemIndex, Node@ node, UIElement@ parentItem)
     return itemIndex;
 }
 
-void UpdateSceneWindowNode(Node@ node)
-{
-    // In case of node's parent is not found in the hierarchy list then the node will inserted at the root level, but it should not happen
-    UpdateSceneWindowNode(GetNodeListIndex(node), node, hierarchyList.items[GetNodeListIndex(node.parent)]);
-}
-
-void UpdateSceneWindowNodeText(Node@ node, bool iconOnly = false)
-{
-    uint index = GetNodeListIndex(node);
-    Text@ text = hierarchyList.items[index];
-    if (text is null)
-        return;
-    if (!iconOnly)
-        text.text = GetNodeTitle(node);
-    SetIconEnabledColor(text, node.enabled);
-}
-
-void UpdateSceneWindowComponentText(Component@ component)
+void UpdateHierarchyWindowItemText(uint itemIndex, bool iconEnabled, const String&in textTitle = TITLE_NO_CHANGE)
 {
-    uint index = GetComponentListIndex(component);
-    Text@ text = hierarchyList.items[index];
+    Text@ text = hierarchyList.items[itemIndex];
     if (text is null)
         return;
-    SetIconEnabledColor(text, component.enabledEffective);
+    
+    SetIconEnabledColor(text, iconEnabled);
+    
+    if (textTitle != TITLE_NO_CHANGE)
+        text.text = textTitle;
 }
 
-void AddComponentToSceneWindow(Component@ component, uint compItemIndex, UIElement@ parentItem)
+void AddComponentItem(uint compItemIndex, Component@ component, UIElement@ parentItem)
 {
     Text@ text = Text();
     text.SetStyle(uiStyle, "FileSelectorListText");
@@ -219,36 +241,57 @@ void AddComponentToSceneWindow(Component@ component, uint compItemIndex, UIEleme
     SetIconEnabledColor(text, component.enabledEffective);
 }
 
-uint GetNodeListIndex(Node@ node)
+void SetID(Text@ text, Serializable@ serializable)
 {
-    if (node is null)
-        return NO_ITEM;
-
-    uint numItems = hierarchyList.numItems;
-    uint nodeID = node.id;
+    if (serializable.type == NODE_TYPE || serializable.type == SCENE_TYPE)
+    {
+        text.vars["Type"] = ITEM_NODE;
+        text.vars["NodeID"] = cast<Node>(serializable).id;
+    }
+    else
+    {
+        text.vars["Type"] = ITEM_UI_ELEMENT;
+        text.vars["ElementName"] = cast<UIElement>(serializable).name;
+    }
+}
 
-    for (uint i = 0; i < numItems; ++i)
+void GetID(Serializable@ serializable, String& idVar, Variant& id, int& itemType)
+{
+    if (serializable.type == NODE_TYPE || serializable.type == SCENE_TYPE)
     {
-        UIElement@ item = hierarchyList.items[i];
-        if (item.vars["Type"].GetInt() == ITEM_NODE && item.vars["NodeID"].GetUInt() == nodeID)
-            return i;
+        idVar = "NodeID";
+        id = Variant(cast<Node>(serializable).id);
+        itemType = ITEM_NODE;
     }
+    else
+    {
+        idVar = "ElementName";
+        id = Variant(cast<UIElement>(serializable).name);
+        itemType = ITEM_UI_ELEMENT;
+    }
+}
 
-    return NO_ITEM;
+bool MatchID(UIElement@ element, const String&in idVar, const Variant&in id, int itemType)
+{
+    return element.vars["Type"].GetInt() == itemType && element.vars[idVar] == id;
 }
 
-uint GetNodeListIndex(Node@ node, uint startPos)
+uint GetListIndex(Serializable@ serializable)
 {
-    if (node is null)
+    if (serializable is null)
         return NO_ITEM;
 
     uint numItems = hierarchyList.numItems;
-    uint nodeID = node.id;
+    
+    String idVar;
+    Variant id;
+    int itemType = ITEM_NONE;
+    GetID(serializable, idVar, id, itemType);
 
-    for (uint i = startPos; i < numItems; --i)
+    for (uint i = 0; i < numItems; ++i)
     {
         UIElement@ item = hierarchyList.items[i];
-        if (item.vars["Type"].GetInt() == ITEM_NODE && item.vars["NodeID"].GetInt() == int(nodeID))
+        if (MatchID(item, idVar, id, itemType))
             return i;
     }
 
@@ -297,17 +340,9 @@ uint GetComponentListIndex(Component@ component)
     return NO_ITEM;
 }
 
-int GetNodeIndent(Node@ node)
+String GetUIElementTitle(UIElement@ element)
 {
-    int indent = 0;
-    for (;;)
-    {
-        if (node.parent is null)
-            break;
-        ++indent;
-        node = node.parent;
-    }
-    return indent;
+    return element.name.empty ? element.typeName : element.name;
 }
 
 String GetNodeTitle(Node@ node)
@@ -341,7 +376,7 @@ void SelectNode(Node@ node, bool multiselect)
         return;
     }
 
-    uint nodeItem = GetNodeListIndex(node);
+    uint nodeItem = GetListIndex(node);
 
     // Go in the parent chain up to make sure the chain is expanded
     for (;;)
@@ -353,7 +388,7 @@ void SelectNode(Node@ node, bool multiselect)
     }
     
     uint numItems = hierarchyList.numItems;
-    uint parentItem = GetNodeListIndex(node);
+    uint parentItem = GetListIndex(node);
 
     if (nodeItem < numItems)
     {
@@ -392,7 +427,7 @@ void SelectComponent(Component@ component, bool multiselect)
         return;
     }
 
-    uint nodeItem = GetNodeListIndex(node);
+    uint nodeItem = GetListIndex(node);
     uint componentItem = GetComponentListIndex(component);
     
     // Go in the parent chain up to make sure the chain is expanded
@@ -405,7 +440,7 @@ void SelectComponent(Component@ component, bool multiselect)
     }
 
     uint numItems = hierarchyList.numItems;
-    uint parentItem = GetNodeListIndex(node);
+    uint parentItem = GetListIndex(node);
 
     if (parentItem >= hierarchyList.numItems && !multiselect)
     {
@@ -435,12 +470,13 @@ void SelectComponent(Component@ component, bool multiselect)
     }
 }
 
-void HandleSceneWindowSelectionChange()
+void HandleHierarchyListSelectionChange()
 {
     if (inSelectionModify)
         return;
     
-    ClearSelection();
+    ClearSceneSelection();
+    ClearUIElementSelection();
 
     Array<uint> indices = hierarchyList.selections;
 
@@ -546,8 +582,8 @@ void HandleSceneWindowSelectionChange()
     {
         editNodes = selectedNodes;
         
-        // Cannot multi-edit on scene and node(s) together as scene and node does not share identical attributes,
-        // editing via gizmo does not makes too much sense either
+        // Cannot multi-edit on scene and node(s) together as scene and node do not share identical attributes,
+        // editing via gizmo does not make too much sense either
         if (editNodes.length > 1 && editNodes[0] is editorScene)
             editNodes.Erase(0);
     }
@@ -556,29 +592,24 @@ void HandleSceneWindowSelectionChange()
     UpdateNodeWindow();
 }
 
-void HandleSceneWindowItemDoubleClick(StringHash eventType, VariantMap& eventData)
+void HandleHierarchyListItemDoubleClick(StringHash eventType, VariantMap& eventData)
 {
     uint index = eventData["Selection"].GetUInt();
     hierarchyList.ToggleExpand(index);
 }
 
-void HandleSceneWindowKey(StringHash eventType, VariantMap& eventData)
-{
-    int key = eventData["Key"].GetInt();
-}
-
 void HandleDragDropTest(StringHash eventType, VariantMap& eventData)
 {
     UIElement@ source = eventData["Source"].GetUIElement();
     UIElement@ target = eventData["Target"].GetUIElement();
-    eventData["Accept"] = TestSceneWindowElements(source, target);
+    eventData["Accept"] = TestDragDrop(source, target);
 }
 
 void HandleDragDropFinish(StringHash eventType, VariantMap& eventData)
 {
     UIElement@ source = eventData["Source"].GetUIElement();
     UIElement@ target = eventData["Target"].GetUIElement();
-    bool accept =  TestSceneWindowElements(source, target);
+    bool accept =  TestDragDrop(source, target);
     eventData["Accept"] = accept;
     if (!accept)
         return;
@@ -598,7 +629,7 @@ void HandleDragDropFinish(StringHash eventType, VariantMap& eventData)
     FocusNode(sourceNode);
 }
 
-bool TestSceneWindowElements(UIElement@ source, UIElement@ target)
+bool TestDragDrop(UIElement@ source, UIElement@ target)
 {
     // Test for validity of reparenting by drag and drop
     Node@ sourceNode;
@@ -625,7 +656,7 @@ bool TestSceneWindowElements(UIElement@ source, UIElement@ target)
 
 void FocusNode(Node@ node)
 {
-    uint index = GetNodeListIndex(node);
+    uint index = GetListIndex(node);
     hierarchyList.selection = index;
 }
 
@@ -687,14 +718,10 @@ void CreateBuiltinObject(const String& name)
     FocusNode(newNode);
 }
 
-bool CheckSceneWindowFocus()
+bool CheckHierarchyWindowFocus()
 {
-    // When we do scene operations based on key shortcuts, make sure either the 3D scene or the node list is focused,
-    // not for example a file selector
-    if (ui.focusElement is hierarchyList || ui.focusElement is null)
-        return true;
-    else
-        return false;
+    // When we do edit operations based on key shortcuts, make sure the hierarchy list is focused, not for example a file selector
+    return ui.focusElement is hierarchyList || ui.focusElement is null;
 }
 
 bool CheckForExistingGlobalComponent(Node@ node, const String&in typeName)
@@ -711,7 +738,7 @@ void HandleNodeAdded(StringHash eventType, VariantMap& eventData)
         return;
 
     Node@ node = eventData["Node"].GetNode();
-    UpdateSceneWindowNode(node);
+    UpdateHierarchyWindowItem(node);
 }
 
 void HandleNodeRemoved(StringHash eventType, VariantMap& eventData)
@@ -720,8 +747,8 @@ void HandleNodeRemoved(StringHash eventType, VariantMap& eventData)
         return;
 
     Node@ node = eventData["Node"].GetNode();
-    uint index = GetNodeListIndex(node);
-    UpdateSceneWindowNode(index, null, null);
+    uint index = GetListIndex(node);
+    UpdateHierarchyWindowItem(index, null, null);
 }
 
 void HandleComponentAdded(StringHash eventType, VariantMap& eventData)
@@ -730,7 +757,7 @@ void HandleComponentAdded(StringHash eventType, VariantMap& eventData)
         return;
 
     Node@ node = eventData["Node"].GetNode();
-    UpdateSceneWindowNode(node);
+    UpdateHierarchyWindowItem(node);
 }
 
 void HandleComponentRemoved(StringHash eventType, VariantMap& eventData)
@@ -742,7 +769,7 @@ void HandleComponentRemoved(StringHash eventType, VariantMap& eventData)
     uint index = GetComponentListIndex(component);
     if (index != NO_ITEM)
     {
-        ListView@ list = sceneWindow.GetChild("NodeList", true);
+        ListView@ list = hierarchyWindow.GetChild("NodeList", true);
         list.RemoveItem(index);
     }
 }
@@ -753,7 +780,7 @@ void HandleNodeNameChanged(StringHash eventType, VariantMap& eventData)
         return;
     
     Node@ node = eventData["Node"].GetNode();
-    UpdateSceneWindowNodeText(node);
+    UpdateHierarchyWindowItemText(GetListIndex(node), node.enabled, GetNodeTitle(node));
 }
 
 void HandleNodeEnabledChanged(StringHash eventType, VariantMap& eventData)
@@ -762,7 +789,7 @@ void HandleNodeEnabledChanged(StringHash eventType, VariantMap& eventData)
         return;
     
     Node@ node = eventData["Node"].GetNode();
-    UpdateSceneWindowNodeText(node, true);
+    UpdateHierarchyWindowItemText(GetListIndex(node), node.enabled);
     attributesDirty = true;
 }
 
@@ -772,6 +799,6 @@ void HandleComponentEnabledChanged(StringHash eventType, VariantMap& eventData)
         return;
     
     Component@ component = eventData["Component"].GetComponent();
-    UpdateSceneWindowComponentText(component);
+    UpdateHierarchyWindowItemText(GetComponentListIndex(component), component.enabledEffective);
     attributesDirty = true;
 }

+ 370 - 191
Bin/Data/Scripts/Editor/EditorUI.as

@@ -5,24 +5,35 @@ XMLFile@ iconStyle;
 UIElement@ uiMenuBar;
 FileSelector@ uiFileSelector;
 
-const ShortStringHash windowType("Window");
-const ShortStringHash menuType("Menu");
+const ShortStringHash UI_ELEMENT_TYPE("UIElement");
+const ShortStringHash WINDOW_TYPE("Window");
+const ShortStringHash MENU_TYPE("Menu");
+const ShortStringHash TEXT_TYPE("Text");
+const ShortStringHash CURSOR_TYPE("Cursor");
+
+const String TEMP_SCENE_NAME("_tempscene_.xml");
+
+const int SHOW_POPUP_INDICATOR = -1;
 
 Array<String> uiSceneFilters = {"*.xml", "*.bin", "*.*"};
+Array<String> uiElementFilters = {"*.xml"};
 Array<String> uiAllFilters = {"*.*"};
 Array<String> uiScriptFilters = {"*.as", "*.*"};
 uint uiSceneFilter = 0;
+uint uiElementFilter = 0;
 uint uiNodeFilter = 0;
 uint uiImportFilter = 0;
 uint uiScriptFilter = 0;
 String uiScenePath = fileSystem.programDir + "Data/Scenes";
+String uiElementPath = fileSystem.programDir + "Data/UI";
 String uiNodePath = fileSystem.programDir + "Data/Objects";
 String uiImportPath;
 String uiScriptPath = fileSystem.programDir + "Data/Scripts";
 
-bool uiHidden = false;
+bool uiFaded = false;
 float uiMinOpacity = 0.3;
 float uiMaxOpacity = 0.7;
+bool uiHidden = false;
 
 void CreateUI()
 {
@@ -33,7 +44,7 @@ void CreateUI()
 
     CreateCursor();
     CreateMenuBar();
-    CreateSceneWindow();
+    CreateHierarchyWindow();
     CreateNodeWindow();
     CreateEditorSettingsDialog();
     CreateEditorPreferencesDialog();
@@ -44,8 +55,8 @@ void CreateUI()
     SubscribeToEvent("ScreenMode", "ResizeUI");
     SubscribeToEvent("MenuSelected", "HandleMenuSelected");
     SubscribeToEvent("KeyDown", "HandleKeyDown");
-    SubscribeToEvent("KeyUp", "UnhideUI");
-    SubscribeToEvent("MouseButtonUp", "UnhideUI");
+    SubscribeToEvent("KeyUp", "UnfadeUI");
+    SubscribeToEvent("MouseButtonUp", "UnfadeUI");
 }
 
 void ResizeUI()
@@ -70,9 +81,12 @@ void ResizeUI()
     Array<UIElement@> children = ui.root.GetChildren();
     for (uint i = 0; i < children.length; ++i)
     {
-        if (children[i].type == windowType)
+        if (children[i].type == WINDOW_TYPE)
             AdjustPosition(children[i]);
     }
+    
+    // Relayout root UI element
+    editorUIElement.SetSize(graphics.width, graphics.height);
 }
 
 void AdjustPosition(Window@ window)
@@ -108,79 +122,102 @@ void CreateMenuBar()
     ui.root.AddChild(uiMenuBar);
 
     {
-        Menu@ fileMenu = CreateMenu("File");
-        Window@ filePopup = fileMenu.popup;
-        filePopup.vars["Popup"] = "File";
-        filePopup.AddChild(CreateMenuItem("New scene", 'N', QUAL_SHIFT | QUAL_CTRL));
-        filePopup.AddChild(CreateMenuItem("Open scene...", 'O', QUAL_CTRL));
-        filePopup.AddChild(CreateMenuItem("Save scene", 'S', QUAL_CTRL));
-        filePopup.AddChild(CreateMenuItem("Save scene as...", 'S', QUAL_SHIFT | QUAL_CTRL));
-        filePopup.AddChild(CreateMenuDivider());
-
-        Menu@ loadNodeMenu = CreateMenuItem("Load node");
+        Menu@ menu = CreateMenu("File");
+        Window@ popup = menu.popup;
+        popup.vars["Popup"] = "File";
+        popup.AddChild(CreateMenuItem("New scene", 'N', QUAL_SHIFT | QUAL_CTRL));
+        popup.AddChild(CreateMenuItem("Open scene...", 'O', QUAL_CTRL));
+        popup.AddChild(CreateMenuItem("Save scene", 'S', QUAL_CTRL));
+        popup.AddChild(CreateMenuItem("Save scene as...", 'S', QUAL_SHIFT | QUAL_CTRL));
+        popup.AddChild(CreateMenuDivider());
+
+        Menu@ loadNodeMenu = CreateMenuItem("Load node", SHOW_POPUP_INDICATOR);
         Window@ loadNodePopup = CreatePopup(loadNodeMenu);
-        loadNodeMenu.popupOffset = IntVector2(loadNodeMenu.width, 0);
         loadNodePopup.AddChild(CreateMenuItem("As replicated..."));
         loadNodePopup.AddChild(CreateMenuItem("As local..."));
-        filePopup.AddChild(loadNodeMenu);
+        popup.AddChild(loadNodeMenu);
         
-        filePopup.AddChild(CreateMenuItem("Save node as..."));
-        filePopup.AddChild(CreateMenuDivider());
-        filePopup.AddChild(CreateMenuItem("Import model..."));
-        filePopup.AddChild(CreateMenuItem("Import scene..."));
-        filePopup.AddChild(CreateMenuItem("Run script..."));
-        filePopup.AddChild(CreateMenuDivider());
-        filePopup.AddChild(CreateMenuItem("Set resource path..."));
-        filePopup.AddChild(CreateMenuDivider());
-        filePopup.AddChild(CreateMenuItem("Exit"));
-        AdjustAccelIndent(filePopup);
-        uiMenuBar.AddChild(fileMenu);
+        popup.AddChild(CreateMenuItem("Save node as..."));
+        popup.AddChild(CreateMenuDivider());
+        popup.AddChild(CreateMenuItem("Import model..."));
+        popup.AddChild(CreateMenuItem("Import scene..."));
+        popup.AddChild(CreateMenuDivider());
+        popup.AddChild(CreateMenuItem("Run script..."));
+        popup.AddChild(CreateMenuItem("Set resource path..."));
+        popup.AddChild(CreateMenuDivider());
+        popup.AddChild(CreateMenuItem("Exit"));
+        AdjustAccelIndent(popup);
+        loadNodeMenu.popupOffset = IntVector2(loadNodeMenu.width, 0);
+        uiMenuBar.AddChild(menu);
+    }
+    
+    {
+        Menu@ menu = CreateMenu("UI-element");
+        Window@ popup = menu.popup;
+        popup.vars["Popup"] = "UI-element";
+        popup.AddChild(CreateMenuItem("New UI-element...", 'N', QUAL_ALT));
+        popup.AddChild(CreateMenuItem("Open UI-element...", 'O', QUAL_ALT));
+        popup.AddChild(CreateMenuDivider());
+        popup.AddChild(CreateMenuItem("Close UI-element", 'C', QUAL_ALT));
+        popup.AddChild(CreateMenuItem("Close all UI-elements"));
+        popup.AddChild(CreateMenuDivider());
+        popup.AddChild(CreateMenuItem("Save UI-element", 'S', QUAL_ALT));
+        popup.AddChild(CreateMenuItem("Save UI-element as..."));
+        popup.AddChild(CreateMenuDivider());
+        popup.AddChild(CreateMenuItem("Load child element..."));
+        popup.AddChild(CreateMenuItem("Save child element as..."));
+        popup.AddChild(CreateMenuDivider());
+        popup.AddChild(CreateMenuItem("Set default style..."));
+        AdjustAccelIndent(popup);
+        uiMenuBar.AddChild(menu);
     }
 
     {
-        Menu@ editMenu = CreateMenu("Edit");
-        Window@ editPopup = editMenu.popup;
-        editPopup.vars["Popup"] = "Edit";
-        editPopup.AddChild(CreateMenuItem("Cut", 'X', QUAL_CTRL));
-        editPopup.AddChild(CreateMenuItem("Copy", 'C', QUAL_CTRL));
-        editPopup.AddChild(CreateMenuItem("Paste", 'V', QUAL_CTRL));
-        editPopup.AddChild(CreateMenuItem("Delete", KEY_DELETE, QUAL_ANY));
-        editPopup.AddChild(CreateMenuItem("Select all", 'A', QUAL_CTRL));
-        editPopup.AddChild(CreateMenuDivider());
-        editPopup.AddChild(CreateMenuItem("Reset position"));
-        editPopup.AddChild(CreateMenuItem("Reset rotation"));
-        editPopup.AddChild(CreateMenuItem("Reset scale"));
-        editPopup.AddChild(CreateMenuItem("Enable/disable", 'E', QUAL_CTRL));
-        editPopup.AddChild(CreateMenuItem("Unparent", 'U', QUAL_CTRL));
-        editPopup.AddChild(CreateMenuDivider());
-        editPopup.AddChild(CreateMenuItem("Toggle update", 'P', QUAL_CTRL));
-        AdjustAccelIndent(editPopup);
-        uiMenuBar.AddChild(editMenu);
+        Menu@ menu = CreateMenu("Edit");
+        Window@ popup = menu.popup;
+        popup.vars["Popup"] = "Edit";
+        popup.AddChild(CreateMenuItem("Cut", 'X', QUAL_CTRL));
+        popup.AddChild(CreateMenuItem("Copy", 'C', QUAL_CTRL));
+        popup.AddChild(CreateMenuItem("Paste", 'V', QUAL_CTRL));
+        popup.AddChild(CreateMenuItem("Delete", KEY_DELETE, QUAL_ANY));
+        popup.AddChild(CreateMenuItem("Select all", 'A', QUAL_CTRL));
+        popup.AddChild(CreateMenuDivider());
+        popup.AddChild(CreateMenuItem("Reset position"));
+        popup.AddChild(CreateMenuItem("Reset rotation"));
+        popup.AddChild(CreateMenuItem("Reset scale"));
+        popup.AddChild(CreateMenuItem("Enable/disable", 'E', QUAL_CTRL));
+        popup.AddChild(CreateMenuItem("Unparent", 'U', QUAL_CTRL));
+        popup.AddChild(CreateMenuDivider());
+        popup.AddChild(CreateMenuItem("Toggle update", 'P', QUAL_CTRL));
+        AdjustAccelIndent(popup);
+        uiMenuBar.AddChild(menu);
     }
 
     {
-        Menu@ createMenu = CreateMenu("Create");
-        Window@ createPopup = createMenu.popup;
-        createPopup.vars["Popup"] = "Create";
-        createPopup.AddChild(CreateMenuItem("Box"));
-        createPopup.AddChild(CreateMenuItem("Cone"));
-        createPopup.AddChild(CreateMenuItem("Cylinder"));
-        createPopup.AddChild(CreateMenuItem("Plane"));
-        createPopup.AddChild(CreateMenuItem("Pyramid"));
-        createPopup.AddChild(CreateMenuItem("Sphere"));
-        uiMenuBar.AddChild(createMenu);
+        Menu@ menu = CreateMenu("Create");
+        Window@ popup = menu.popup;
+        popup.vars["Popup"] = "Create";
+        popup.AddChild(CreateMenuItem("Box"));
+        popup.AddChild(CreateMenuItem("Cone"));
+        popup.AddChild(CreateMenuItem("Cylinder"));
+        popup.AddChild(CreateMenuItem("Plane"));
+        popup.AddChild(CreateMenuItem("Pyramid"));
+        popup.AddChild(CreateMenuItem("Sphere"));
+        uiMenuBar.AddChild(menu);
     }
 
     {
-        Menu@ viewMenu = CreateMenu("View");
-        Window@ viewPopup = viewMenu.popup;
-        viewPopup.vars["Popup"] = "View";
-        viewPopup.AddChild(CreateMenuItem("Hierarchy", 'H', QUAL_CTRL));
-        viewPopup.AddChild(CreateMenuItem("Attribute inspector", 'I', QUAL_CTRL));
-        viewPopup.AddChild(CreateMenuItem("Editor settings"));
-        viewPopup.AddChild(CreateMenuItem("Editor preferences"));
-        AdjustAccelIndent(viewPopup);
-        uiMenuBar.AddChild(viewMenu);
+        Menu@ menu = CreateMenu("View");
+        Window@ popup = menu.popup;
+        popup.vars["Popup"] = "View";
+        popup.AddChild(CreateMenuItem("Hierarchy", 'H', QUAL_CTRL));
+        popup.AddChild(CreateMenuItem("Attribute inspector", 'I', QUAL_CTRL));
+        popup.AddChild(CreateMenuItem("Editor settings"));
+        popup.AddChild(CreateMenuItem("Editor preferences"));
+        popup.AddChild(CreateMenuDivider());
+        popup.AddChild(CreateMenuItem("Hide editor", KEY_F12, QUAL_ANY));
+        AdjustAccelIndent(popup);
+        uiMenuBar.AddChild(menu);
     }
 
     BorderImage@ spacer = BorderImage("MenuBarSpacer");
@@ -188,12 +225,174 @@ void CreateMenuBar()
     uiMenuBar.AddChild(spacer);
 }
 
+void HandleMenuSelected(StringHash eventType, VariantMap& eventData)
+{
+    Menu@ menu = eventData["Element"].GetUIElement();
+    if (menu is null)
+        return;
+
+    String action = menu.name;
+    if (action.empty)
+        return;
+
+    HandlePopup(menu);
+
+    if (uiFileSelector is null)
+    {
+        // File
+        if (action == "New scene")
+            ResetScene();
+        else if (action == "Open scene...")
+        {
+            CreateFileSelector("Open scene", "Open", "Cancel", uiScenePath, uiSceneFilters, uiSceneFilter);
+            SubscribeToEvent(uiFileSelector, "FileSelected", "HandleOpenSceneFile");
+        }
+        else if (action == "Save scene" && !editorScene.fileName.empty && editorScene.fileName != TEMP_SCENE_NAME)
+            SaveScene(editorScene.fileName);
+        else if (action == "Save scene as..." || action == "Save scene")
+        {
+            CreateFileSelector("Save scene as", "Save", "Cancel", uiScenePath, uiSceneFilters, uiSceneFilter);
+            uiFileSelector.fileName = GetFileNameAndExtension(editorScene.fileName);
+            SubscribeToEvent(uiFileSelector, "FileSelected", "HandleSaveSceneFile");
+        }
+        else if (action == "As replicated...")
+        {
+            instantiateMode = REPLICATED;
+            CreateFileSelector("Load node", "Load", "Cancel", uiNodePath, uiSceneFilters, uiNodeFilter);
+            SubscribeToEvent(uiFileSelector, "FileSelected", "HandleLoadNodeFile");
+        }
+        else if (action == "As local...")
+        {
+            instantiateMode = LOCAL;
+            CreateFileSelector("Load node", "Load", "Cancel", uiNodePath, uiSceneFilters, uiNodeFilter);
+            SubscribeToEvent(uiFileSelector, "FileSelected", "HandleLoadNodeFile");
+        }
+        else if (action == "Save node as...")
+        {
+            if (selectedNodes.length == 1 && selectedNodes[0] !is editorScene)
+            {
+                CreateFileSelector("Save node", "Save", "Cancel", uiNodePath, uiSceneFilters, uiNodeFilter);
+                uiFileSelector.fileName = GetFileNameAndExtension(instantiateFileName);
+                SubscribeToEvent(uiFileSelector, "FileSelected", "HandleSaveNodeFile");
+            }
+        }
+        else if (action == "Import model...")
+        {
+            CreateFileSelector("Import model", "Import", "Cancel", uiImportPath, uiAllFilters, uiImportFilter);
+            SubscribeToEvent(uiFileSelector, "FileSelected", "HandleImportModel");
+        }
+        else if (action == "Import scene...")
+        {
+            CreateFileSelector("Import scene", "Import", "Cancel", uiImportPath, uiAllFilters, uiImportFilter);
+            SubscribeToEvent(uiFileSelector, "FileSelected", "HandleImportScene");
+        }
+        else if (action == "Run script...")
+        {
+            CreateFileSelector("Run script", "Run", "Cancel", uiScriptPath, uiScriptFilters, uiScriptFilter);
+            SubscribeToEvent(uiFileSelector, "FileSelected", "HandleRunScript");
+        }
+        else if (action == "Set resource path...")
+        {
+            CreateFileSelector("Set resource path", "Set", "Cancel", sceneResourcePath, uiAllFilters, 0);
+            uiFileSelector.directoryMode = true;
+            SubscribeToEvent(uiFileSelector, "FileSelected", "HandleResourcePath");
+        }
+        // UI-element
+        else if (action == "New UI-element...")
+            NewUIElement();
+        else if (action == "Open UI-element...")
+        {
+            CreateFileSelector("Open UI-element", "Open", "Cancel", uiElementPath, uiElementFilters, uiElementFilter);
+            SubscribeToEvent(uiFileSelector, "FileSelected", "HandleOpenUIElementFile");
+        }
+        else if (action == "Close UI-element")
+            CloseUIElement();
+        else if (action == "Close all UI-elements")
+            CloseUIElement(true);
+        else if (action == "Save UI-element" && editUIElement !is null && !editUIElement.vars[FILENAME_VAR].GetString().empty)
+            SaveUIElement(editUIElement.vars[FILENAME_VAR].GetString());
+        else if (action == "Save UI-element as..." || action == "Save UI-element")
+        {
+            if (editUIElement !is null)
+            {
+                CreateFileSelector("Save UI-element as", "Save", "Cancel", uiElementPath, uiElementFilters, uiElementFilter);
+                uiFileSelector.fileName = GetFileNameAndExtension(editUIElement.vars[FILENAME_VAR].GetString());
+                SubscribeToEvent(uiFileSelector, "FileSelected", "HandleSaveUIElementFile");
+            }
+        }
+        else if (action == "Load child element...")
+        {
+            if (editUIElement !is null)
+            {
+                CreateFileSelector("Load child element", "Load", "Cancel", uiElementPath, uiElementFilters, uiElementFilter);
+                SubscribeToEvent(uiFileSelector, "FileSelected", "HandleLoadChildUIElementFile");
+            }
+        }
+        else if (action == "Save child element as...")
+        {
+            if (editUIElement !is null && !editUIElement.vars.Contains(FILENAME_VAR))
+            {
+                CreateFileSelector("Save child element", "Save", "Cancel", uiElementPath, uiElementFilters, uiElementFilter);
+                uiFileSelector.fileName = GetFileNameAndExtension(childElementFileName);
+                SubscribeToEvent(uiFileSelector, "FileSelected", "HandleSaveChildUIElementFile");
+            }
+        }
+        else if (action == "Set default style...")
+        {
+            CreateFileSelector("Set default style", "Set", "Cancel", uiElementPath, uiElementFilters, uiElementFilter);
+            SubscribeToEvent(uiFileSelector, "FileSelected", "HandleUIElementDefaultStyle");
+        }
+    }
+
+    // File
+    if (action == "Exit")
+        engine.Exit();
+    // Edit
+    else if (action == "Cut")
+        SceneCut();
+    else if (action == "Copy")
+        SceneCopy();
+    else if (action == "Paste")
+        ScenePaste();
+    else if (action == "Delete")
+        SceneDelete();
+    else if (action == "Reset position")
+        SceneResetPosition();
+    else if (action == "Reset rotation")
+        SceneResetRotation();
+    else if (action == "Reset scale")
+        SceneResetScale();
+    else if (action == "Enable/disable")
+        SceneToggleEnable();
+    else if (action == "Unparent")
+        SceneUnparent();
+    else if (action == "Select all")
+        SceneSelectAll();
+    else if (action == "Toggle update")
+        ToggleUpdate();
+    // Create
+    else if (action == "Box" || action == "Cone" || action == "Cylinder" || action == "Plane" ||
+        action == "Pyramid" || action == "Sphere")
+        CreateBuiltinObject(action);
+    // View
+    else if (action == "Hierarchy")
+        ShowHierarchyWindow();
+    else if (action == "Attribute inspector")
+        ShowNodeWindow();
+    else if (action == "Editor settings")
+        ShowEditorSettingsDialog();
+    else if (action == "Editor preferences")
+        ShowEditorPreferencesDialog();
+    else if (action == "Hide editor")
+        HideUI(!uiHidden);
+}
+
 Menu@ CreateMenuItem(const String&in title, int accelKey = 0, int accelQual = 0, int padding = 16)
 {
     Menu@ menu = Menu(title);
     menu.style = uiStyle;
     menu.SetLayout(LM_HORIZONTAL, 0, IntRect(padding, 2, padding, 2));
-    if (accelKey != 0)
+    if (accelKey > 0)
         menu.SetAccelerator(accelKey, accelQual);
 
     Text@ menuText = Text();
@@ -252,6 +451,33 @@ Text@ CreateAccelKeyText(int accelKey, int accelQual)
         text = "Del";
     else if (accelKey == KEY_SPACE)
         text = "Space";
+    // Cannot use range as the key constants below do not appear to be in sequence
+    else if (accelKey == KEY_F1)
+        text = "F1";
+    else if (accelKey == KEY_F2)
+        text = "F2";
+    else if (accelKey == KEY_F3)
+        text = "F3";
+    else if (accelKey == KEY_F4)
+        text = "F4";
+    else if (accelKey == KEY_F5)
+        text = "F5";
+    else if (accelKey == KEY_F6)
+        text = "F6";
+    else if (accelKey == KEY_F7)
+        text = "F7";
+    else if (accelKey == KEY_F8)
+        text = "F8";
+    else if (accelKey == KEY_F9)
+        text = "F9";
+    else if (accelKey == KEY_F10)
+        text = "F10";
+    else if (accelKey == KEY_F11)
+        text = "F11";
+    else if (accelKey == KEY_F12)
+        text = "F12";
+    else if (accelKey == SHOW_POPUP_INDICATOR)
+        text = ">";
     else
         text.AppendUTF8(accelKey);
     if (accelQual & QUAL_ALT > 0)
@@ -268,12 +494,12 @@ Text@ CreateAccelKeyText(int accelKey, int accelQual)
 void AdjustAccelIndent(Window@ popup)
 {
     // Find the maximum menu text width
-	Array<UIElement@> children = popup.GetChildren();
+    Array<UIElement@> children = popup.GetChildren();
     int maxWidth = 0;
     for (uint i = 0; i < children.length; ++i)
     {
         UIElement@ element = children[i];
-        if (element.type != menuType)	// Skip if not menu item
+        if (element.type != MENU_TYPE)	// Skip if not menu item
             continue;
         
         int width = element.children[0].width;
@@ -286,7 +512,7 @@ void AdjustAccelIndent(Window@ popup)
     for (uint i = 0; i < children.length; ++i)
     {
         UIElement@ element = children[i];
-        if (element.type != menuType)
+        if (element.type != MENU_TYPE)
             continue;
         
         element = element.children[0];
@@ -309,6 +535,7 @@ void CreateFileSelector(const String&in title, const String&in ok, const String&
     uiFileSelector.SetButtonTexts(ok, cancel);
     uiFileSelector.SetFilters(filters, initialFilter);
     uiFileSelector.window.vars["Popup"] = "FileSelector";
+    uiFileSelector.window.priority = 1000;	// Ensure when it is visible then it has the highest priority (in front of all others UI)
 
     CenterDialog(uiFileSelector.window);
 }
@@ -349,12 +576,11 @@ void CenterDialog(UIElement@ element)
 
 void UpdateWindowTitle()
 {
-    String sceneName = GetFileNameAndExtension(sceneFileName);
-    if (sceneName.empty)
+    String sceneName = GetFileNameAndExtension(editorScene.fileName);
+    if (sceneName.empty || sceneName == TEMP_SCENE_NAME)
         sceneName = "Untitled";
     if (sceneModified)
         sceneName += "*";
-
     graphics.windowTitle = "Urho3D editor - " + sceneName;
 }
 
@@ -381,116 +607,6 @@ void HandlePopup(Menu@ menu)
         menu.showPopup = false;
 }
 
-void HandleMenuSelected(StringHash eventType, VariantMap& eventData)
-{
-    Menu@ menu = eventData["Element"].GetUIElement();
-    if (menu is null)
-        return;
-
-    String action = menu.name;
-    if (action.empty)
-        return;
-
-    HandlePopup(menu);
-
-    if (uiFileSelector is null)
-    {
-        if (action == "New scene")
-            ResetScene();
-        else if (action == "Open scene...")
-        {
-            CreateFileSelector("Open scene", "Open", "Cancel", uiScenePath, uiSceneFilters, uiSceneFilter);
-            SubscribeToEvent(uiFileSelector, "FileSelected", "HandleOpenSceneFile");
-        }
-        else if (action == "Save scene" && !sceneFileName.empty)
-            SaveScene(sceneFileName);
-        else if (action == "Save scene as..." || action == "Save scene")
-        {
-            CreateFileSelector("Save scene as", "Save", "Cancel", uiScenePath, uiSceneFilters, uiSceneFilter);
-            uiFileSelector.fileName = GetFileNameAndExtension(sceneFileName);
-            SubscribeToEvent(uiFileSelector, "FileSelected", "HandleSaveSceneFile");
-        }
-        else if (action == "As replicated...")
-        {
-            instantiateMode = REPLICATED;
-            CreateFileSelector("Load node", "Load", "Cancel", uiNodePath, uiSceneFilters, uiNodeFilter);
-            SubscribeToEvent(uiFileSelector, "FileSelected", "HandleLoadNodeFile");
-        }
-        else if (action == "As local...")
-        {
-            instantiateMode = LOCAL;
-            CreateFileSelector("Load node", "Load", "Cancel", uiNodePath, uiSceneFilters, uiNodeFilter);
-            SubscribeToEvent(uiFileSelector, "FileSelected", "HandleLoadNodeFile");
-        }
-        else if (action == "Save node as...")
-        {
-            if (selectedNodes.length == 1 && selectedNodes[0] !is editorScene)
-            {
-                CreateFileSelector("Save node", "Save", "Cancel", uiNodePath, uiSceneFilters, uiNodeFilter);
-                uiFileSelector.fileName = GetFileNameAndExtension(instantiateFileName);
-                SubscribeToEvent(uiFileSelector, "FileSelected", "HandleSaveNodeFile");
-            }
-        }
-        else if (action == "Import model...")
-        {
-            CreateFileSelector("Import model", "Import", "Cancel", uiImportPath, uiAllFilters, uiImportFilter);
-            SubscribeToEvent(uiFileSelector, "FileSelected", "HandleImportModel");
-        }
-        else if (action == "Import scene...")
-        {
-            CreateFileSelector("Import scene", "Import", "Cancel", uiImportPath, uiAllFilters, uiImportFilter);
-            SubscribeToEvent(uiFileSelector, "FileSelected", "HandleImportScene");
-        }
-        else if (action == "Run script...")
-        {
-            CreateFileSelector("Run script", "Run", "Cancel", uiScriptPath, uiScriptFilters, uiScriptFilter);
-            SubscribeToEvent(uiFileSelector, "FileSelected", "HandleRunScript");
-        }
-        else if (action == "Set resource path...")
-        {
-            CreateFileSelector("Set resource path", "Set", "Cancel", sceneResourcePath, uiAllFilters, 0);
-            uiFileSelector.directoryMode = true;
-            SubscribeToEvent(uiFileSelector, "FileSelected", "HandleResourcePath");
-        }
-    }
-
-    if (action == "Hierarchy")
-        ShowSceneWindow();
-    else if (action == "Attribute inspector")
-        ShowNodeWindow();
-    else if (action == "Editor settings")
-        ShowEditorSettingsDialog();
-    else if (action == "Editor preferences")
-        ShowEditorPreferencesDialog();
-    else if (action == "Cut")
-        SceneCut();
-    else if (action == "Copy")
-        SceneCopy();
-    else if (action == "Paste")
-        ScenePaste();
-    else if (action == "Delete")
-        SceneDelete();
-    else if (action == "Reset position")
-        SceneResetPosition();
-    else if (action == "Reset rotation")
-        SceneResetRotation();
-    else if (action == "Reset scale")
-        SceneResetScale();
-    else if (action == "Enable/disable")
-        SceneToggleEnable();
-    else if (action == "Unparent")
-        SceneUnparent();
-    else if (action == "Select all")
-        SceneSelectAll();
-    else if (action == "Toggle update")
-        ToggleUpdate();
-    else if (action == "Box" || action == "Cone" || action == "Cylinder" || action == "Plane" ||
-        action == "Pyramid" || action == "Sphere")
-        CreateBuiltinObject(action);
-    else if (action == "Exit")
-        engine.Exit();
-}
-
 String ExtractFileName(VariantMap& eventData)
 {
     String fileName;
@@ -568,6 +684,36 @@ void HandleResourcePath(StringHash eventType, VariantMap& eventData)
     SetResourcePath(ExtractFileName(eventData), false);
 }
 
+void HandleOpenUIElementFile(StringHash eventType, VariantMap& eventData)
+{
+    CloseFileSelector(uiElementFilter, uiElementPath);
+    OpenUIElement(ExtractFileName(eventData));
+}
+
+void HandleSaveUIElementFile(StringHash eventType, VariantMap& eventData)
+{
+    CloseFileSelector(uiElementFilter, uiElementPath);
+    SaveUIElement(ExtractFileName(eventData));
+}
+
+void HandleLoadChildUIElementFile(StringHash eventType, VariantMap& eventData)
+{
+    CloseFileSelector(uiElementFilter, uiElementPath);
+    LoadChildUIElement(ExtractFileName(eventData));
+}
+
+void HandleSaveChildUIElementFile(StringHash eventType, VariantMap& eventData)
+{
+    CloseFileSelector(uiElementFilter, uiElementPath);
+    SaveChildUIElement(ExtractFileName(eventData));
+}
+
+void HandleUIElementDefaultStyle(StringHash eventType, VariantMap& eventData)
+{
+    CloseFileSelector(uiElementFilter, uiElementPath);
+    SetUIElementDefaultStyle(ExtractFileName(eventData));
+}
+
 void HandleKeyDown(StringHash eventType, VariantMap& eventData)
 {
     int key = eventData["Key"].GetInt();
@@ -578,11 +724,13 @@ void HandleKeyDown(StringHash eventType, VariantMap& eventData)
     if (key == KEY_ESC)
     {
         UIElement@ front = ui.frontElement;
-        if (console.visible)
+        if (uiHidden)
+            UnhideUI();
+        else if (console.visible)
             console.visible = false;
         else if (uiFileSelector !is null && front is uiFileSelector.window)
             CloseFileSelector();
-        else if (front is settingsDialog)
+        else if (front is settingsDialog || front is preferencesDialog)
         {
             ui.focusElement = null;
             front.visible = false;
@@ -634,6 +782,26 @@ void HandleKeyDown(StringHash eventType, VariantMap& eventData)
     }
 }
 
+void UnfadeUI()
+{
+    FadeUI(false);
+}
+
+void FadeUI(bool fade = true)
+{
+    if (uiHidden || uiFaded == fade)
+        return;
+    
+    float opacity = (uiFaded = fade) ? uiMinOpacity : uiMaxOpacity;
+    Array<UIElement@> children = ui.root.GetChildren();
+    for (uint i = 0; i < children.length; ++i)
+    {
+        // Texts, popup windows, and editorUIElement are excluded
+        if (children[i].type != TEXT_TYPE && children[i] !is editorUIElement && !children[i].vars.Contains("Popup"))
+            children[i].opacity = opacity;
+    }
+}
+
 void UnhideUI()
 {
     HideUI(false);
@@ -644,13 +812,24 @@ void HideUI(bool hide = true)
     if (uiHidden == hide)
         return;
     
-    float opacity = (uiHidden = hide) ? uiMinOpacity : uiMaxOpacity;
+    bool visible = !(uiHidden = hide);
     Array<UIElement@> children = ui.root.GetChildren();
     for (uint i = 0; i < children.length; ++i)
     {
-        // Texts and popup windows are excluded
-        if (children[i].type != textType && !children[i].vars.Contains("Popup"))
-            children[i].opacity = opacity;
+        // Cursor, FileSelector, and editorUIElement are excluded
+        if (children[i].type != CURSOR_TYPE && children[i] !is editorUIElement && (uiFileSelector is null || children[i] !is uiFileSelector.window))
+        {
+            if (visible)
+            {
+                if (!children[i].visible)
+                    children[i].visible = children[i].vars["HideUI"].GetBool();
+            }
+            else
+            {
+                children[i].vars["HideUI"] = children[i].visible;
+                children[i].visible = false;
+            }
+        }
     }
 }
 
@@ -717,4 +896,4 @@ void UpdateDirtyUI()
     // Perform some event-triggered updates latently in case a large hierarchy was changed
     if (attributesDirty)
         UpdateAttributes(false);
-}
+}

+ 5 - 5
Bin/Data/Scripts/Editor/EditorView.as

@@ -174,22 +174,22 @@ void UpdateView(float timeStep)
         if (input.keyDown['W'] || input.keyDown[KEY_UP])
         {
             cameraNode.TranslateRelative(Vector3(0, 0, cameraBaseSpeed) * timeStep * speedMultiplier);
-            HideUI();
+            FadeUI();
         }
         if (input.keyDown['S'] || input.keyDown[KEY_DOWN])
         {
             cameraNode.TranslateRelative(Vector3(0, 0, -cameraBaseSpeed) * timeStep * speedMultiplier);
-            HideUI();
+            FadeUI();
         }
         if (input.keyDown['A'] || input.keyDown[KEY_LEFT])
         {
             cameraNode.TranslateRelative(Vector3(-cameraBaseSpeed, 0, 0) * timeStep * speedMultiplier);
-            HideUI();
+            FadeUI();
         }
         if (input.keyDown['D'] || input.keyDown[KEY_RIGHT])
         {
             cameraNode.TranslateRelative(Vector3(cameraBaseSpeed, 0, 0) * timeStep * speedMultiplier);
-            HideUI();
+            FadeUI();
         }
     }
 
@@ -207,7 +207,7 @@ void UpdateView(float timeStep)
                 cameraPitch = 90.0;
 
             cameraNode.rotation = Quaternion(cameraPitch, cameraYaw, 0);
-            HideUI();
+            FadeUI();
         }
     }
 

BIN
Bin/Data/Textures/EditorIcons.png


+ 68 - 0
Bin/Data/UI/EditorIcons.xml

@@ -107,4 +107,72 @@
         <attribute name="Texture" value="Texture2D;Textures/EditorIcons.png" />
         <attribute name="Image Rect" value="240 0 254 14" />
     </element>
+    <element type="RootUIElement">
+        <attribute name="Texture" value="Texture2D;Textures/EditorIcons.png" />
+        <attribute name="Image Rect" value="0 32 14 46" />
+    </element>
+    <element type="UIElement">
+        <attribute name="Texture" value="Texture2D;Textures/EditorIcons.png" />
+        <attribute name="Image Rect" value="16 32 30 46" />
+    </element>
+    <element type="HierarchyContainer">
+        <attribute name="Texture" value="Texture2D;Textures/EditorIcons.png" />
+        <attribute name="Image Rect" value="16 32 30 46" />
+    </element>
+    <element type="BorderImage">
+        <attribute name="Texture" value="Texture2D;Textures/EditorIcons.png" />
+        <attribute name="Image Rect" value="32 32 46 46" />
+    </element>
+    <element type="Button">
+        <attribute name="Texture" value="Texture2D;Textures/EditorIcons.png" />
+        <attribute name="Image Rect" value="32 32 46 46" />
+    </element>
+    <element type="Menu">
+        <attribute name="Texture" value="Texture2D;Textures/EditorIcons.png" />
+        <attribute name="Image Rect" value="32 32 46 46" />
+    </element>
+    <element type="DropDownList">
+        <attribute name="Texture" value="Texture2D;Textures/EditorIcons.png" />
+        <attribute name="Image Rect" value="32 32 46 46" />
+    </element>
+    <element type="CheckBox">
+        <attribute name="Texture" value="Texture2D;Textures/EditorIcons.png" />
+        <attribute name="Image Rect" value="32 32 46 46" />
+    </element>
+    <element type="Cursor">
+        <attribute name="Texture" value="Texture2D;Textures/EditorIcons.png" />
+        <attribute name="Image Rect" value="32 32 46 46" />
+    </element>
+    <element type="LineEdit">
+        <attribute name="Texture" value="Texture2D;Textures/EditorIcons.png" />
+        <attribute name="Image Rect" value="32 32 46 46" />
+    </element>
+    <element type="Slider">
+        <attribute name="Texture" value="Texture2D;Textures/EditorIcons.png" />
+        <attribute name="Image Rect" value="32 32 46 46" />
+    </element>
+    <element type="Window">
+        <attribute name="Texture" value="Texture2D;Textures/EditorIcons.png" />
+        <attribute name="Image Rect" value="32 32 46 46" />
+    </element>
+    <element type="ScrollBar">
+        <attribute name="Texture" value="Texture2D;Textures/EditorIcons.png" />
+        <attribute name="Image Rect" value="32 32 46 46" />
+    </element>
+    <element type="ScrollView">
+        <attribute name="Texture" value="Texture2D;Textures/EditorIcons.png" />
+        <attribute name="Image Rect" value="32 32 46 46" />
+    </element>
+    <element type="ListView">
+        <attribute name="Texture" value="Texture2D;Textures/EditorIcons.png" />
+        <attribute name="Image Rect" value="32 32 46 46" />
+    </element>
+    <element type="Sprite">
+        <attribute name="Texture" value="Texture2D;Textures/EditorIcons.png" />
+        <attribute name="Image Rect" value="32 32 46 46" />
+    </element>
+    <element type="Text">
+        <attribute name="Texture" value="Texture2D;Textures/EditorIcons.png" />
+        <attribute name="Image Rect" value="32 32 46 46" />
+    </element>
 </elements>

+ 15 - 0
Docs/ScriptAPI.dox

@@ -298,6 +298,7 @@ Methods:<br>
 - int Compare(const String&, bool arg1 = true) const
 - bool Contains(const String&) const
 - bool Contains(uint8) const
+- void Clear()
 - String[]@ Split(uint8) const
 - void Join(String[]&, const String&)
 - bool ToBool() const
@@ -3194,6 +3195,7 @@ Properties:<br>
 - bool colorGradient (readonly)
 - FocusMode focusMode
 - uint dragDropMode
+- TraversalMode traversalMode
 - XMLFile@ defaultStyle
 - LayoutMode layoutMode
 - int layoutSpacing
@@ -3301,6 +3303,7 @@ Properties:<br>
 - bool colorGradient (readonly)
 - FocusMode focusMode
 - uint dragDropMode
+- TraversalMode traversalMode
 - XMLFile@ defaultStyle
 - LayoutMode layoutMode
 - int layoutSpacing
@@ -3500,6 +3503,7 @@ Properties:<br>
 - bool colorGradient (readonly)
 - FocusMode focusMode
 - uint dragDropMode
+- TraversalMode traversalMode
 - XMLFile@ defaultStyle
 - LayoutMode layoutMode
 - int layoutSpacing
@@ -3618,6 +3622,7 @@ Properties:<br>
 - bool colorGradient (readonly)
 - FocusMode focusMode
 - uint dragDropMode
+- TraversalMode traversalMode
 - XMLFile@ defaultStyle
 - LayoutMode layoutMode
 - int layoutSpacing
@@ -3734,6 +3739,7 @@ Properties:<br>
 - bool colorGradient (readonly)
 - FocusMode focusMode
 - uint dragDropMode
+- TraversalMode traversalMode
 - XMLFile@ defaultStyle
 - LayoutMode layoutMode
 - int layoutSpacing
@@ -3849,6 +3855,7 @@ Properties:<br>
 - bool colorGradient (readonly)
 - FocusMode focusMode
 - uint dragDropMode
+- TraversalMode traversalMode
 - XMLFile@ defaultStyle
 - LayoutMode layoutMode
 - int layoutSpacing
@@ -3968,6 +3975,7 @@ Properties:<br>
 - bool colorGradient (readonly)
 - FocusMode focusMode
 - uint dragDropMode
+- TraversalMode traversalMode
 - XMLFile@ defaultStyle
 - LayoutMode layoutMode
 - int layoutSpacing
@@ -4084,6 +4092,7 @@ Properties:<br>
 - bool colorGradient (readonly)
 - FocusMode focusMode
 - uint dragDropMode
+- TraversalMode traversalMode
 - XMLFile@ defaultStyle
 - LayoutMode layoutMode
 - int layoutSpacing
@@ -4216,6 +4225,7 @@ Properties:<br>
 - bool colorGradient (readonly)
 - FocusMode focusMode
 - uint dragDropMode
+- TraversalMode traversalMode
 - XMLFile@ defaultStyle
 - LayoutMode layoutMode
 - int layoutSpacing
@@ -4345,6 +4355,7 @@ Properties:<br>
 - bool colorGradient (readonly)
 - FocusMode focusMode
 - uint dragDropMode
+- TraversalMode traversalMode
 - XMLFile@ defaultStyle
 - LayoutMode layoutMode
 - int layoutSpacing
@@ -4464,6 +4475,7 @@ Properties:<br>
 - bool colorGradient (readonly)
 - FocusMode focusMode
 - uint dragDropMode
+- TraversalMode traversalMode
 - XMLFile@ defaultStyle
 - LayoutMode layoutMode
 - int layoutSpacing
@@ -4593,6 +4605,7 @@ Properties:<br>
 - bool colorGradient (readonly)
 - FocusMode focusMode
 - uint dragDropMode
+- TraversalMode traversalMode
 - XMLFile@ defaultStyle
 - LayoutMode layoutMode
 - int layoutSpacing
@@ -4726,6 +4739,7 @@ Properties:<br>
 - bool colorGradient (readonly)
 - FocusMode focusMode
 - uint dragDropMode
+- TraversalMode traversalMode
 - XMLFile@ defaultStyle
 - LayoutMode layoutMode
 - int layoutSpacing
@@ -4853,6 +4867,7 @@ Properties:<br>
 - bool colorGradient (readonly)
 - FocusMode focusMode
 - uint dragDropMode
+- TraversalMode traversalMode
 - XMLFile@ defaultStyle
 - LayoutMode layoutMode
 - int layoutSpacing

+ 2 - 0
Engine/Engine/APITemplates.h

@@ -944,6 +944,8 @@ template <class T> void RegisterUIElement(asIScriptEngine* engine, const char* c
         engine->RegisterObjectMethod(className, "FocusMode get_focusMode() const", asMETHOD(T, GetFocusMode), asCALL_THISCALL);
         engine->RegisterObjectMethod(className, "void set_dragDropMode(uint)", asMETHOD(T, SetDragDropMode), asCALL_THISCALL);
         engine->RegisterObjectMethod(className, "uint get_dragDropMode() const", asMETHOD(T, GetDragDropMode), asCALL_THISCALL);
+        engine->RegisterObjectMethod(className, "void set_traversalMode(TraversalMode)", asMETHOD(T, SetTraversalMode), asCALL_THISCALL);
+        engine->RegisterObjectMethod(className, "TraversalMode get_traversalMode() const", asMETHOD(T, GetTraversalMode), asCALL_THISCALL);
     }
     engine->RegisterObjectMethod(className, "void set_defaultStyle(XMLFile@+)", asMETHOD(T, SetDefaultStyle), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "XMLFile@+ get_defaultStyle()", asMETHOD(T, GetDefaultStyle), asCALL_THISCALL);

+ 4 - 0
Engine/Engine/UIAPI.cpp

@@ -88,6 +88,10 @@ static void RegisterUIElement(asIScriptEngine* engine)
     engine->RegisterEnumValue("LayoutMode", "LM_HORIZONTAL", LM_HORIZONTAL);
     engine->RegisterEnumValue("LayoutMode", "LM_VERTICAL", LM_VERTICAL);
     
+    engine->RegisterEnum("TraversalMode");
+    engine->RegisterEnumValue("TraversalMode", "TM_BREADTH_FIRST", TM_BREADTH_FIRST);
+    engine->RegisterEnumValue("TraversalMode", "TM_DEPTH_FIRST", TM_DEPTH_FIRST);
+
     engine->RegisterGlobalProperty("const uint DD_DISABLED", (void*)&DD_DISABLED);
     engine->RegisterGlobalProperty("const uint DD_SOURCE", (void*)&DD_SOURCE);
     engine->RegisterGlobalProperty("const uint DD_TARGET", (void*)&DD_TARGET);

+ 29 - 2
Engine/Scene/Scene.cpp

@@ -117,6 +117,8 @@ bool Scene::Load(Deserializer& source)
     
     LOGINFO("Loading scene from " + source.GetName());
     
+    Clear();
+
     // Load the whole scene, then perform post-load if successfully loaded
     if (Node::Load(source))
     {
@@ -142,7 +144,13 @@ bool Scene::Save(Serializer& dest)
     if (ptr)
         LOGINFO("Saving scene to " + ptr->GetName());
     
-    return Node::Save(dest);
+    if (Node::Save(dest))
+    {
+    	FinishSaving(&dest);
+    	return true;
+    }
+    else
+    	return false;
 }
 
 bool Scene::LoadXML(const XMLElement& source)
@@ -183,6 +191,8 @@ bool Scene::LoadXML(Deserializer& source)
     
     LOGINFO("Loading scene from " + source.GetName());
     
+    Clear();
+
     if (Node::LoadXML(xml->GetRoot()))
     {
         FinishLoading(&source);
@@ -205,7 +215,13 @@ bool Scene::SaveXML(Serializer& dest)
     if (ptr)
         LOGINFO("Saving scene to " + ptr->GetName());
     
-    return xml->Save(dest);
+    if (xml->Save(dest))
+    {
+    	FinishSaving(&dest);
+    	return true;
+    }
+    else
+    	return false;
 }
 
 bool Scene::LoadAsync(File* file)
@@ -362,6 +378,7 @@ void Scene::Clear()
     StopAsyncLoading();
     RemoveAllChildren();
     RemoveAllComponents();
+    SetName(String::EMPTY);
     fileName_.Clear();
     checksum_ = 0;
     replicatedNodeID_ = FIRST_REPLICATED_ID;
@@ -878,6 +895,16 @@ void Scene::FinishLoading(Deserializer* source)
     }
 }
 
+void Scene::FinishSaving(Serializer* dest)
+{
+    Deserializer* ptr = dynamic_cast<Deserializer*>(dest);
+    if (ptr)
+    {
+        fileName_ = ptr->GetName();
+        checksum_ = ptr->GetChecksum();
+    }
+}
+
 void RegisterSceneLibrary(Context* context)
 {
     Node::RegisterObject(context);

+ 2 - 0
Engine/Scene/Scene.h

@@ -191,6 +191,8 @@ private:
     void FinishAsyncLoading();
     /// Finish loading. Sets the scene filename and checksum.
     void FinishLoading(Deserializer* source);
+    /// Finish saving. Sets the scene filename and checksum.
+    void FinishSaving(Serializer* dest);
     
     /// Replicated scene nodes by ID.
     HashMap<unsigned, Node*> replicatedNodes_;

+ 1 - 0
Engine/Script/Addons.cpp

@@ -1492,6 +1492,7 @@ void RegisterString(asIScriptEngine *engine)
     engine->RegisterObjectMethod("String", "int Compare(const String&in, bool caseSensitive = true) const", asMETHODPR(String, Compare, (const String&, bool) const, int), asCALL_THISCALL);
     engine->RegisterObjectMethod("String", "bool Contains(const String&in) const", asMETHODPR(String, Contains, (const String&) const, bool), asCALL_THISCALL);
     engine->RegisterObjectMethod("String", "bool Contains(uint8) const", asMETHODPR(String, Contains, (char) const, bool), asCALL_THISCALL);
+    engine->RegisterObjectMethod("String", "void Clear()", asMETHOD(String, Clear), asCALL_THISCALL);
     
     // Register automatic conversion functions for convenience
     engine->RegisterObjectBehaviour("String", asBEHAVE_CONSTRUCT, "void f(int)", asFUNCTION(ConstructStringInt), asCALL_CDECL_OBJLAST);

+ 14 - 14
Engine/UI/ListView.cpp

@@ -324,24 +324,24 @@ void ListView::InsertItem(unsigned index, UIElement* item, UIElement* parentItem
         {
             baseIndent = parentItem->GetIndent();
             SetItemHierarchyParent(parentItem, true);
-        }
-        item->SetIndent(baseIndent + 1);
-        SetItemExpanded(item, item->IsVisible());
 
-        // Adjust the index to ensure it is within the children index limit of the parent item
-        unsigned indexLimit = FindItem(parentItem);
-        if (index <= indexLimit)
-            index = indexLimit + 1;
-        else
-        {
-            while (++indexLimit < numItems)
+            // Adjust the index to ensure it is within the children index limit of the parent item
+            unsigned indexLimit = FindItem(parentItem);
+            if (index <= indexLimit)
+                index = indexLimit + 1;
+            else
             {
-                if (contentElement_->GetChild(indexLimit)->GetIndent() <= baseIndent)
-                    break;
+                while (++indexLimit < numItems)
+                {
+                    if (contentElement_->GetChild(indexLimit)->GetIndent() <= baseIndent)
+                        break;
+                }
+                if (index > indexLimit)
+                    index = indexLimit;
             }
-            if (index > indexLimit)
-                index = indexLimit;
         }
+        item->SetIndent(baseIndent + 1);
+        SetItemExpanded(item, item->IsVisible());
 
         // Use the 'overrided' version to insert the child item
         static_cast<HierarchyContainer*>(contentElement_.Get())->InsertChild(index, item);

+ 3 - 1
Engine/UI/UI.cpp

@@ -70,6 +70,8 @@ UI::UI(Context* context) :
     nonFocusedMouseWheel_(true)     // Default Mac OS X and Linux behaviour
     #endif
 {
+	rootElement_->SetTraversalMode(TM_DEPTH_FIRST);
+
     SubscribeToEvent(E_SCREENMODE, HANDLER(UI, HandleScreenMode));
     SubscribeToEvent(E_MOUSEBUTTONDOWN, HANDLER(UI, HandleMouseButtonDown));
     SubscribeToEvent(E_MOUSEBUTTONUP, HANDLER(UI, HandleMouseButtonUp));
@@ -513,7 +515,7 @@ void UI::GetBatches(UIElement* element, IntRect currentScissor)
     // For non-root elements draw all children of same priority before recursing into their children: assumption is that they have
     // same renderstate
     Vector<SharedPtr<UIElement> >::ConstIterator i = children.Begin();
-    if (element != rootElement_)
+    if (element->GetTraversalMode() == TM_BREADTH_FIRST)
     {
         Vector<SharedPtr<UIElement> >::ConstIterator j = i;
         while (i != children.End())

+ 9 - 3
Engine/UI/UIElement.cpp

@@ -143,7 +143,8 @@ UIElement::UIElement(Context* context) :
     opacityDirty_(true),
     derivedColorDirty_(true),
     sortOrderDirty_(false),
-    colorGradient_(false)
+    colorGradient_(false),
+    traversalMode_(TM_BREADTH_FIRST)
 {
 }
 
@@ -790,14 +791,14 @@ void UIElement::SetIndent(int indent)
 {
     indent_ = indent;
     if (parent_)
-    	parent_->UpdateLayout();
+        parent_->UpdateLayout();
 }
 
 void UIElement::SetIndentSpacing(int indentSpacing)
 {
     indentSpacing_ = Max(indentSpacing, 0);
     if (parent_)
-    	parent_->UpdateLayout();
+        parent_->UpdateLayout();
 }
 
 void UIElement::UpdateLayout()
@@ -1123,6 +1124,11 @@ void UIElement::SetInternal(bool enable)
     internal_ = enable;
 }
 
+void UIElement::SetTraversalMode(TraversalMode traversalMode)
+{
+    traversalMode_ = traversalMode;
+}
+
 float UIElement::GetDerivedOpacity() const
 {
     if (!useDerivedOpacity_)

+ 16 - 0
Engine/UI/UIElement.h

@@ -87,6 +87,15 @@ enum LayoutMode
     LM_VERTICAL
 };
 
+/// Traversal mode.
+enum TraversalMode
+{
+    /// Traverse thru children having same priority first and recurse into their children before traversing children having higher priority.
+    TM_BREADTH_FIRST = 0,
+    /// Traverse thru each child and its children immediately after in sequence.
+    TM_DEPTH_FIRST
+};
+
 /// Drag and drop disabled.
 static const unsigned DD_DISABLED = 0x0;
 /// Drag and drop source flag.
@@ -283,6 +292,9 @@ public:
     void SetVar(ShortStringHash key, const Variant& value);
     /// Mark as internally (programmatically) created. Used when an element composes itself out of child elements.
     void SetInternal(bool enable);
+    /// Set traversal mode. The default traversal mode is TM_BREADTH_FIRST for non-root element. Root element should be set to TM_DEPTH_FIRST to avoid artifacts during rendering.
+    void SetTraversalMode(TraversalMode traversalMode);
+
     /// Template version of creating a child element.
     template <class T> T* CreateChild(const String& name = String::EMPTY);
     
@@ -415,6 +427,8 @@ public:
         currentScissor);
     /// Get color attribute. Uses just the top-left color.
     const Color& GetColorAttr() const { return color_[0]; }
+    /// Get traversal mode.
+    TraversalMode GetTraversalMode() const { return traversalMode_; }
 
 protected:
     /// Mark screen position as needing an update.
@@ -521,6 +535,8 @@ private:
     bool colorGradient_;
     /// Default style file.
     SharedPtr<XMLFile> defaultStyle_;
+    /// Traversal mode.
+    TraversalMode traversalMode_;
 };
 
 template <class T> T* UIElement::CreateChild(const String& name) { return static_cast<T*>(CreateChild(T::GetTypeStatic(), name)); }