Browse Source

Enhanced UIElement to be able to set a default style file for later reference by children elements, added horizontal indentation feature, and added new E_LAYOUTUPDATED event. Enhanced ListView to better support hierarchy mode and added feature to show 'expansion triangle' in hierarchy mode. Fixed a bug in ScrollView to update view size correctly when resizing content element. Fixed a bug in Log's WriteRaw() method so it sends error message to stderr even in quite mode. Refactored Editor to take advantage of the enhancement.

Wei Tjong Yao 12 years ago
parent
commit
d7f6b4d0b6

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

@@ -32,7 +32,7 @@ void CreateNodeWindow()
     InitResourcePicker();
     InitVectorStructs();
 
-    nodeWindow = ui.LoadLayout(cache.GetResource("XMLFile", "UI/EditorNodeWindow.xml"), uiStyle);
+    nodeWindow = ui.LoadLayout(cache.GetResource("XMLFile", "UI/EditorNodeWindow.xml"));
     componentXMLResource = cache.GetResource("XMLFile", "UI/EditorComponent.xml");
     componentParentContainer = nodeWindow.GetChild("ComponentParentContainer", true);
     AddComponentContainer();
@@ -133,7 +133,7 @@ void UpdateAttributes(bool fullUpdate)
                     AddComponentContainer();
                 
                 Text@ componentTitle = GetComponentContainer(j).GetChild("ComponentTitle");
-                componentTitle.text = GetComponentTitle(editComponents[j * numEditableComponents], 0) + multiplierText;
+                componentTitle.text = GetComponentTitle(editComponents[j * numEditableComponents]) + multiplierText;
                 
                 Array<Serializable@> components;
                 for (uint i = 0; i < numEditableComponents; ++i)

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

@@ -18,7 +18,7 @@ void CreateEditorPreferencesDialog()
     if (preferencesDialog !is null)
         return;
     
-    preferencesDialog = ui.LoadLayout(cache.GetResource("XMLFile", "UI/EditorPreferencesDialog.xml"), uiStyle);
+    preferencesDialog = ui.LoadLayout(cache.GetResource("XMLFile", "UI/EditorPreferencesDialog.xml"));
     ui.root.AddChild(preferencesDialog);
     preferencesDialog.opacity = uiMaxOpacity;
     CenterDialog(preferencesDialog);

+ 6 - 12
Bin/Data/Scripts/Editor/EditorScene.as

@@ -277,10 +277,9 @@ bool SceneDelete()
         return false;
 
     BeginSelectionModify();
-    ListView@ list = sceneWindow.GetChild("NodeList", true);
     
     // Clear the selection now to prevent repopulation of selectedNodes and selectedComponents combo
-    list.ClearSelection();
+    hierarchyList.ClearSelection();
 
     // Remove nodes
     for (uint i = 0; i < selectedNodes.length; ++i)
@@ -298,7 +297,7 @@ bool SceneDelete()
 
         // If deleting only one node, select the next item in the same index
         if (selectedNodes.length == 1 && selectedComponents.empty)
-            list.selection = nodeIndex;
+            hierarchyList.selection = nodeIndex;
     }
 
     // Then remove components, if they still remain
@@ -326,7 +325,7 @@ bool SceneDelete()
 
         // If deleting only one component, select the next item in the same index
         if (selectedComponents.length == 1 && selectedNodes.empty)
-            list.selection = index;
+            hierarchyList.selection = index;
     }
 
     EndSelectionModify();
@@ -350,7 +349,6 @@ bool SceneCopy()
     if (!selectedNodes.empty && !selectedComponents.empty)
         return false;
 
-    ListView@ list = sceneWindow.GetChild("NodeList", true);
     copyBuffer.Clear();
 
     // Copy components
@@ -393,7 +391,6 @@ bool ScenePaste()
     if (editNode is null || !CheckSceneWindowFocus() || copyBuffer.empty)
         return false;
 
-    ListView@ list = sceneWindow.GetChild("NodeList", true);
     bool pasteComponents = false;
 
     for (uint i = 0; i < copyBuffer.length; ++i)
@@ -444,8 +441,6 @@ void SceneUnparent()
     if (!CheckSceneWindowFocus() || !selectedComponents.empty || selectedNodes.empty)
         return;
 
-    ListView@ list = sceneWindow.GetChild("NodeList", true);
-
     // Parent selected nodes to root
     for (uint i = 0; i < selectedNodes.length; ++i)
     {
@@ -493,11 +488,10 @@ void SceneResetScale()
 
 void SceneSelectAll()
 {
-    ListView@ list = sceneWindow.GetChild("NodeList", true);
-    if (!list.selections.empty)
+    if (!hierarchyList.selections.empty)
     {
         BeginSelectionModify();
-        list.ClearSelection();
+        hierarchyList.ClearSelection();
         EndSelectionModify();
     }
     else
@@ -507,7 +501,7 @@ void SceneSelectAll()
         Array<uint> indices;
         for (uint i = 0; i < rootLevelNodes.length; ++i)
             indices.Push(GetNodeListIndex(rootLevelNodes[i]));
-        list.SetSelections(indices);
+        hierarchyList.SetSelections(indices);
         EndSelectionModify();
     }
 }

+ 85 - 153
Bin/Data/Scripts/Editor/EditorSceneWindow.as

@@ -3,9 +3,10 @@
 const int ITEM_NONE = 0;
 const int ITEM_NODE = 1;
 const int ITEM_COMPONENT = 2;
-const uint NO_ITEM = 0xffffffff;
+const uint NO_ITEM = M_MAX_UNSIGNED;
 
 Window@ sceneWindow;
+ListView@ hierarchyList;
 
 bool suppressSceneChanges = false;
 
@@ -14,7 +15,8 @@ void CreateSceneWindow()
     if (sceneWindow !is null)
         return;
 
-    @sceneWindow = ui.LoadLayout(cache.GetResource("XMLFile", "UI/EditorSceneWindow.xml"), uiStyle);
+    sceneWindow = ui.LoadLayout(cache.GetResource("XMLFile", "UI/EditorSceneWindow.xml"));
+    hierarchyList = sceneWindow.GetChild("NodeList");
     ui.root.AddChild(sceneWindow);
     int height = Min(ui.root.height - 60, 500);
     sceneWindow.SetSize(300, height);
@@ -43,18 +45,16 @@ void CreateSceneWindow()
         newComponentList.AddItem(choice);
     }
 
-    // Set drag & drop target mode on the node list background, which is used to parent
-    // nodes back to the root node
-    ListView@ list = sceneWindow.GetChild("NodeList");
-    list.contentElement.dragDropMode = DD_TARGET;
-    list.scrollPanel.dragDropMode = DD_TARGET;
+    // Set drag & drop target mode on the node list background, which is used to parent nodes back to the root node
+    hierarchyList.contentElement.dragDropMode = DD_TARGET;
+    hierarchyList.scrollPanel.dragDropMode = DD_TARGET;
 
     SubscribeToEvent(sceneWindow.GetChild("CloseButton", true), "Released", "HideSceneWindow");
-    SubscribeToEvent(sceneWindow.GetChild("ExpandAllButton", true), "Released", "ExpandCollapseHierarchy");
-    SubscribeToEvent(sceneWindow.GetChild("CollapseAllButton", true), "Released", "ExpandCollapseHierarchy");
-    SubscribeToEvent(sceneWindow.GetChild("NodeList", true), "SelectionChanged", "HandleSceneWindowSelectionChange");
-    SubscribeToEvent(sceneWindow.GetChild("NodeList", true), "ItemDoubleClicked", "HandleSceneWindowItemDoubleClick");
-    SubscribeToEvent(sceneWindow.GetChild("NodeList", true), "UnhandledKey", "HandleSceneWindowKey");
+    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(newNodeList, "ItemSelected", "HandleCreateNode");
     SubscribeToEvent(newComponentList, "ItemSelected", "HandleCreateComponent");
     SubscribeToEvent("DragDropTest", "HandleDragDropTest");
@@ -80,17 +80,17 @@ void HideSceneWindow()
 void ExpandCollapseHierarchy(StringHash eventType, VariantMap& eventData)
 {
     Button@ button = eventData["Element"].GetUIElement();
-    bool enable = button.name == "ExpandAllButton";
+    bool enable = button.name == "ExpandButton";
+    bool all = cast<CheckBox>(sceneWindow.GetChild("AllCheckBox", true)).checked;
 
-    ListView@ list = sceneWindow.GetChild("NodeList", true);
-    Array<uint> selections = list.selections;
+    Array<uint> selections = hierarchyList.selections;
     for (uint i = 0; i < selections.length; ++i)
-        list.Expand(selections[i], enable, true);
+        hierarchyList.Expand(selections[i], enable, all);
 }
 
 void EnableExpandCollapseButtons(bool enable)
 {
-    String[] buttons = { "ExpandAllButton", "CollapseAllButton" };
+    String[] buttons = { "ExpandButton", "CollapseButton", "AllCheckBox" };
     for (uint i = 0; i < buttons.length; ++i)
     {
         UIElement@ element = sceneWindow.GetChild(buttons[i], true);
@@ -104,60 +104,56 @@ void ClearSceneWindow()
     if (sceneWindow is null)
         return;
 
-    ListView@ list = sceneWindow.GetChild("NodeList", true);
-    list.RemoveAllItems();
+    hierarchyList.RemoveAllItems();
 }
 
 void UpdateSceneWindow()
 {
     ClearSceneWindow();
-    UpdateSceneWindowNode(0, editorScene);
+    UpdateSceneWindowNode(0, editorScene, null);
+
+    // Re-enable layout update
+    hierarchyList.EnableLayoutUpdate();
 
     // Clear copybuffer when whole window refreshed
     copyBuffer.Clear();
 }
 
-uint UpdateSceneWindowNode(uint itemIndex, Node@ node)
-{
-    ListView@ list = sceneWindow.GetChild("NodeList", true);
-
+uint UpdateSceneWindowNode(uint itemIndex, Node@ node, UIElement@ parentItem)
+{  
     // Whenever we're updating, disable layout update to optimize speed
-    list.contentElement.DisableLayoutUpdate();
+    hierarchyList.contentElement.DisableLayoutUpdate();
 
     // Remove old item if exists
-    if (itemIndex < list.numItems && (node is null || (list.items[itemIndex].vars["Type"].GetInt() == ITEM_NODE &&
-        list.items[itemIndex].vars["NodeID"].GetUInt() == node.id)))
-        list.RemoveItem(itemIndex);
+    if (itemIndex < hierarchyList.numItems && (node is null || (hierarchyList.items[itemIndex].vars["Type"].GetInt() == ITEM_NODE &&
+        hierarchyList.items[itemIndex].vars["NodeID"].GetUInt() == node.id)))
+        hierarchyList.RemoveItem(itemIndex);
     if (node is null)
     {
-        list.contentElement.EnableLayoutUpdate();
-        list.contentElement.UpdateLayout();
+        hierarchyList.contentElement.EnableLayoutUpdate();
+        hierarchyList.contentElement.UpdateLayout();
         return itemIndex;
     }
 
-    int indent = GetNodeIndent(node);
-
     Text@ text = Text();
     text.SetStyle(uiStyle, "FileSelectorListText");
     text.vars["Type"] = ITEM_NODE;
     text.vars["NodeID"] = node.id;
-    text.vars["Indent"] = indent;
-    text.text = GetNodeTitle(node, indent);
+    text.text = GetNodeTitle(node);
+    
     // 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
         text.dragDropMode = DD_TARGET;
 
-    list.InsertItem(itemIndex, text);
-
-    ++itemIndex;
+    hierarchyList.InsertItem(itemIndex++, text, parentItem);
 
     // Update components first
-    for (uint j = 0; j < node.numComponents; ++j)
+    for (uint i = 0; i < node.numComponents; ++i)
     {
-        Component@ component = node.components[j];
-        AddComponentToSceneWindow(component, indent + 1, itemIndex);
+        Component@ component = node.components[i];
+        AddComponentToSceneWindow(component, itemIndex, text);
         ++itemIndex;
     }
 
@@ -165,35 +161,28 @@ uint UpdateSceneWindowNode(uint itemIndex, Node@ node)
     for (uint i = 0; i < node.numChildren; ++i)
     {
         Node@ childNode = node.children[i];
-        itemIndex = UpdateSceneWindowNode(itemIndex, childNode);
+        itemIndex = UpdateSceneWindowNode(itemIndex, childNode, text);
     }
 
     // Re-enable layout update (and do manual layout) now
-    list.contentElement.EnableLayoutUpdate();
-    list.contentElement.UpdateLayout();
+    hierarchyList.contentElement.EnableLayoutUpdate();
+    hierarchyList.contentElement.UpdateLayout();
 
     return itemIndex;
 }
 
 void UpdateSceneWindowNodeOnly(uint itemIndex, Node@ node)
 {
-    ListView@ list = sceneWindow.GetChild("NodeList", true);
-
-    int indent = GetNodeIndent(node);
-
-    Text@ text = list.items[itemIndex];
+    Text@ text = hierarchyList.items[itemIndex];
     if (text is null)
         return;
-    text.text = GetNodeTitle(node, indent);
+    text.text = GetNodeTitle(node);
 }
 
 void UpdateSceneWindowNode(Node@ node)
 {
-    uint index = GetNodeListIndex(node);
-    if (index == NO_ITEM)
-        index = GetParentAddIndex(node);
-
-    UpdateSceneWindowNode(index, 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(M_MAX_UNSIGNED, node, hierarchyList.items[GetNodeListIndex(node.parent)]);
 }
 
 void UpdateSceneWindowNodeOnly(Node@ node)
@@ -202,18 +191,15 @@ void UpdateSceneWindowNodeOnly(Node@ node)
     UpdateSceneWindowNodeOnly(index, node);
 }
 
-void AddComponentToSceneWindow(Component@ component, int indent, uint compItemIndex)
+void AddComponentToSceneWindow(Component@ component, uint compItemIndex, UIElement@ parentItem)
 {
-    ListView@ list = sceneWindow.GetChild("NodeList", true);
-
     Text@ text = Text();
     text.SetStyle(uiStyle, "FileSelectorListText");
     text.vars["Type"] = ITEM_COMPONENT;
     text.vars["NodeID"] = component.node.id;
     text.vars["ComponentID"] = component.id;
-    text.vars["Indent"] = indent;
-    text.text = GetComponentTitle(component, indent);
-    list.InsertItem(compItemIndex, text);
+    text.text = GetComponentTitle(component);
+    hierarchyList.InsertItem(compItemIndex, text, parentItem);
 }
 
 uint GetNodeListIndex(Node@ node)
@@ -221,13 +207,12 @@ uint GetNodeListIndex(Node@ node)
     if (node is null)
         return NO_ITEM;
 
-    ListView@ list = sceneWindow.GetChild("NodeList", true);
-    uint numItems = list.numItems;
+    uint numItems = hierarchyList.numItems;
     uint nodeID = node.id;
 
     for (uint i = 0; i < numItems; ++i)
     {
-        UIElement@ item = list.items[i];
+        UIElement@ item = hierarchyList.items[i];
         if (item.vars["Type"].GetInt() == ITEM_NODE && item.vars["NodeID"].GetUInt() == nodeID)
             return i;
     }
@@ -235,46 +220,17 @@ uint GetNodeListIndex(Node@ node)
     return NO_ITEM;
 }
 
-uint GetParentAddIndex(Node@ node)
-{
-    if (node is null || node.parent is null)
-        return NO_ITEM;
-
-    ListView@ list = sceneWindow.GetChild("NodeList", true);
-    uint numItems = list.numItems;
-    uint parentID = node.parent.id;
-
-    for (uint i = 0; i < numItems; ++i)
-    {
-        UIElement@ item = list.items[i];
-        if (item.vars["Type"].GetInt() == ITEM_NODE && item.vars["NodeID"].GetUInt() == parentID)
-        {
-            int indent = item.vars["Indent"].GetInt();
-            for (uint j = i + 1; j < numItems; ++j)
-            {
-                // Scan for the next node on this or lower level; that is the place to insert the new child node
-                if (list.items[j].vars["Indent"].GetInt() <= indent)
-                    return j;
-            }
-            return numItems;
-        }
-    }
-
-    return NO_ITEM;
-}
-
 uint GetNodeListIndex(Node@ node, uint startPos)
 {
     if (node is null)
         return NO_ITEM;
 
-    ListView@ list = sceneWindow.GetChild("NodeList", true);
-    uint numItems = list.numItems;
+    uint numItems = hierarchyList.numItems;
     uint nodeID = node.id;
 
     for (uint i = startPos; i < numItems; --i)
     {
-        UIElement@ item = list.items[i];
+        UIElement@ item = hierarchyList.items[i];
         if (item.vars["Type"].GetInt() == ITEM_NODE && item.vars["NodeID"].GetInt() == int(nodeID))
             return i;
     }
@@ -284,8 +240,7 @@ uint GetNodeListIndex(Node@ node, uint startPos)
 
 Node@ GetListNode(uint index)
 {
-    ListView@ list = sceneWindow.GetChild("NodeList", true);
-    UIElement@ item = list.items[index];
+    UIElement@ item = hierarchyList.items[index];
     if (item is null)
         return null;
 
@@ -294,8 +249,7 @@ Node@ GetListNode(uint index)
 
 Component@ GetListComponent(uint index)
 {
-    ListView@ list = sceneWindow.GetChild("NodeList", true);
-    UIElement@ item = list.items[index];
+    UIElement@ item = hierarchyList.items[index];
     return GetListComponent(item);
 }
 
@@ -315,12 +269,10 @@ uint GetComponentListIndex(Component@ component)
     if (component is null)
         return NO_ITEM;
 
-    ListView@ list = sceneWindow.GetChild("NodeList", true);
-    uint numItems = list.numItems;
-
+    uint numItems = hierarchyList.numItems;
     for (uint i = 0; i < numItems; ++i)
     {
-        UIElement@ item = list.items[i];
+        UIElement@ item = hierarchyList.items[i];
         if (item.vars["Type"].GetInt() == ITEM_COMPONENT && item.vars["ComponentID"].GetUInt() == component.id)
             return i;
     }
@@ -341,13 +293,8 @@ int GetNodeIndent(Node@ node)
     return indent;
 }
 
-String GetNodeTitle(Node@ node, int indent)
+String GetNodeTitle(Node@ node)
 {
-    String indentStr;
-    indentStr.Resize(indent);
-    for (int i = 0; i < indent; ++i)
-        indentStr[i] = ' ';
-
     String idStr;
     if (node.id >= FIRST_LOCAL_ID)
         idStr = "Local " + String(node.id - FIRST_LOCAL_ID);
@@ -355,32 +302,25 @@ String GetNodeTitle(Node@ node, int indent)
         idStr = String(node.id);
 
     if (node.name.empty)
-        return indentStr + node.typeName + " (" + idStr + ")";
+        return node.typeName + " (" + idStr + ")";
     else
-        return indentStr + node.name + " (" + idStr + ")";
+        return node.name + " (" + idStr + ")";
 }
 
-String GetComponentTitle(Component@ component, int indent)
+String GetComponentTitle(Component@ component)
 {
-    String indentStr;
     String localStr;
-    indentStr.Resize(indent);
-    for (int i = 0; i < indent; ++i)
-        indentStr[i] = ' ';
-
     if (component.id >= FIRST_LOCAL_ID)
         localStr = " (Local)";
 
-    return indentStr + component.typeName + localStr;
+    return component.typeName + localStr;
 }
 
 void SelectNode(Node@ node, bool multiselect)
 {
-    ListView@ list = sceneWindow.GetChild("NodeList", true);
-
     if (node is null && !multiselect)
     {
-        list.ClearSelection();
+        hierarchyList.ClearSelection();
         return;
     }
 
@@ -395,45 +335,43 @@ void SelectNode(Node@ node, bool multiselect)
         node = parent;
     }
     
-    uint numItems = list.numItems;
+    uint numItems = hierarchyList.numItems;
     uint parentItem = GetNodeListIndex(node);
 
     if (nodeItem < numItems)
     {
         // Expand the node chain now
-        if (!multiselect || !list.IsSelected(nodeItem))
+        if (!multiselect || !hierarchyList.IsSelected(nodeItem))
         {
             if (parentItem < numItems)
-                list.Expand(parentItem, true);
-            list.Expand(nodeItem, true);
+                hierarchyList.Expand(parentItem, true);
+            hierarchyList.Expand(nodeItem, true);
         }
         // This causes an event to be sent, in response we set the node/component selections, and refresh editors
         if (!multiselect)
-            list.selection = nodeItem;
+            hierarchyList.selection = nodeItem;
         else
-            list.ToggleSelection(nodeItem);
+            hierarchyList.ToggleSelection(nodeItem);
     }
     else
     {
         if (!multiselect)
-            list.ClearSelection();
+            hierarchyList.ClearSelection();
     }
 }
 
 void SelectComponent(Component@ component, bool multiselect)
 {
-    ListView@ list = sceneWindow.GetChild("NodeList", true);
-
     if (component is null && !multiselect)
     {
-        list.ClearSelection();
+        hierarchyList.ClearSelection();
         return;
     }
     
     Node@ node = component.node;
     if (node is null && !multiselect)
     {
-        list.ClearSelection();
+        hierarchyList.ClearSelection();
         return;
     }
 
@@ -449,34 +387,34 @@ void SelectComponent(Component@ component, bool multiselect)
         node = parent;
     }
 
-    uint numItems = list.numItems;
+    uint numItems = hierarchyList.numItems;
     uint parentItem = GetNodeListIndex(node);
 
-    if (parentItem >= list.numItems && !multiselect)
+    if (parentItem >= hierarchyList.numItems && !multiselect)
     {
-        list.ClearSelection();
+        hierarchyList.ClearSelection();
         return;
     }
 
     if (nodeItem < numItems && componentItem < numItems)
     {
         // Expand the node chain now
-        if (!multiselect || !list.IsSelected(componentItem))
+        if (!multiselect || !hierarchyList.IsSelected(componentItem))
         {
             if (parentItem < numItems)
-                list.Expand(parentItem, true);
-            list.Expand(nodeItem, true);
+                hierarchyList.Expand(parentItem, true);
+            hierarchyList.Expand(nodeItem, true);
         }
         // This causes an event to be sent, in response we set the node/component selections, and refresh editors
         if (!multiselect)
-            list.selection = componentItem;
+            hierarchyList.selection = componentItem;
         else
-            list.ToggleSelection(componentItem);
+            hierarchyList.ToggleSelection(componentItem);
     }
     else
     {
         if (!multiselect)
-            list.ClearSelection();
+            hierarchyList.ClearSelection();
     }
 }
 
@@ -487,8 +425,7 @@ void HandleSceneWindowSelectionChange()
     
     ClearSelection();
 
-    ListView@ list = sceneWindow.GetChild("NodeList", true);
-    Array<uint> indices = list.selections;
+    Array<uint> indices = hierarchyList.selections;
 
     // Enable Expand/Collapse button when there is selection
     EnableExpandCollapseButtons(indices.length > 0);
@@ -496,7 +433,7 @@ void HandleSceneWindowSelectionChange()
     for (uint i = 0; i < indices.length; ++i)
     {
         uint index = indices[i];
-        UIElement@ item = list.items[index];
+        UIElement@ item = hierarchyList.items[index];
         int type = item.vars["Type"].GetInt();
         if (type == ITEM_COMPONENT)
         {
@@ -606,10 +543,8 @@ void HandleSceneWindowSelectionChange()
 
 void HandleSceneWindowItemDoubleClick(StringHash eventType, VariantMap& eventData)
 {
-    ListView@ list = sceneWindow.GetChild("NodeList", true);
-
     uint index = eventData["Selection"].GetUInt();
-    list.ToggleExpand(index);
+    hierarchyList.ToggleExpand(index);
 }
 
 void HandleSceneWindowKey(StringHash eventType, VariantMap& eventData)
@@ -686,15 +621,13 @@ bool TestSceneWindowElements(UIElement@ source, UIElement@ target)
 void FocusNode(Node@ node)
 {
     uint index = GetNodeListIndex(node);
-    ListView@ list = sceneWindow.GetChild("NodeList", true);
-    list.selection = index;
+    hierarchyList.selection = index;
 }
 
 void FocusComponent(Component@ component)
 {
     uint index = GetComponentListIndex(component);
-    ListView@ list = sceneWindow.GetChild("NodeList", true);
-    list.selection = index;
+    hierarchyList.selection = index;
 }
 
 void HandleCreateNode(StringHash eventType, VariantMap& eventData)
@@ -748,8 +681,7 @@ bool CheckSceneWindowFocus()
 {
     // 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
-    ListView@ list = sceneWindow.GetChild("NodeList", true);
-    if (ui.focusElement is list || ui.focusElement is null)
+    if (ui.focusElement is hierarchyList || ui.focusElement is null)
         return true;
     else
         return false;
@@ -779,7 +711,7 @@ void HandleNodeRemoved(StringHash eventType, VariantMap& eventData)
 
     Node@ node = eventData["Node"].GetNode();
     uint index = GetNodeListIndex(node);
-    UpdateSceneWindowNode(index, null);
+    UpdateSceneWindowNode(index, null, null);
 }
 
 void HandleComponentAdded(StringHash eventType, VariantMap& eventData)

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

@@ -8,7 +8,7 @@ void CreateEditorSettingsDialog()
     if (settingsDialog !is null)
         return;
     
-    settingsDialog = ui.LoadLayout(cache.GetResource("XMLFile", "UI/EditorSettingsDialog.xml"), uiStyle);
+    settingsDialog = ui.LoadLayout(cache.GetResource("XMLFile", "UI/EditorSettingsDialog.xml"));
     ui.root.AddChild(settingsDialog);
     settingsDialog.opacity = uiMaxOpacity;
     CenterDialog(settingsDialog);

+ 1 - 0
Bin/Data/Scripts/Editor/EditorUI.as

@@ -25,6 +25,7 @@ float uiMaxOpacity = 0.7;
 void CreateUI()
 {
     uiStyle = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");
+    ui.root.defaultStyle = uiStyle;
 
     CreateCursor();
     CreateMenuBar();

BIN
Bin/Data/Textures/UI.png


+ 8 - 0
Bin/Data/UI/DefaultStyle.xml

@@ -245,6 +245,14 @@
             <attribute name="Clip Border" value="1 1 1 1" />
         </element>
     </element>
+    <element type="ListViewHierarchyOverlay">
+        <attribute name="Min Size" value="16 16" />
+        <attribute name="Max Size" value="16 16" />
+        <attribute name="Texture" value="Texture2D;Textures/UI.png" />
+        <attribute name="Image Rect" value="160 0 176 16" />
+        <attribute name="Checked Image Offset" value="16 0" />
+        <attribute name="Hover Image Offset" value="0 16" />
+    </element>
     <element type="Menu">
         <attribute name="Texture" value="Texture2D;Textures/UI.png" />
         <attribute name="Image Rect" value="96 0 112 16" />

+ 12 - 2
Bin/Data/UI/EditorSceneWindow.xml

@@ -24,7 +24,7 @@
         <attribute name="Layout Mode" value="Horizontal" />
         <attribute name="Layout Spacing" value="4" />
         <element type="Button">
-            <attribute name="Name" value="ExpandAllButton" />
+            <attribute name="Name" value="ExpandButton" />
             <attribute name="Min Size" value="70 17" />
             <attribute name="Max Size" value="70 17" />
             <attribute name="Layout Mode" value="Vertical" />
@@ -35,7 +35,7 @@
             </element>
         </element>
         <element type="Button">
-            <attribute name="Name" value="CollapseAllButton" />
+            <attribute name="Name" value="CollapseButton" />
             <attribute name="Min Size" value="70 17" />
             <attribute name="Max Size" value="70 17" />
             <attribute name="Layout Mode" value="Vertical" />
@@ -45,6 +45,16 @@
                 <attribute name="Text Alignment" value="Center" />
             </element>
         </element>
+        <element type="CheckBox">
+            <attribute name="Name" value="AllCheckBox" />
+            <attribute name="Min Size" value="15 15" />
+            <attribute name="Max Size" value="15 15" />
+            <attribute name="Layout Mode" value="Horizontal" />
+            <attribute name="Layout Border" value="20 0 0 0" />
+            <element type="Text">
+                <attribute name="Text" value="All" />
+            </element>
+        </element>
     </element>
     <element type="ListView">
         <attribute name="Name" value="NodeList" />

+ 87 - 15
Docs/ScriptAPI.dox

@@ -3101,7 +3101,8 @@ Methods:<br>
 - UIElement@ CreateChild(const String&, const String& arg1 = String ( ))
 - void AddChild(UIElement@)
 - void InsertChild(uint, UIElement@)
-- void RemoveChild(UIElement@)
+- void RemoveChild(UIElement@, uint arg1 = 0)
+- void RemoveChild(uint)
 - void RemoveAllChildren()
 - void Remove()
 - UIElement@ GetChild(const String&, bool arg1 = false) const
@@ -3151,9 +3152,13 @@ Properties:<br>
 - bool colorGradient (readonly)
 - FocusMode focusMode
 - uint dragDropMode
+- XMLFile@ defaultStyle
 - LayoutMode layoutMode
 - int layoutSpacing
 - IntRect layoutBorder
+- int indent
+- int indentSpacing
+- int indentWidth (readonly)
 - IntVector2 rotationPivot
 - float rotation
 - IntVector2 childOffset (readonly)
@@ -3202,7 +3207,8 @@ Methods:<br>
 - UIElement@ CreateChild(const String&, const String& arg1 = String ( ))
 - void AddChild(UIElement@)
 - void InsertChild(uint, UIElement@)
-- void RemoveChild(UIElement@)
+- void RemoveChild(UIElement@, uint arg1 = 0)
+- void RemoveChild(uint)
 - void RemoveAllChildren()
 - void Remove()
 - UIElement@ GetChild(const String&, bool arg1 = false) const
@@ -3254,9 +3260,13 @@ Properties:<br>
 - bool colorGradient (readonly)
 - FocusMode focusMode
 - uint dragDropMode
+- XMLFile@ defaultStyle
 - LayoutMode layoutMode
 - int layoutSpacing
 - IntRect layoutBorder
+- int indent
+- int indentSpacing
+- int indentWidth (readonly)
 - IntVector2 rotationPivot
 - float rotation
 - IntVector2 childOffset (readonly)
@@ -3311,7 +3321,8 @@ Methods:<br>
 - UIElement@ CreateChild(const String&, const String& arg1 = String ( ))
 - void AddChild(UIElement@)
 - void InsertChild(uint, UIElement@)
-- void RemoveChild(UIElement@)
+- void RemoveChild(UIElement@, uint arg1 = 0)
+- void RemoveChild(uint)
 - void RemoveAllChildren()
 - void Remove()
 - UIElement@ GetChild(const String&, bool arg1 = false) const
@@ -3366,9 +3377,13 @@ Properties:<br>
 - bool colorGradient (readonly)
 - FocusMode focusMode
 - uint dragDropMode
+- XMLFile@ defaultStyle
 - LayoutMode layoutMode
 - int layoutSpacing
 - IntRect layoutBorder
+- int indent
+- int indentSpacing
+- int indentWidth (readonly)
 - IntVector2 rotationPivot
 - float rotation
 - IntVector2 childOffset (readonly)
@@ -3427,7 +3442,8 @@ Methods:<br>
 - UIElement@ CreateChild(const String&, const String& arg1 = String ( ))
 - void AddChild(UIElement@)
 - void InsertChild(uint, UIElement@)
-- void RemoveChild(UIElement@)
+- void RemoveChild(UIElement@, uint arg1 = 0)
+- void RemoveChild(uint)
 - void RemoveAllChildren()
 - void Remove()
 - UIElement@ GetChild(const String&, bool arg1 = false) const
@@ -3480,9 +3496,13 @@ Properties:<br>
 - bool colorGradient (readonly)
 - FocusMode focusMode
 - uint dragDropMode
+- XMLFile@ defaultStyle
 - LayoutMode layoutMode
 - int layoutSpacing
 - IntRect layoutBorder
+- int indent
+- int indentSpacing
+- int indentWidth (readonly)
 - IntVector2 rotationPivot
 - float rotation
 - IntVector2 childOffset (readonly)
@@ -3539,7 +3559,8 @@ Methods:<br>
 - UIElement@ CreateChild(const String&, const String& arg1 = String ( ))
 - void AddChild(UIElement@)
 - void InsertChild(uint, UIElement@)
-- void RemoveChild(UIElement@)
+- void RemoveChild(UIElement@, uint arg1 = 0)
+- void RemoveChild(uint)
 - void RemoveAllChildren()
 - void Remove()
 - UIElement@ GetChild(const String&, bool arg1 = false) const
@@ -3592,9 +3613,13 @@ Properties:<br>
 - bool colorGradient (readonly)
 - FocusMode focusMode
 - uint dragDropMode
+- XMLFile@ defaultStyle
 - LayoutMode layoutMode
 - int layoutSpacing
 - IntRect layoutBorder
+- int indent
+- int indentSpacing
+- int indentWidth (readonly)
 - IntVector2 rotationPivot
 - float rotation
 - IntVector2 childOffset (readonly)
@@ -3650,7 +3675,8 @@ Methods:<br>
 - UIElement@ CreateChild(const String&, const String& arg1 = String ( ))
 - void AddChild(UIElement@)
 - void InsertChild(uint, UIElement@)
-- void RemoveChild(UIElement@)
+- void RemoveChild(UIElement@, uint arg1 = 0)
+- void RemoveChild(uint)
 - void RemoveAllChildren()
 - void Remove()
 - UIElement@ GetChild(const String&, bool arg1 = false) const
@@ -3703,9 +3729,13 @@ Properties:<br>
 - bool colorGradient (readonly)
 - FocusMode focusMode
 - uint dragDropMode
+- XMLFile@ defaultStyle
 - LayoutMode layoutMode
 - int layoutSpacing
 - IntRect layoutBorder
+- int indent
+- int indentSpacing
+- int indentWidth (readonly)
 - IntVector2 rotationPivot
 - float rotation
 - IntVector2 childOffset (readonly)
@@ -3765,7 +3795,8 @@ Methods:<br>
 - UIElement@ CreateChild(const String&, const String& arg1 = String ( ))
 - void AddChild(UIElement@)
 - void InsertChild(uint, UIElement@)
-- void RemoveChild(UIElement@)
+- void RemoveChild(UIElement@, uint arg1 = 0)
+- void RemoveChild(uint)
 - void RemoveAllChildren()
 - void Remove()
 - UIElement@ GetChild(const String&, bool arg1 = false) const
@@ -3818,9 +3849,13 @@ Properties:<br>
 - bool colorGradient (readonly)
 - FocusMode focusMode
 - uint dragDropMode
+- XMLFile@ defaultStyle
 - LayoutMode layoutMode
 - int layoutSpacing
 - IntRect layoutBorder
+- int indent
+- int indentSpacing
+- int indentWidth (readonly)
 - IntVector2 rotationPivot
 - float rotation
 - IntVector2 childOffset (readonly)
@@ -3878,7 +3913,8 @@ Methods:<br>
 - UIElement@ CreateChild(const String&, const String& arg1 = String ( ))
 - void AddChild(UIElement@)
 - void InsertChild(uint, UIElement@)
-- void RemoveChild(UIElement@)
+- void RemoveChild(UIElement@, uint arg1 = 0)
+- void RemoveChild(uint)
 - void RemoveAllChildren()
 - void Remove()
 - UIElement@ GetChild(const String&, bool arg1 = false) const
@@ -3930,9 +3966,13 @@ Properties:<br>
 - bool colorGradient (readonly)
 - FocusMode focusMode
 - uint dragDropMode
+- XMLFile@ defaultStyle
 - LayoutMode layoutMode
 - int layoutSpacing
 - IntRect layoutBorder
+- int indent
+- int indentSpacing
+- int indentWidth (readonly)
 - IntVector2 rotationPivot
 - float rotation
 - IntVector2 childOffset (readonly)
@@ -3989,7 +4029,8 @@ Methods:<br>
 - UIElement@ CreateChild(const String&, const String& arg1 = String ( ))
 - void AddChild(UIElement@)
 - void InsertChild(uint, UIElement@)
-- void RemoveChild(UIElement@)
+- void RemoveChild(UIElement@, uint arg1 = 0)
+- void RemoveChild(uint)
 - void RemoveAllChildren()
 - void Remove()
 - UIElement@ GetChild(const String&, bool arg1 = false) const
@@ -4001,7 +4042,7 @@ Methods:<br>
 - void SetViewPosition(int, int)
 - void SetScrollBarsVisible(bool, bool)
 - void AddItem(UIElement@)
-- void InsertItem(uint, UIElement@)
+- void InsertItem(uint, UIElement@, UIElement@ arg2 = null)
 - void RemoveItem(UIElement@, uint arg1 = 0)
 - void RemoveItem(uint)
 - void RemoveAllItems()
@@ -4014,7 +4055,9 @@ Methods:<br>
 - void Expand(uint, bool, bool arg2 = false)
 - void ToggleExpand(uint, bool arg1 = false)
 - bool IsSelected(uint) const
+- bool IsExpanded(uint) const
 - UIElement@[]@ GetItems() const
+- uint FindItem(UIElement@)
 
 Properties:<br>
 - ShortStringHash type (readonly)
@@ -4056,9 +4099,13 @@ Properties:<br>
 - bool colorGradient (readonly)
 - FocusMode focusMode
 - uint dragDropMode
+- XMLFile@ defaultStyle
 - LayoutMode layoutMode
 - int layoutSpacing
 - IntRect layoutBorder
+- int indent
+- int indentSpacing
+- int indentWidth (readonly)
 - IntVector2 rotationPivot
 - float rotation
 - IntVector2 childOffset (readonly)
@@ -4126,7 +4173,8 @@ Methods:<br>
 - UIElement@ CreateChild(const String&, const String& arg1 = String ( ))
 - void AddChild(UIElement@)
 - void InsertChild(uint, UIElement@)
-- void RemoveChild(UIElement@)
+- void RemoveChild(UIElement@, uint arg1 = 0)
+- void RemoveChild(uint)
 - void RemoveAllChildren()
 - void Remove()
 - UIElement@ GetChild(const String&, bool arg1 = false) const
@@ -4180,9 +4228,13 @@ Properties:<br>
 - bool colorGradient (readonly)
 - FocusMode focusMode
 - uint dragDropMode
+- XMLFile@ defaultStyle
 - LayoutMode layoutMode
 - int layoutSpacing
 - IntRect layoutBorder
+- int indent
+- int indentSpacing
+- int indentWidth (readonly)
 - IntVector2 rotationPivot
 - float rotation
 - IntVector2 childOffset (readonly)
@@ -4243,7 +4295,8 @@ Methods:<br>
 - UIElement@ CreateChild(const String&, const String& arg1 = String ( ))
 - void AddChild(UIElement@)
 - void InsertChild(uint, UIElement@)
-- void RemoveChild(UIElement@)
+- void RemoveChild(UIElement@, uint arg1 = 0)
+- void RemoveChild(uint)
 - void RemoveAllChildren()
 - void Remove()
 - UIElement@ GetChild(const String&, bool arg1 = false) const
@@ -4295,9 +4348,13 @@ Properties:<br>
 - bool colorGradient (readonly)
 - FocusMode focusMode
 - uint dragDropMode
+- XMLFile@ defaultStyle
 - LayoutMode layoutMode
 - int layoutSpacing
 - IntRect layoutBorder
+- int indent
+- int indentSpacing
+- int indentWidth (readonly)
 - IntVector2 rotationPivot
 - float rotation
 - IntVector2 childOffset (readonly)
@@ -4363,7 +4420,8 @@ Methods:<br>
 - UIElement@ CreateChild(const String&, const String& arg1 = String ( ))
 - void AddChild(UIElement@)
 - void InsertChild(uint, UIElement@)
-- void RemoveChild(UIElement@)
+- void RemoveChild(UIElement@, uint arg1 = 0)
+- void RemoveChild(uint)
 - void RemoveAllChildren()
 - void Remove()
 - UIElement@ GetChild(const String&, bool arg1 = false) const
@@ -4420,9 +4478,13 @@ Properties:<br>
 - bool colorGradient (readonly)
 - FocusMode focusMode
 - uint dragDropMode
+- XMLFile@ defaultStyle
 - LayoutMode layoutMode
 - int layoutSpacing
 - IntRect layoutBorder
+- int indent
+- int indentSpacing
+- int indentWidth (readonly)
 - IntVector2 rotationPivot
 - float rotation
 - IntVector2 childOffset (readonly)
@@ -4486,7 +4548,8 @@ Methods:<br>
 - UIElement@ CreateChild(const String&, const String& arg1 = String ( ))
 - void AddChild(UIElement@)
 - void InsertChild(uint, UIElement@)
-- void RemoveChild(UIElement@)
+- void RemoveChild(UIElement@, uint arg1 = 0)
+- void RemoveChild(uint)
 - void RemoveAllChildren()
 - void Remove()
 - UIElement@ GetChild(const String&, bool arg1 = false) const
@@ -4549,9 +4612,13 @@ Properties:<br>
 - bool colorGradient (readonly)
 - FocusMode focusMode
 - uint dragDropMode
+- XMLFile@ defaultStyle
 - LayoutMode layoutMode
 - int layoutSpacing
 - IntRect layoutBorder
+- int indent
+- int indentSpacing
+- int indentWidth (readonly)
 - IntVector2 rotationPivot
 - float rotation
 - IntVector2 childOffset (readonly)
@@ -4620,7 +4687,8 @@ Methods:<br>
 - UIElement@ CreateChild(const String&, const String& arg1 = String ( ))
 - void AddChild(UIElement@)
 - void InsertChild(uint, UIElement@)
-- void RemoveChild(UIElement@)
+- void RemoveChild(UIElement@, uint arg1 = 0)
+- void RemoveChild(uint)
 - void RemoveAllChildren()
 - void Remove()
 - UIElement@ GetChild(const String&, bool arg1 = false) const
@@ -4672,9 +4740,13 @@ Properties:<br>
 - bool colorGradient (readonly)
 - FocusMode focusMode
 - uint dragDropMode
+- XMLFile@ defaultStyle
 - LayoutMode layoutMode
 - int layoutSpacing
 - IntRect layoutBorder
+- int indent
+- int indentSpacing
+- int indentWidth (readonly)
 - IntVector2 rotationPivot
 - float rotation
 - IntVector2 childOffset (readonly)

+ 16 - 3
Engine/Engine/APITemplates.h

@@ -776,9 +776,14 @@ static UIElement* UIElementCreateChild(const String& typeName, const String& nam
     return ptr->CreateChild(ShortStringHash(typeName), name);
 }
 
-static void UIElementRemoveChild(UIElement* child, UIElement* ptr)
+static void UIElementRemoveChild(UIElement* child, unsigned index, UIElement* ptr)
 {
-    ptr->RemoveChild(child);
+    ptr->RemoveChild(child, index);
+}
+
+static void UIElementRemoveChild(unsigned index, UIElement* ptr)
+{
+    ptr->RemoveChildAtIndex(index);
 }
 
 static CScriptArray* UIElementGetChildren(bool recursive, UIElement* ptr)
@@ -837,7 +842,8 @@ template <class T> void RegisterUIElement(asIScriptEngine* engine, const char* c
     engine->RegisterObjectMethod(className, "UIElement@+ CreateChild(const String&in, const String&in name = String())", asFUNCTION(UIElementCreateChild), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod(className, "void AddChild(UIElement@+)", asMETHOD(T, AddChild), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void InsertChild(uint, UIElement@+)", asMETHOD(T, InsertChild), asCALL_THISCALL);
-    engine->RegisterObjectMethod(className, "void RemoveChild(UIElement@+)", asFUNCTION(UIElementRemoveChild), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod(className, "void RemoveChild(UIElement@+, uint arg1 = 0)", asFUNCTIONPR(UIElementRemoveChild, (UIElement*, unsigned, UIElement*), void), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod(className, "void RemoveChild(uint)", asFUNCTIONPR(UIElementRemoveChild, (unsigned, UIElement*), void), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod(className, "void RemoveAllChildren()", asMETHOD(T, RemoveAllChildren), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void Remove()", asMETHOD(T, Remove), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "UIElement@+ GetChild(const String&in, bool recursive = false) const", asMETHODPR(T, GetChild, (const String&, bool) const, UIElement*), asCALL_THISCALL);
@@ -906,12 +912,19 @@ 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_defaultStyle(XMLFile@+)", asMETHOD(T, SetDefaultStyle), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "XMLFile@+ get_defaultStyle()", asMETHOD(T, GetDefaultStyle), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void set_layoutMode(LayoutMode)", asMETHOD(T, SetLayoutMode), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "LayoutMode get_layoutMode() const", asMETHOD(T, GetLayoutMode), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void set_layoutSpacing(int)", asMETHOD(T, SetLayoutSpacing), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "int get_layoutSpacing() const", asMETHOD(T, GetLayoutSpacing), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void set_layoutBorder(const IntRect&)", asMETHOD(T, SetLayoutBorder), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "const IntRect& get_layoutBorder() const", asMETHOD(T, GetLayoutBorder), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "void set_indent(int)", asMETHOD(T, SetIndent), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "int get_indent() const", asMETHOD(T, GetIndent), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "void set_indentSpacing(int)", asMETHOD(T, SetIndentSpacing), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "int get_indentSpacing() const", asMETHOD(T, GetIndentSpacing), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "int get_indentWidth() const", asMETHOD(T, GetIndentWidth), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void set_rotationPivot(const IntVector2&)", asMETHOD(T, SetRotationPivot), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "const IntVector2& get_rotationPivot() const", asMETHOD(T, GetRotationPivot), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void set_rotation(float)", asMETHOD(T, SetRotation), asCALL_THISCALL);

+ 3 - 1
Engine/Engine/UIAPI.cpp

@@ -233,7 +233,7 @@ static void RegisterListView(asIScriptEngine* engine)
     engine->RegisterObjectMethod("ListView", "void SetViewPosition(int, int)", asMETHODPR(ListView, SetViewPosition, (int, int), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("ListView", "void SetScrollBarsVisible(bool, bool)", asMETHOD(ListView, SetScrollBarsVisible), asCALL_THISCALL);
     engine->RegisterObjectMethod("ListView", "void AddItem(UIElement@+)", asMETHOD(ListView, AddItem), asCALL_THISCALL);
-    engine->RegisterObjectMethod("ListView", "void InsertItem(uint, UIElement@+)", asMETHOD(ListView, InsertItem), asCALL_THISCALL);
+    engine->RegisterObjectMethod("ListView", "void InsertItem(uint, UIElement@+, UIElement@+ arg2 = null)", asMETHOD(ListView, InsertItem), asCALL_THISCALL);
     engine->RegisterObjectMethod("ListView", "void RemoveItem(UIElement@+, uint index = 0)", asMETHODPR(ListView, RemoveItem, (UIElement*, unsigned), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("ListView", "void RemoveItem(uint)", asMETHODPR(ListView, RemoveItem, (unsigned), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("ListView", "void RemoveAllItems()", asMETHOD(ListView, RemoveAllItems), asCALL_THISCALL);
@@ -246,7 +246,9 @@ static void RegisterListView(asIScriptEngine* engine)
     engine->RegisterObjectMethod("ListView", "void Expand(uint, bool, bool arg2 = false)", asMETHOD(ListView, Expand), asCALL_THISCALL);
     engine->RegisterObjectMethod("ListView", "void ToggleExpand(uint, bool arg1 = false)", asMETHOD(ListView, ToggleExpand), asCALL_THISCALL);
     engine->RegisterObjectMethod("ListView", "bool IsSelected(uint) const", asMETHOD(ListView, IsSelected), asCALL_THISCALL);
+    engine->RegisterObjectMethod("ListView", "bool IsExpanded(uint) const", asMETHOD(ListView, IsExpanded), asCALL_THISCALL);
     engine->RegisterObjectMethod("ListView", "Array<UIElement@>@ GetItems() const", asFUNCTION(ListViewGetItems), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("ListView", "uint FindItem(UIElement@+)", asMETHOD(ListView, FindItem), asCALL_THISCALL);
     engine->RegisterObjectMethod("ListView", "void set_viewPosition(const IntVector2&in)", asMETHODPR(ListView, SetViewPosition, (const IntVector2&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("ListView", "const IntVector2& get_viewPosition() const", asMETHOD(ListView, GetViewPosition), asCALL_THISCALL);
     engine->RegisterObjectMethod("ListView", "UIElement@+ get_contentElement() const", asMETHOD(ListView, GetContentElement), asCALL_THISCALL);

+ 8 - 2
Engine/IO/Log.cpp

@@ -190,8 +190,14 @@ void Log::WriteRaw(const String& message, bool error)
         #elif defined(IOS)
         SDL_IOS_LogMessage(message.CString());
         #else
-        if (!quiet_)
-            PrintUnicode(message, error);
+        if (quiet_)
+        {
+            // If in quiet mode, still print the error message to the standard error stream
+            if (error)
+            	PrintUnicode(message, true);
+        }
+        else
+        	PrintUnicode(message, error);
         #endif
         
         if (logFile_)

+ 13 - 11
Engine/UI/BorderImage.cpp

@@ -150,7 +150,9 @@ void BorderImage::GetBatches(PODVector<UIBatch>& batches, PODVector<UIQuad>& qua
     UIBatch batch(GetBatchTransform(), blendMode_ == BLEND_REPLACE && !allOpaque ? BLEND_ALPHA : blendMode_, currentScissor, texture_, &quads);
     
     // Calculate size of the inner rect, and texture dimensions of the inner rect
-    const IntVector2& size = GetSize();
+    int x = GetIndentWidth();
+    IntVector2 size = GetSize();
+    size.x_ -= x;
     IntVector2 innerSize(
         Max(size.x_ - border_.left_ - border_.right_, 0), 
         Max(size.y_ - border_.top_ - border_.bottom_, 0));
@@ -160,30 +162,30 @@ void BorderImage::GetBatches(PODVector<UIBatch>& batches, PODVector<UIQuad>& qua
     
     IntVector2 topLeft(imageRect_.left_, imageRect_.top_);
     topLeft += offset;
-    
+
     // Top
     if (border_.top_)
     {
         if (border_.left_)
-            batch.AddQuad(*this, 0, 0, border_.left_, border_.top_, topLeft.x_, topLeft.y_);
+            batch.AddQuad(*this, x, 0, border_.left_, border_.top_, topLeft.x_, topLeft.y_);
         if (innerSize.x_)
-            batch.AddQuad(*this, border_.left_, 0, innerSize.x_, border_.top_,
+            batch.AddQuad(*this, x + border_.left_, 0, innerSize.x_, border_.top_,
             topLeft.x_ + border_.left_, topLeft.y_, innerTextureSize.x_, border_.top_, tiled_);
         if (border_.right_)
-            batch.AddQuad(*this, border_.left_ + innerSize.x_, 0, border_.right_, border_.top_,
+            batch.AddQuad(*this, x + border_.left_ + innerSize.x_, 0, border_.right_, border_.top_,
             topLeft.x_ + border_.left_ + innerTextureSize.x_, topLeft.y_);
     }
     // Middle
     if (innerSize.y_)
     {
         if (border_.left_)
-            batch.AddQuad(*this, 0, border_.top_, border_.left_, innerSize.y_,
+            batch.AddQuad(*this, x, border_.top_, border_.left_, innerSize.y_,
             topLeft.x_, topLeft.y_ + border_.top_, border_.left_, innerTextureSize.y_, tiled_);
         if (innerSize.x_)
-            batch.AddQuad(*this, border_.left_, border_.top_, innerSize.x_, innerSize.y_,
+            batch.AddQuad(*this, x + border_.left_, border_.top_, innerSize.x_, innerSize.y_,
             topLeft.x_ + border_.left_, topLeft.y_ + border_.top_, innerTextureSize.x_, innerTextureSize.y_, tiled_);
         if (border_.right_)
-            batch.AddQuad(*this, border_.left_ + innerSize.x_, border_.top_, border_.right_,
+            batch.AddQuad(*this, x + border_.left_ + innerSize.x_, border_.top_, border_.right_,
             innerSize.y_, topLeft.x_ + border_.left_ + innerTextureSize.x_, topLeft.y_ + border_.top_,
             border_.right_, innerTextureSize.y_, tiled_);
     }
@@ -191,14 +193,14 @@ void BorderImage::GetBatches(PODVector<UIBatch>& batches, PODVector<UIQuad>& qua
     if (border_.bottom_)
     {
         if (border_.left_)
-            batch.AddQuad(*this, 0, border_.top_ + innerSize.y_, border_.left_, border_.bottom_,
+            batch.AddQuad(*this, x, border_.top_ + innerSize.y_, border_.left_, border_.bottom_,
             topLeft.x_, topLeft.y_ + border_.top_ + innerTextureSize.y_);
         if (innerSize.x_)
-            batch.AddQuad(*this, border_.left_, border_.top_ + innerSize.y_, innerSize.x_,
+            batch.AddQuad(*this, x + border_.left_, border_.top_ + innerSize.y_, innerSize.x_,
             border_.bottom_, topLeft.x_ + border_.left_, topLeft.y_ + border_.top_ + innerTextureSize.y_,
             innerTextureSize.x_, border_.bottom_, tiled_);
         if (border_.right_)
-            batch.AddQuad(*this, border_.left_ + innerSize.x_, border_.top_ + innerSize.y_,
+            batch.AddQuad(*this, x + border_.left_ + innerSize.x_, border_.top_ + innerSize.y_,
             border_.right_, border_.bottom_, topLeft.x_ + border_.left_ + innerTextureSize.x_, 
             topLeft.y_ + border_.top_ + innerTextureSize.y_);
     }

+ 1 - 1
Engine/UI/DropDownList.cpp

@@ -121,7 +121,7 @@ void DropDownList::OnShowPopup()
 
 void DropDownList::AddItem(UIElement* item)
 {
-    InsertItem(listView_->GetNumItems(), item);
+    InsertItem(M_MAX_UNSIGNED, item);
 }
 
 void DropDownList::InsertItem(unsigned index, UIElement* item)

+ 301 - 106
Engine/UI/ListView.cpp

@@ -21,7 +21,7 @@
 //
 
 #include "Precompiled.h"
-#include "BorderImage.h"
+#include "CheckBox.h"
 #include "Context.h"
 #include "InputEvents.h"
 #include "ListView.h"
@@ -35,9 +35,6 @@
 namespace Urho3D
 {
 
-static const ShortStringHash indentHash("Indent");
-static const ShortStringHash expandedHash("Expanded");
-
 static const char* highlightModes[] =
 {
     "Never",
@@ -51,41 +48,128 @@ template<> HighlightMode Variant::Get<HighlightMode>() const
     return (HighlightMode)GetInt();
 }
 
-int GetItemIndent(UIElement* item)
-{
-    return item ? item->GetVar(indentHash).GetInt() : 0;
-}
+static const ShortStringHash expandedHash("Expanded");
 
 bool GetItemExpanded(UIElement* item)
 {
-    return item ? item->GetVar(expandedHash).GetBool() : true;
+    return item ? item->GetVar(expandedHash).GetBool() : false;
 }
-    
+
 void SetItemExpanded(UIElement* item, bool enable)
 {
     item->SetVar(expandedHash, enable);
 }
 
+static const ShortStringHash hierarchyParentHash("HierarchyParent");
+
+bool GetItemHierarchyParent(UIElement* item)
+{
+    return item ? item->GetVar(hierarchyParentHash).GetBool() : false;
+}
+
+void SetItemHierarchyParent(UIElement* item, bool enable)
+{
+    item->SetVar(hierarchyParentHash, enable);
+}
+
+/// Hierarchy container (used by ListView internally when in hierarchy mode).
+class HierarchyContainer : public UIElement
+{
+    OBJECT(HierarchyContainer);
+
+public:
+    /// Construct.
+    HierarchyContainer(Context* context, UIElement* overlayContainer) :
+        UIElement(context),
+        overlayContainer_(overlayContainer)
+    {
+        SubscribeToEvent(this, E_LAYOUTUPDATED, HANDLER(HierarchyContainer, HandleLayoutUpdated));
+        SubscribeToEvent(overlayContainer->GetParent(), E_VIEWCHANGED, HANDLER(HierarchyContainer, HandleViewChanged));
+        SubscribeToEvent(E_UIMOUSECLICK, HANDLER(HierarchyContainer, HandleUIMouseClick));
+    }
+
+    /// Handle layout updated by adjusting the position of the overlays.
+    void HandleLayoutUpdated(StringHash eventType, VariantMap& eventData)
+    {
+        // Adjust the container size for child clipping effect
+        overlayContainer_->SetSize(GetParent()->GetSize());
+
+        for (unsigned i = 0; i < children_.Size(); ++i)
+        {
+            const IntVector2& position = children_[i]->GetPosition();
+            CheckBox* overlay = static_cast<CheckBox*>(overlayContainer_->GetChild(i));
+            bool visible = children_[i]->IsVisible() && GetItemHierarchyParent(children_[i]);
+            overlay->SetVisible(visible);
+            if (visible)
+            {
+                overlay->SetPosition(position.x_, position.y_);
+                overlay->SetChecked(GetItemExpanded(children_[i]));
+            }
+        }
+    }
+
+    /// Handle view changed by scrolling the overlays in tandem.
+    void HandleViewChanged(StringHash eventType, VariantMap& eventData)
+    {
+        using namespace ViewChanged;
+
+        int x = eventData[P_X].GetInt();
+        int y = eventData[P_Y].GetInt();
+
+        IntRect panelBorder = GetParent()->GetClipBorder();
+        overlayContainer_->SetChildOffset(IntVector2(-x + panelBorder.left_, -y + panelBorder.top_));
+    }
+
+    /// Handle mouse click on overlays by toggling the expansion state of the corresponding item
+    void HandleUIMouseClick(StringHash eventType, VariantMap& eventData)
+    {
+        using namespace UIMouseClick;
+
+        UIElement* overlay = static_cast<UIElement*>(eventData[UIMouseClick::P_ELEMENT].GetPtr());
+        if (overlay)
+        {
+            const Vector<SharedPtr<UIElement> >& children = overlayContainer_->GetChildren();
+            Vector<SharedPtr<UIElement> >::ConstIterator i = children.Find(SharedPtr<UIElement>(overlay));
+            if (i != children.End())
+                static_cast<ListView*>(overlayContainer_->GetParent())->ToggleExpand(i - children.Begin());
+        }
+    }
+
+    /// Insert a child element into a specific position in the child list.
+    void InsertChild(unsigned index, UIElement* element)
+    {
+        // Insert the overlay at the same index position to the overlay container
+        CheckBox* overlay = new CheckBox(context_);
+        overlay->SetStyle(overlayContainer_->GetDefaultStyle(), "ListViewHierarchyOverlay");
+        overlay->SetIndent(element->GetIndent() - 1);
+        overlay->SetFixedWidth(element->GetIndentWidth());
+        overlayContainer_->InsertChild(index, overlay);
+
+        // Then insert the element as child as per normal
+        UIElement::InsertChild(index, element);
+    }
+
+private:
+    UIElement* overlayContainer_;
+};
+
+OBJECTTYPESTATIC(HierarchyContainer);
 OBJECTTYPESTATIC(ListView);
 
 ListView::ListView(Context* context) :
     ScrollView(context),
     highlightMode_(HM_FOCUS),
     multiselect_(false),
-    hierarchyMode_(false),
+    hierarchyMode_(true),	// Init to true here so that the setter below takes effect
     clearSelectionOnDefocus_(false),
     doubleClickInterval_(500),
     lastClickedItem_(M_MAX_UNSIGNED)
 {
     resizeContentWidth_ = true;
-    
-    UIElement* container = new UIElement(context_);
-    container->SetInternal(true);
-    container->SetActive(true);
-    container->SetLayout(LM_VERTICAL);
-    container->SetSortChildren(false);
-    SetContentElement(container);
-    
+
+    // By default list view is set to non-hierarchy mode
+    SetHierarchyMode(false);
+
     SubscribeToEvent(E_UIMOUSECLICK, HANDLER(ListView, HandleUIMouseClick));
     SubscribeToEvent(E_FOCUSCHANGED, HANDLER(ListView, HandleFocusChanged));
 }
@@ -97,7 +181,7 @@ ListView::~ListView()
 void ListView::RegisterObject(Context* context)
 {
     context->RegisterFactory<ListView>();
-    
+
     ENUM_ACCESSOR_ATTRIBUTE(ListView, "Highlight Mode", GetHighlightMode, SetHighlightMode, HighlightMode, highlightModes, HM_FOCUS, AM_FILE);
     ACCESSOR_ATTRIBUTE(ListView, VAR_BOOL, "Multiselect", GetMultiselect, SetMultiselect, bool, false, AM_FILE);
     ACCESSOR_ATTRIBUTE(ListView, VAR_BOOL, "Hierarchy Mode", GetHierarchyMode, SetHierarchyMode, bool, false, AM_FILE);
@@ -116,7 +200,7 @@ void ListView::OnKey(int key, int buttons, int qualifiers)
     bool additive = multiselect_ && qualifiers & (QUAL_SHIFT | QUAL_CTRL);
     int delta = 0;
     int pageDirection = 1;
-    
+
     if (selection != M_MAX_UNSIGNED && numItems)
     {
         switch (key)
@@ -129,7 +213,7 @@ void ListView::OnKey(int key, int buttons, int qualifiers)
                 return;
             }
             break;
-            
+
         case KEY_RETURN:
         case KEY_RETURN2:
         case KEY_KP_ENTER:
@@ -139,19 +223,19 @@ void ListView::OnKey(int key, int buttons, int qualifiers)
                 return;
             }
             break;
-            
+
         case KEY_UP:
             delta = -1;
             break;
-            
+
         case KEY_DOWN:
             delta = 1;
             break;
-            
+
         case KEY_PAGEUP:
             pageDirection = -1;
             // Fallthru
-            
+
         case KEY_PAGEDOWN:
             {
                 // Convert page step to pixels and see how many items have to be skipped to reach that many pixels
@@ -178,25 +262,25 @@ void ListView::OnKey(int key, int buttons, int qualifiers)
                 delta = okSelection - selection - pageDirection * invisible;
             }
             break;
-            
+
         case KEY_HOME:
             delta = -(int)GetNumItems();
             break;
-            
+
         case KEY_END:
             delta = GetNumItems();
             break;
         }
     }
-    
+
     if (delta)
     {
         ChangeSelection(delta, additive);
         return;
     }
-    
+
     using namespace UnhandledKey;
-    
+
     VariantMap eventData;
     eventData[P_ELEMENT] = (void*)this;
     eventData[P_KEY] = key;
@@ -205,21 +289,67 @@ void ListView::OnKey(int key, int buttons, int qualifiers)
     SendEvent(E_UNHANDLEDKEY, eventData);
 }
 
+void ListView::OnResize()
+{
+    ScrollView::OnResize();
+
+    // When in hierarchy mode also need to resize the overlay container
+    if (hierarchyMode_)
+        overlayContainer_->SetSize(scrollPanel_->GetSize());
+}
+
 void ListView::AddItem(UIElement* item)
 {
-    InsertItem(contentElement_->GetNumChildren(), item);
+    InsertItem(M_MAX_UNSIGNED, item);
 }
 
-void ListView::InsertItem(unsigned index, UIElement* item)
+void ListView::InsertItem(unsigned index, UIElement* item, UIElement* parentItem)
 {
     if (!item || item->GetParent() == contentElement_)
         return;
-    
+
     // Enable input so that clicking the item can be detected
     item->SetActive(true);
     item->SetSelected(false);
-    contentElement_->InsertChild(index, item);
-    
+
+    unsigned numItems = contentElement_->GetNumChildren();
+    if (hierarchyMode_)
+    {
+        int baseIndent = 0;
+        if (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)
+            {
+                if (contentElement_->GetChild(indexLimit)->GetIndent() <= baseIndent)
+                    break;
+            }
+            if (index > indexLimit)
+                index = indexLimit;
+        }
+
+        // Use the 'overrided' version to insert the child item
+        static_cast<HierarchyContainer*>(contentElement_.Get())->InsertChild(index, item);
+    }
+    else
+    {
+        if (index > numItems)
+            index = numItems;
+
+        contentElement_->InsertChild(index, item);
+    }
+
     // If necessary, shift the following selections
     if (!selections_.Empty())
     {
@@ -228,18 +358,15 @@ void ListView::InsertItem(unsigned index, UIElement* item)
             if (selections_[i] >= index)
                 ++selections_[i];
         }
-        
+
         UpdateSelectionEffect();
     }
-    
-    if (hierarchyMode_)
-        SetItemExpanded(item, item->IsVisible());
 }
 
 void ListView::RemoveItem(UIElement* item, unsigned index)
 {
     unsigned numItems = GetNumItems();
-    
+
     for (unsigned i = index; i < numItems; ++i)
     {
         if (GetItem(i) == item)
@@ -247,26 +374,49 @@ void ListView::RemoveItem(UIElement* item, unsigned index)
             item->SetSelected(false);
             selections_.Remove(i);
 
-            // Remove any child items in hierarchy mode
             unsigned removed = 1;
             if (hierarchyMode_)
             {
-                int baseIndent = GetItemIndent(item);
-                for (unsigned j = i + 1; ; ++j)
+                // Remove any child items in hierarchy mode
+                if (GetItemHierarchyParent(item))
                 {
-                    UIElement* childItem = GetItem(i + 1);
-                    if (!childItem)
-                        break;
-                    if (GetItemIndent(childItem) > baseIndent)
+                    int baseIndent = item->GetIndent();
+                    for (unsigned j = i + 1; ; ++j)
                     {
-                        childItem->SetSelected(false);
-                        selections_.Erase(j);
-                        contentElement_->RemoveChild(childItem, i + 1);
-                        ++removed;
+                        UIElement* childItem = GetItem(i + 1);
+                        if (!childItem)
+                            break;
+                        if (childItem->GetIndent() > baseIndent)
+                        {
+                            childItem->SetSelected(false);
+                            selections_.Erase(j);
+                            contentElement_->RemoveChildAtIndex(i + 1);
+                            overlayContainer_->RemoveChildAtIndex(i + 1);
+                            ++removed;
+                        }
+                        else
+                            break;
+                    }
+                }
+
+                // Check if the parent of removed item still has other children
+                if (i > 0)
+                {
+                    int baseIndent = item->GetIndent();
+                    UIElement* prevKin = GetItem(i - 1);		// Could be parent or sibling
+                    if (prevKin->GetIndent() < baseIndent)
+                    {
+                        UIElement* nextKin = GetItem(i + 1);	// Could be sibling or parent-sibling or 0 if index out of bound
+                        if (!nextKin || nextKin->GetIndent() < baseIndent)
+                        {
+                            // If we reach here then the parent has no other children
+                            SetItemHierarchyParent(prevKin, false);
+                        }
                     }
-                    else
-                        break;
                 }
+
+                // Remove the overlay at the same index
+                overlayContainer_->RemoveChildAtIndex(i);
             }
 
             // If necessary, shift the following selections
@@ -277,11 +427,11 @@ void ListView::RemoveItem(UIElement* item, unsigned index)
                     if (selections_[j] > i)
                         selections_[j] -= removed;
                 }
-                
+
                 UpdateSelectionEffect();
             }
-            
-            contentElement_->RemoveChild(item, i);
+
+            contentElement_->RemoveChildAtIndex(i);
             break;
         }
     }
@@ -295,13 +445,14 @@ void ListView::RemoveItem(unsigned index)
 void ListView::RemoveAllItems()
 {
     contentElement_->DisableLayoutUpdate();
-    
+
     ClearSelection();
     contentElement_->RemoveAllChildren();
-    
+    if (hierarchyMode_)
+        overlayContainer_->RemoveAllChildren();
+
     contentElement_->EnableLayoutUpdate();
     contentElement_->UpdateLayout();
-    UpdateViewSize();
 }
 
 void ListView::SetSelection(unsigned index)
@@ -315,7 +466,7 @@ void ListView::SetSelection(unsigned index)
 void ListView::SetSelections(const PODVector<unsigned>& indices)
 {
     unsigned numItems = GetNumItems();
-    
+
     // Remove first items that should no longer be selected
     for (PODVector<unsigned>::Iterator i = selections_.Begin(); i != selections_.End();)
     {
@@ -323,9 +474,9 @@ void ListView::SetSelections(const PODVector<unsigned>& indices)
         if (!indices.Contains(index))
         {
             i = selections_.Erase(i);
-            
+
             using namespace ItemSelected;
-            
+
             VariantMap eventData;
             eventData[P_ELEMENT] = (void*)this;
             eventData[P_SELECTION] = index;
@@ -334,9 +485,9 @@ void ListView::SetSelections(const PODVector<unsigned>& indices)
         else
             ++i;
     }
-    
+
     bool added = false;
-    
+
     // Then add missing items
     for (PODVector<unsigned>::ConstIterator i = indices.Begin(); i != indices.End(); ++i)
     {
@@ -352,9 +503,9 @@ void ListView::SetSelections(const PODVector<unsigned>& indices)
                     selections_.Push(index);
                     added = true;
                 }
-                
+
                 using namespace ItemSelected;
-                
+
                 VariantMap eventData;
                 eventData[P_ELEMENT] = (void*)this;
                 eventData[P_SELECTION] = *i;
@@ -365,11 +516,11 @@ void ListView::SetSelections(const PODVector<unsigned>& indices)
         if (!multiselect_)
             break;
     }
-    
+
     // Re-sort selections if necessary
     if (added)
         Sort(selections_.Begin(), selections_.End());
-    
+
     UpdateSelectionEffect();
     SendEvent(E_SELECTIONCHANGED);
 }
@@ -382,21 +533,21 @@ void ListView::AddSelection(unsigned index)
     {
         if (index >= GetNumItems())
             return;
-        
+
         if (!selections_.Contains(index))
         {
             selections_.Push(index);
-            
+
             using namespace ItemSelected;
-            
+
             VariantMap eventData;
             eventData[P_ELEMENT] = (void*)this;
             eventData[P_SELECTION] = index;
             SendEvent(E_ITEMSELECTED, eventData);
-            
+
             Sort(selections_.Begin(), selections_.End());
         }
-        
+
         EnsureItemVisibility(index);
         UpdateSelectionEffect();
         SendEvent(E_SELECTIONCHANGED);
@@ -407,17 +558,17 @@ void ListView::RemoveSelection(unsigned index)
 {
     if (index >= GetNumItems())
         return;
-    
+
     if (selections_.Remove(index))
     {
         using namespace ItemSelected;
-        
+
         VariantMap eventData;
         eventData[P_ELEMENT] = (void*)this;
         eventData[P_SELECTION] = index;
         SendEvent(E_ITEMDESELECTED, eventData);
     }
-    
+
     EnsureItemVisibility(index);
     UpdateSelectionEffect();
     SendEvent(E_SELECTIONCHANGED);
@@ -428,7 +579,7 @@ void ListView::ToggleSelection(unsigned index)
     unsigned numItems = GetNumItems();
     if (index >= numItems)
         return;
-    
+
     if (selections_.Contains(index))
         RemoveSelection(index);
     else
@@ -441,7 +592,7 @@ void ListView::ChangeSelection(int delta, bool additive)
         return;
     if (!multiselect_)
         additive = false;
-    
+
     // If going downwards, use the last selection as a base. Otherwise use first
     unsigned selection = delta > 0 ? selections_.Back() : selections_.Front();
     int direction = delta > 0 ? 1 : -1;
@@ -460,10 +611,10 @@ void ListView::ChangeSelection(int delta, bool additive)
         if (item->IsVisible())
         {
             indices.Push(okSelection = newSelection);
-            delta -= direction;            
+            delta -= direction;
         }
     }
-    
+
     if (!additive)
         SetSelection(okSelection);
     else
@@ -488,7 +639,37 @@ void ListView::SetMultiselect(bool enable)
 
 void ListView::SetHierarchyMode(bool enable)
 {
+    if (enable == hierarchyMode_)
+        return;
+
     hierarchyMode_ = enable;
+    UIElement* container;
+    if (enable)
+    {
+        overlayContainer_ = CreateChild<UIElement>();
+        overlayContainer_->SetInternal(true);
+        overlayContainer_->SetLayoutMode(LM_FREE);
+        overlayContainer_->SetSortChildren(false);
+        overlayContainer_->SetClipChildren(true);
+
+        container = new HierarchyContainer(context_, overlayContainer_);
+    }
+    else
+    {
+        if (overlayContainer_)
+        {
+            RemoveChild(overlayContainer_);
+            overlayContainer_.Reset();
+        }
+
+        container = new UIElement(context_);
+    }
+
+    SetContentElement(container);
+    container->SetInternal(true);
+    container->SetActive(true);
+    container->SetLayout(LM_VERTICAL);
+    container->SetSortChildren(false);
 }
 
 void ListView::SetClearSelectionOnDefocus(bool enable)
@@ -496,7 +677,7 @@ void ListView::SetClearSelectionOnDefocus(bool enable)
     if (enable != clearSelectionOnDefocus_)
     {
         clearSelectionOnDefocus_ = enable;
-    
+
         if (clearSelectionOnDefocus_)
         {
             SubscribeToEvent(this, E_DEFOCUSED, HANDLER(ListView, HandleDefocused));
@@ -518,31 +699,31 @@ void ListView::Expand(unsigned index, bool enable, bool recursive)
 {
     if (!hierarchyMode_)
         return;
-    
+
     unsigned numItems = GetNumItems();
     if (index >= numItems)
         return;
-    
+
     UIElement* item = GetItem(index++);
     SetItemExpanded(item, enable);
-    int baseIndent = GetItemIndent(item);
-    
+    int baseIndent = item->GetIndent();
+
     PODVector<bool> expanded(baseIndent + 1);
     expanded[baseIndent] = enable;
-    
+
     contentElement_->DisableLayoutUpdate();
-    
+
     while (index < numItems)
     {
         item = GetItem(index++);
-        int indent = GetItemIndent(item);
+        int indent = item->GetIndent();
         if (indent <= baseIndent)
             break;
 
         // Propagate the state to children when it is recursive
         if (recursive)
             SetItemExpanded(item, enable);
-        
+
         // Use the parent expanded flag to influence the visibility of its children
         bool visible = enable && expanded[indent - 1];
         item->SetVisible(visible);
@@ -551,21 +732,20 @@ void ListView::Expand(unsigned index, bool enable, bool recursive)
             expanded.Resize(indent + 1);
         expanded[indent] = visible && GetItemExpanded(item);
     }
-    
+
     contentElement_->EnableLayoutUpdate();
     contentElement_->UpdateLayout();
-    UpdateViewSize();
 }
 
 void ListView::ToggleExpand(unsigned index, bool recursive)
 {
     if (!hierarchyMode_)
         return;
-    
+
     unsigned numItems = GetNumItems();
     if (index >= numItems)
         return;
-    
+
     UIElement* item = GetItem(index);
     Expand(index, !GetItemExpanded(item), recursive);
 }
@@ -587,6 +767,16 @@ PODVector<UIElement*> ListView::GetItems() const
     return items;
 }
 
+unsigned ListView::FindItem(UIElement* item) const
+{
+    const Vector<SharedPtr<UIElement> >& children = contentElement_->GetChildren();
+    Vector<SharedPtr<UIElement> >::ConstIterator i = children.Find(SharedPtr<UIElement>(item));
+    if (i != children.End())
+        return i - children.Begin();
+    else
+        return M_MAX_UNSIGNED;
+}
+
 unsigned ListView::GetSelection() const
 {
     if (selections_.Empty())
@@ -603,14 +793,14 @@ UIElement* ListView::GetSelectedItem() const
 PODVector<UIElement*> ListView::GetSelectedItems() const
 {
     PODVector<UIElement*> ret;
-    
+
     for (PODVector<unsigned>::ConstIterator i = selections_.Begin(); i != selections_.End(); ++i)
     {
         UIElement* item = GetItem(*i);
         if (item)
             ret.Push(item);
     }
-    
+
     return ret;
 }
 
@@ -619,6 +809,11 @@ bool ListView::IsSelected(unsigned index) const
     return selections_.Contains(index);
 }
 
+bool ListView::IsExpanded(unsigned index) const
+{
+    return GetItemExpanded(contentElement_->GetChild(index));
+}
+
 float ListView::GetDoubleClickInterval() const
 {
     return (float)doubleClickInterval_ / 1000.0f;
@@ -628,7 +823,7 @@ void ListView::UpdateSelectionEffect()
 {
     unsigned numItems = GetNumItems();
     bool highlighted = highlightMode_ == HM_ALWAYS || HasFocus();
-    
+
     for (unsigned i = 0; i < numItems; ++i)
     {
         UIElement* item = GetItem(i);
@@ -648,18 +843,18 @@ void ListView::EnsureItemVisibility(UIElement* item)
 {
     if (!item || !item->IsVisible())
         return;
-    
+
     IntVector2 newView = GetViewPosition();
     IntVector2 currentOffset = item->GetPosition() - newView;
     const IntRect& clipBorder = scrollPanel_->GetClipBorder();
     IntVector2 windowSize(scrollPanel_->GetWidth() - clipBorder.left_ - clipBorder.right_, scrollPanel_->GetHeight() -
         clipBorder.top_ - clipBorder.bottom_);
-    
+
     if (currentOffset.y_ < 0)
         newView.y_ += currentOffset.y_;
     if (currentOffset.y_ + item->GetHeight() > windowSize.y_)
         newView.y_ += currentOffset.y_ + item->GetHeight() - windowSize.y_;
-    
+
     SetViewPosition(newView);
 }
 
@@ -668,9 +863,9 @@ void ListView::HandleUIMouseClick(StringHash eventType, VariantMap& eventData)
     if (eventData[UIMouseClick::P_BUTTON].GetInt() != MOUSEB_LEFT)
         return;
     int qualifiers = eventData[UIMouseClick::P_QUALIFIERS].GetInt();
-    
+
     UIElement* element = static_cast<UIElement*>(eventData[UIMouseClick::P_ELEMENT].GetPtr());
-    
+
     unsigned numItems = GetNumItems();
     for (unsigned i = 0; i < numItems; ++i)
     {
@@ -684,7 +879,7 @@ void ListView::HandleUIMouseClick(StringHash eventType, VariantMap& eventData)
                     lastClickedItem_ = i;
                 SetSelection(i);
             }
-            
+
             // Check multiselect with shift & ctrl
             if (multiselect_)
             {
@@ -731,7 +926,7 @@ void ListView::HandleUIMouseClick(StringHash eventType, VariantMap& eventData)
                 else if (qualifiers & QUAL_CTRL)
                     ToggleSelection(i);
             }
-            
+
             if (isDoubleClick)
             {
                 VariantMap eventData;
@@ -739,7 +934,7 @@ void ListView::HandleUIMouseClick(StringHash eventType, VariantMap& eventData)
                 eventData[ItemDoubleClicked::P_SELECTION] = i;
                 SendEvent(E_ITEMDOUBLECLICKED, eventData);
             }
-            
+
             return;
         }
     }
@@ -748,7 +943,7 @@ void ListView::HandleUIMouseClick(StringHash eventType, VariantMap& eventData)
 void ListView::HandleFocusChanged(StringHash eventType, VariantMap& eventData)
 {
     using namespace FocusChanged;
-    
+
     UIElement* element = static_cast<UIElement*>(eventData[P_ELEMENT].GetPtr());
     while (element)
     {

+ 16 - 5
Engine/UI/ListView.h

@@ -23,7 +23,6 @@
 #pragma once
 
 #include "ScrollView.h"
-#include "HashSet.h"
 
 namespace Urho3D
 {
@@ -54,11 +53,16 @@ public:
     
     /// React to a key press.
     virtual void OnKey(int key, int buttons, int qualifiers);
-    
+    /// React to resize.
+    virtual void OnResize();
+
     /// Add item to the end of the list.
     void AddItem(UIElement* item);
-    /// Insert item at a specific position.
-    void InsertItem(unsigned index, UIElement* item);
+    /// \brief Insert item at a specific index. In hierarchy mode, the optional parameter will be used to determine the child's indent level in respect to its parent.
+    /// If index is greater than the total items then the new item is inserted at the end of the list.
+    /// In hierarchy mode, if index is greater than the index of last children of the specified parent item then the new item is inserted next to the last children.
+    /// And if the index is lesser than the index of the parent item itself then the new item is inserted before the first child item.
+    void InsertItem(unsigned index, UIElement* item, UIElement* parentItem = 0);
     /// Remove specific item, starting search at the specified index if provided. In hierarchy mode will also remove any children.
     void RemoveItem(UIElement* item, unsigned index = 0);
     /// Remove item at index. In hierarchy mode will also remove any children.
@@ -83,7 +87,8 @@ public:
     void SetHighlightMode(HighlightMode mode);
     /// Enable multiselect.
     void SetMultiselect(bool enable);
-    /// Enable hierarchy mode. Allows hiding/showing items that have "indent" userdata.
+    /// \brief Enable hierarchy mode. Allows items to have parent-child relationship at different indent level and the ability to expand/collapse child items.
+    /// All items in the list will be lost during mode change.
     void SetHierarchyMode(bool enable);
     /// Enable clearing of selection on defocus.
     void SetClearSelectionOnDefocus(bool enable);
@@ -101,6 +106,8 @@ public:
     UIElement* GetItem(unsigned index) const;
     /// Return all items.
     PODVector<UIElement*> GetItems() const;
+    /// Return index of item, or M_MAX_UNSIGNED If not found.
+    unsigned FindItem(UIElement* item) const;
     /// Return first selected index, or M_MAX_UNSIGNED if none selected.
     unsigned GetSelection() const;
     /// Return all selected indices.
@@ -111,6 +118,8 @@ public:
     PODVector<UIElement*> GetSelectedItems() const;
     /// Return whether an item at index is seleccted.
     bool IsSelected(unsigned index) const;
+    /// Return whether an item at index has its children expanded (in hierachy mode only).
+    bool IsExpanded(unsigned index) const;
     /// Return highlight mode.
     HighlightMode GetHighlightMode() const { return highlightMode_; }
     /// Return whether multiselect enabled.
@@ -138,6 +147,8 @@ protected:
     bool multiselect_;
     /// Hierarchy mode flag.
     bool hierarchyMode_;
+    /// Overlay container, used in hierarchy mode only.
+    SharedPtr<UIElement> overlayContainer_;
     /// Clear selection on defocus flag.
     bool clearSelectionOnDefocus_;
     /// Doubleclick interval.

+ 1 - 0
Engine/UI/ScrollView.cpp

@@ -284,6 +284,7 @@ void ScrollView::UpdatePanelSize()
     {
         IntRect panelBorder = scrollPanel_->GetClipBorder();
         contentElement_->SetWidth(scrollPanel_->GetWidth() - panelBorder.left_ - panelBorder.right_);
+        UpdateViewSize();
     }
     
     ignoreEvents_ = false;

+ 6 - 4
Engine/UI/Text.cpp

@@ -520,14 +520,16 @@ int Text::GetRowStartPosition(unsigned rowIndex) const
     if (rowIndex < rowWidths_.Size())
         rowWidth = rowWidths_[rowIndex];
     
+    int ret = GetIndentWidth();
+
     switch (textAlignment_)
     {
-    default:
-        return 0;
+    case HA_LEFT:
+        return ret;
     case HA_CENTER:
-        return (GetSize().x_ - rowWidth) / 2;
+        return ret + (GetSize().x_ - rowWidth) / 2;
     case HA_RIGHT:
-        return GetSize().x_ - rowWidth;
+        return ret + GetSize().x_ - rowWidth;
     }
 }
 

+ 10 - 2
Engine/UI/UI.cpp

@@ -393,7 +393,14 @@ SharedPtr<UIElement> UI::LoadLayout(XMLFile* file, XMLFile* styleFile)
         LOGERROR("Could not create unknown UI element " + typeName);
         return root;
     }
-    
+
+    if (styleFile)
+        // Set it as default for later use by children elements
+        root->SetDefaultStyle(styleFile);
+    else
+        // Use default style file of the root element if it has one
+        styleFile = rootElement_->GetDefaultStyle(false);
+
     root->LoadXML(rootElem, styleFile);
     return root;
 }
@@ -670,10 +677,11 @@ void UI::HandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
 {
     mouseButtons_ = eventData[MouseButtonDown::P_BUTTONS].GetInt();
     qualifiers_ = eventData[MouseButtonDown::P_QUALIFIERS].GetInt();
-    int button = eventData[MouseButtonDown::P_BUTTON].GetInt();
     
     if (cursor_ && cursor_->IsVisible())
     {
+        int button = eventData[MouseButtonDown::P_BUTTON].GetInt();
+
         IntVector2 pos = cursor_->GetPosition();
         WeakPtr<UIElement> element(GetElementAt(pos));
         

+ 0 - 2
Engine/UI/UIBatch.cpp

@@ -26,8 +26,6 @@
 #include "Texture.h"
 #include "UIElement.h"
 
-#include <cstring>
-
 #include "DebugNew.h"
 
 namespace Urho3D

+ 64 - 11
Engine/UI/UIElement.cpp

@@ -131,6 +131,7 @@ UIElement::UIElement(Context* context) :
     resizeNestingLevel_(0),
     layoutNestingLevel_(0),
     layoutMinSize_(0),
+    indent_(0),
     position_(IntVector2::ZERO),
     size_(IntVector2::ZERO),
     minSize_(IntVector2::ZERO),
@@ -143,7 +144,8 @@ UIElement::UIElement(Context* context) :
     opacityDirty_(true),
     derivedColorDirty_(true),
     sortOrderDirty_(false),
-    colorGradient_(false)
+    colorGradient_(false),
+    indentSpacing_(16)
 {
 }
 
@@ -188,6 +190,8 @@ void UIElement::RegisterObject(Context* context)
     ENUM_ACCESSOR_ATTRIBUTE(UIElement, "Layout Mode", GetLayoutMode, SetLayoutMode, LayoutMode, layoutModes, LM_FREE, AM_FILE);
     ACCESSOR_ATTRIBUTE(UIElement, VAR_INT, "Layout Spacing", GetLayoutSpacing, SetLayoutSpacing, int, 0, AM_FILE);
     REF_ACCESSOR_ATTRIBUTE(UIElement, VAR_INTRECT, "Layout Border", GetLayoutBorder, SetLayoutBorder, IntRect, IntRect::ZERO, AM_FILE);
+    ACCESSOR_ATTRIBUTE(UIElement, VAR_INT, "Indent", GetIndent, SetIndent, int, 0, AM_FILE);
+    ACCESSOR_ATTRIBUTE(UIElement, VAR_INT, "Indent Spacing", GetIndentSpacing, SetIndentSpacing, int, 16, AM_FILE);
     REF_ACCESSOR_ATTRIBUTE(UIElement, VAR_INTVECTOR2, "Rotation Pivot", GetRotationPivot, SetRotationPivot, IntVector2, IntVector2::ZERO, AM_FILE);
     ACCESSOR_ATTRIBUTE(UIElement, VAR_FLOAT, "Rotation", GetRotation, SetRotation, float, 0.0f, AM_FILE);
     ATTRIBUTE(UIElement, VAR_VARIANTMAP, "Variables", vars_, Variant::emptyVariantMap, AM_FILE);
@@ -690,6 +694,11 @@ void UIElement::SetStyleAuto(XMLFile* file)
     SetStyle(file, GetTypeName());
 }
 
+void UIElement::SetDefaultStyle(XMLFile* style)
+{
+    defaultStyle_ = style;
+}
+
 void UIElement::SetLayout(LayoutMode mode, int spacing, const IntRect& border)
 {
     layoutMode_ = mode;
@@ -716,6 +725,16 @@ void UIElement::SetLayoutBorder(const IntRect& border)
     UpdateLayout();
 }
 
+void UIElement::SetIndent(int indent)
+{
+    indent_ = indent;
+}
+
+void UIElement::SetIndentSpacing(int indentSpacing)
+{
+    indentSpacing_ = Max(indentSpacing, 0);
+}
+
 void UIElement::SetRotationPivot(const IntVector2& pivot)
 {
     rotationPivot_ = pivot;
@@ -739,6 +758,8 @@ void UIElement::UpdateLayout()
     PODVector<int> minSizes;
     PODVector<int> maxSizes;
     
+    int baseIndent = GetIndentWidth();
+
     if (layoutMode_ == LM_HORIZONTAL)
     {
         int minChildHeight = 0;
@@ -747,15 +768,15 @@ void UIElement::UpdateLayout()
         {
             if (!children_[i]->IsVisible())
                 continue;
-            positions.Push(0);
-            sizes.Push(children_[i]->GetWidth());
-            minSizes.Push(children_[i]->GetMinWidth());
-            maxSizes.Push(children_[i]->GetMaxWidth());
+            positions.Push(baseIndent);
+            unsigned indent = children_[i]->GetIndentWidth();
+            sizes.Push(children_[i]->GetWidth() + indent);
+            minSizes.Push(children_[i]->GetMinWidth() + indent);
+            maxSizes.Push(children_[i]->GetMaxWidth() + indent);
             minChildHeight = Max(minChildHeight, children_[i]->GetMinHeight());
         }
         
-        CalculateLayout(positions, sizes, minSizes, maxSizes, GetWidth(), layoutBorder_.left_, layoutBorder_.right_,
-            layoutSpacing_);
+        CalculateLayout(positions, sizes, minSizes, maxSizes, GetWidth(), layoutBorder_.left_, layoutBorder_.right_, layoutSpacing_);
         
         int width = CalculateLayoutParentSize(sizes, layoutBorder_.left_, layoutBorder_.right_, layoutSpacing_);
         int height = Max(GetHeight(), minChildHeight + layoutBorder_.top_ + layoutBorder_.bottom_);
@@ -790,15 +811,14 @@ void UIElement::UpdateLayout()
         {
             if (!children_[i]->IsVisible())
                 continue;
-            positions.Push(0);
+            positions.Push(baseIndent);
             sizes.Push(children_[i]->GetHeight());
             minSizes.Push(children_[i]->GetMinHeight());
             maxSizes.Push(children_[i]->GetMaxHeight());
-            minChildWidth = Max(minChildWidth, children_[i]->GetMinWidth());
+            minChildWidth = Max(minChildWidth, children_[i]->GetMinWidth() + children_[i]->GetIndentWidth());
         }
         
-        CalculateLayout(positions, sizes, minSizes, maxSizes, GetHeight(), layoutBorder_.top_, layoutBorder_.bottom_,
-            layoutSpacing_);
+        CalculateLayout(positions, sizes, minSizes, maxSizes, GetHeight(), layoutBorder_.top_, layoutBorder_.bottom_, layoutSpacing_);
         
         int height = CalculateLayoutParentSize(sizes, layoutBorder_.top_, layoutBorder_.bottom_, layoutSpacing_);
         int width = Max(GetWidth(), minChildWidth + layoutBorder_.left_ + layoutBorder_.right_);
@@ -824,6 +844,12 @@ void UIElement::UpdateLayout()
         }
     }
     
+    using namespace LayoutUpdated;
+
+    VariantMap eventData;
+    eventData[P_ELEMENT] = (void*)this;
+    SendEvent(E_LAYOUTUPDATED, eventData);
+
     EnableLayoutUpdate();
 }
 
@@ -953,6 +979,16 @@ void UIElement::RemoveChild(UIElement* element, unsigned index)
     }
 }
 
+void UIElement::RemoveChildAtIndex(unsigned index)
+{
+    if (index >= children_.Size())
+        return;
+
+    children_[index]->Detach();
+    children_.Erase(index);
+    UpdateLayout();
+}
+
 void UIElement::RemoveAllChildren()
 {
     for (Vector<SharedPtr<UIElement> >::Iterator i = children_.Begin(); i < children_.End(); )
@@ -1065,6 +1101,23 @@ bool UIElement::HasFocus() const
         return ui->GetFocusElement() == this;
 }
 
+XMLFile* UIElement::GetDefaultStyle(bool recursiveUp) const
+{
+    if (recursiveUp)
+    {
+        const UIElement* element = this;
+        while (element)
+        {
+            if (element->defaultStyle_)
+                return element->defaultStyle_;
+            element = element->parent_;
+        }
+        return 0;
+    }
+    else
+        return defaultStyle_;
+}
+
 void UIElement::GetChildren(PODVector<UIElement*>& dest, bool recursive) const
 {
     dest.Clear();

+ 24 - 2
Engine/UI/UIElement.h

@@ -235,6 +235,8 @@ public:
     void SetStyle(const XMLElement& element);
     /// Set style from an XML file. Find the style element automatically.
     void SetStyleAuto(XMLFile* file);
+    /// Set default style for later use by children elements.
+    void SetDefaultStyle(XMLFile* style);
     /// Set layout.
     void SetLayout(LayoutMode mode, int spacing = 0, const IntRect& border = IntRect::ZERO);
     /// Set layout mode only.
@@ -243,6 +245,10 @@ public:
     void SetLayoutSpacing(int spacing);
     /// Set layout border.
     void SetLayoutBorder(const IntRect& border);
+    /// Set horizontal indentation.
+    void SetIndent(int indent);
+    /// Set indent spacing (number of pixels per indentation level).
+    void SetIndentSpacing(int indentSpacing);
     /// Set content rotation pivot offset.
     void SetRotationPivot(const IntVector2& pivot);
     /// Set content rotation angle in degrees. Positive is clockwise. Note: child elements do not rotate.
@@ -263,6 +269,8 @@ public:
     void InsertChild(unsigned index, UIElement* element);
     /// Remove a child element. Starting search at specified index if provided.
     void RemoveChild(UIElement* element, unsigned index = 0);
+    /// Remove a child element at index.
+    void RemoveChildAtIndex(unsigned index);
     /// Remove all child elements.
     void RemoveAllChildren();
     /// Remove from the parent element. If no other shared pointer references exist, causes immediate deletion.
@@ -344,6 +352,8 @@ public:
     FocusMode GetFocusMode() const { return focusMode_; }
     /// Return drag and drop flags.
     unsigned GetDragDropMode() const { return dragDropMode_; }
+    /// Return default style.
+    XMLFile* GetDefaultStyle(bool recursiveUp = true) const;
     /// Return layout mode.
     LayoutMode GetLayoutMode() const { return layoutMode_; }
     /// Return layout spacing.
@@ -389,6 +399,12 @@ public:
     void SortChildren();
     /// Return minimum layout element size in the layout direction. Only valid after layout has been calculated.
     int GetLayoutMinSize() const { return layoutMinSize_; }
+    /// Return horizontal indentation.
+    int GetIndent() const { return indent_; }
+    /// Return indent spacing (number of pixels per indentation level).
+    int GetIndentSpacing() const { return indentSpacing_; }
+    /// Return indent width in pixels.
+    int GetIndentWidth() const { return indent_ * indentSpacing_; }
     
     /// Set child offset.
     void SetChildOffset(const IntVector2& offset);
@@ -405,7 +421,7 @@ public:
         currentScissor);
     /// Get color attribute. Uses just the top-left color.
     const Color& GetColorAttr() const { return color_[0]; }
-    
+
 protected:
     /// Mark screen position as needing an update.
     void MarkDirty();
@@ -464,7 +480,9 @@ protected:
     unsigned layoutNestingLevel_;
     /// Layout element minimum size in layout direction.
     int layoutMinSize_;
-    
+    /// Horizontal indentation.
+    int indent_;
+
 private:
     /// Return child elements recursively.
     void GetChildrenRecursive(PODVector<UIElement*>& dest) const;
@@ -509,6 +527,10 @@ private:
     bool sortOrderDirty_;
     /// Has color gradient flag.
     bool colorGradient_;
+    /// Indent spacing (number of pixels per indentation level).
+    int indentSpacing_;
+    /// Default style file.
+    SharedPtr<XMLFile> defaultStyle_;
 };
 
 template <class T> T* UIElement::CreateChild(const String& name) { return static_cast<T*>(CreateChild(T::GetTypeStatic(), name)); }

+ 6 - 0
Engine/UI/UIEvents.h

@@ -88,6 +88,12 @@ EVENT(E_DEFOCUSED, Defocused)
     PARAM(P_ELEMENT, Element);              // UIElement pointer
 }
 
+/// UI element layout updated.
+EVENT(E_LAYOUTUPDATED, LayoutUpdated)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+}
+
 /// UI button pressed.
 EVENT(E_PRESSED, Pressed)
 {