소스 검색

Laying the groundwork in the Attribute Inspector for showing the UIElement's attributes (still need more work). Enhanced UI subsystem to support modal element, currently only support modal Window. Exposed a new Variant readonly property to test for 'empty' variant. New UIElement's method to get a child by matching the child's user-defined variant map and exposed it to script, also exposed the existing GetVar() method to script.

Wei Tjong Yao 12 년 전
부모
커밋
2bf7facaa7

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

@@ -48,7 +48,7 @@ void Start()
     // Create user interface for the editor
     CreateUI();
     // Create root UI element where all 'editable' UI elements would be parented to
-    CreateUIElement();
+    CreateRootUIElement();
     // Load the initial scene if provided
     ParseArguments();
 }

+ 30 - 50
Bin/Data/Scripts/Editor/AttributeEditor.as

@@ -115,7 +115,7 @@ LineEdit@ CreateAttributeLineEdit(UIElement@ parent, Array<Serializable@>@ seria
     attrEdit.vars["SubIndex"] = subIndex;
     SetAttributeEditorID(attrEdit, serializables);
     parent.AddChild(attrEdit);
-    
+
     return attrEdit;
 }
 
@@ -126,7 +126,7 @@ UIElement@ CreateStringAttributeEditor(ListView@ list, Array<Serializable@>@ ser
     attrEdit.dragDropMode = DD_TARGET;
     SubscribeToEvent(attrEdit, "TextChanged", "EditAttribute");
     SubscribeToEvent(attrEdit, "TextFinished", "EditAttribute");
-    
+
     return parent;
 }
 
@@ -137,7 +137,7 @@ UIElement@ CreateBoolAttributeEditor(ListView@ list, Array<Serializable@>@ seria
         parent = CreateAttributeEditorParentAsListChild(list, info.name, index, subIndex);
     else
         parent = CreateAttributeEditorParent(list, info.name, index, subIndex);
-    
+
     CheckBox@ attrEdit = CheckBox();
     attrEdit.style = uiStyle;
     attrEdit.SetFixedSize(16, 16);
@@ -146,7 +146,7 @@ UIElement@ CreateBoolAttributeEditor(ListView@ list, Array<Serializable@>@ seria
     SetAttributeEditorID(attrEdit, serializables);
     parent.AddChild(attrEdit);
     SubscribeToEvent(attrEdit, "Toggled", "EditAttribute");
-    
+
     return parent;
 }
 
@@ -161,7 +161,7 @@ UIElement@ CreateNumAttributeEditor(ListView@ list, Array<Serializable@>@ serial
         numCoords = 4;
     else if (type == VAR_INTVECTOR2)
         numCoords = 2;
-    
+
     for (uint i = 0; i < numCoords; ++i)
     {
         LineEdit@ attrEdit = CreateAttributeLineEdit(parent, serializables, index, subIndex);
@@ -169,8 +169,8 @@ UIElement@ CreateNumAttributeEditor(ListView@ list, Array<Serializable@>@ serial
         SubscribeToEvent(attrEdit, "TextChanged", "EditAttribute");
         SubscribeToEvent(attrEdit, "TextFinished", "EditAttribute");
     }
-    
-    return parent;    
+
+    return parent;
 }
 
 UIElement@ CreateIntAttributeEditor(ListView@ list, Array<Serializable@>@ serializables, const AttributeInfo&in info, uint index, uint subIndex)
@@ -228,9 +228,9 @@ UIElement@ CreateResourceRefAttributeEditor(ListView@ list, Array<Serializable@>
     container.SetLayout(LM_HORIZONTAL, 4, IntRect(info.name.StartsWith("   ") ? 20 : 10, 0, 4, 0));    // Left margin is indented more when the name is so
     container.SetFixedHeight(ATTR_HEIGHT);
     parent.AddChild(container);
-        
+
     LineEdit@ attrEdit = CreateAttributeLineEdit(container, serializables, index, subIndex);
-    attrEdit.vars["Type"] = resourceType.value;
+    attrEdit.vars[TYPE_VAR] = resourceType.value;
     SubscribeToEvent(attrEdit, "TextFinished", "EditAttribute");
 
     Button@ pickButton = Button();
@@ -262,14 +262,14 @@ UIElement@ CreateResourceRefAttributeEditor(ListView@ list, Array<Serializable@>
     openButton.AddChild(openButtonText);
     container.AddChild(openButton);
     SubscribeToEvent(openButton, "Released", "OpenResource");
-    
+
     return parent;
 }
 
 UIElement@ CreateAttributeEditor(ListView@ list, Array<Serializable@>@ serializables, const AttributeInfo&in info, uint index, uint subIndex, bool suppressedSeparatedLabel = false)
 {
     UIElement@ parent;
-    
+
     VariantType type = info.type;
     if (type == VAR_STRING || type == VAR_BUFFER)
         parent = CreateStringAttributeEditor(list, serializables, info, index, subIndex);
@@ -284,7 +284,7 @@ UIElement@ CreateAttributeEditor(ListView@ list, Array<Serializable@>@ serializa
     else if (type == VAR_RESOURCEREFLIST)
     {
         uint numRefs = serializables[0].attributes[index].GetResourceRefList().length;
-        
+
         // Straightly speaking the individual resource reference in the list is not an attribute of the serializable
         // However, the AttributeInfo structure is used here to reduce the number of parameters being passed in the function
         AttributeInfo refInfo;
@@ -320,7 +320,7 @@ UIElement@ CreateAttributeEditor(ListView@ list, Array<Serializable@>@ serializa
         for (uint i = 0; i < keys.length; ++i)
         {
             Variant value = map[keys[i]];
-            
+
             // The individual variant in the map is not an attribute of the serializable, the structure is reused for convenience
             AttributeInfo mapInfo;
             mapInfo.name = scene.GetVarName(keys[i]) + " (Var)";
@@ -371,7 +371,7 @@ UIElement@ GetAttributeEditorParent(UIElement@ parent, uint index, uint subIndex
 void LoadAttributeEditor(ListView@ list, Array<Serializable@>@ serializables, const AttributeInfo&in info, uint index)
 {
     bool editable = info.mode & AM_NOEDIT == 0;
-    
+
     UIElement@ parent = GetAttributeEditorParent(list, index, 0);
     if (parent is null)
         return;
@@ -407,13 +407,13 @@ void LoadAttributeEditor(UIElement@ parent, const Variant&in value, const Attrib
     {
         bool modified;
         if (info.defaultValue.type == VAR_NONE || info.defaultValue.type == VAR_RESOURCEREFLIST)
-            modified = !value.IsZero();
+            modified = !value.zero;
         else
             modified = value != info.defaultValue;
         cast<Text>(label).color = (editable ? (modified ? modifiedTextColor : normalTextColor) : nonEditableTextColor);
     }
-    
-    VariantType type = info.type;    
+
+    VariantType type = info.type;
     if (type == VAR_FLOAT || type == VAR_STRING || type == VAR_BUFFER)
         SetEditable(SetValue(parent.children[1], value.ToString(), sameValue), editable && sameValue);
     else if (type == VAR_BOOL)
@@ -440,7 +440,7 @@ void LoadAttributeEditor(UIElement@ parent, const Variant&in value, const Attrib
             parent = GetAttributeEditorParent(list, index, subIndex);
             if (parent is null)
                 break;
-            
+
             StringHash firstID = refList.ids[subIndex];
             bool idSameValue = true;
             if (!sameValue)
@@ -468,7 +468,7 @@ void LoadAttributeEditor(UIElement@ parent, const Variant&in value, const Attrib
             parent = GetAttributeEditorParent(list, index, subIndex);
             if (parent is null)
                 break;
-            
+
             Variant firstValue = vector[subIndex];
             bool varSameValue = true;
             if (!sameValue)
@@ -484,7 +484,7 @@ void LoadAttributeEditor(UIElement@ parent, const Variant&in value, const Attrib
                     }
                 }
             }
-            
+
             // The individual variant in the list is not an attribute of the serializable, the structure is reused for convenience
             AttributeInfo info;
             info.type = firstValue.type;
@@ -501,7 +501,7 @@ void LoadAttributeEditor(UIElement@ parent, const Variant&in value, const Attrib
             parent = GetAttributeEditorParent(list, index, subIndex);
             if (parent is null)
                 break;
-            
+
             Variant firstValue = map[keys[subIndex]];
             bool varSameValue = true;
             if (!sameValue)
@@ -517,7 +517,7 @@ void LoadAttributeEditor(UIElement@ parent, const Variant&in value, const Attrib
                     }
                 }
             }
-            
+
             // The individual variant in the map is not an attribute of the serializable, the structure is reused for convenience
             AttributeInfo info;
             info.type = firstValue.type;
@@ -530,11 +530,11 @@ void LoadAttributeEditor(UIElement@ parent, const Variant&in value, const Attrib
         for (uint i = 0; i < values.length; ++i)
         {
             Variant value = values[i];
-            
+
             // Convert Quaternion value to Vector3 value first
             if (type == VAR_QUATERNION)
                 value = value.GetQuaternion().eulerAngles;
-            
+
             coordinates.Push(value.ToString().Split(' '));
         }
         for (uint i = 0; i < coordinates[0].length; ++i)
@@ -660,7 +660,7 @@ void GetEditorValue(UIElement@ parent, VariantType type, Array<String>@ enumName
         LineEdit@ attrEdit = parent.children[0];
         ResourceRef ref;
         ref.id = StringHash(attrEdit.text.Trimmed());
-        ref.type = ShortStringHash(attrEdit.vars["Type"].GetUInt());
+        ref.type = ShortStringHash(attrEdit.vars[TYPE_VAR].GetUInt());
         FillValue(values, Variant(ref));
     }
     else
@@ -698,16 +698,6 @@ void UpdateAttributes(Array<Serializable@>@ serializables, ListView@ list, bool
             if (!children[i].internal)
                 children[i].Remove();
         }
-
-        //\todo Remove the hardcoding
-        // Resize the node editor according to the number of variables, up to a certain maximum
-        if (list.name == "NodeAttributeList")
-        {
-            if ((editNode !is null && editNode.typeName == "Node") || (editNodes.length > 0 && editNodes[0].typeName == "Node"))
-                --count;	// The 'Is Enabled' attribute is not inserted as list item
-            uint maxAttrs = Clamp(count, MIN_NODE_ATTRIBUTES, MAX_NODE_ATTRIBUTES);
-            list.SetFixedHeight(maxAttrs * ATTR_HEIGHT + 2);
-        }
     }
 
     if (serializables.empty)
@@ -725,7 +715,7 @@ void UpdateAttributes(Array<Serializable@>@ serializables, ListView@ list, bool
 
         LoadAttributeEditor(list, serializables, info, i);
     }
-    
+
     if (fullUpdate)
         list.viewPosition = oldViewPos;
 }
@@ -757,7 +747,7 @@ void EditAttribute(StringHash eventType, VariantMap& eventData)
     // If not an intermediate edit, reload the editor fields with validated values
     // (attributes may have interactions; therefore we load everything, not just the value being edited)
     if (!intermediateEdit)
-        UpdateAttributes(false);
+        UpdateAttributeInspector(false);
 }
 
 // Resource picker functionality
@@ -910,7 +900,7 @@ void PickResourceDone(StringHash eventType, VariantMap& eventData)
         break;
     }
     if (res is null) {
-        Print("Can not find resource type: " + resourcePicker.resourceType + " Name:" +resourceName);
+        log.Warning("Cannot find resource type: " + resourcePicker.resourceType + " Name:" +resourceName);
         return;
     }
 
@@ -950,27 +940,17 @@ void PickResourceDone(StringHash eventType, VariantMap& eventData)
     }
 
     PostEditAttribute(resourceTargets, resourcePickIndex);
-    UpdateAttributes(false);
+    UpdateAttributeInspector(false);
 
     resourceTargets.Clear();
     @resourcePicker = null;
 }
 
-void PickResourceCanceled()
-{
-    if (!resourceTargets.empty)
-    {
-        resourceTargets.Clear();
-        @resourcePicker = null;
-        CloseFileSelector();
-    }
-}
-
 void OpenResource(StringHash eventType, VariantMap& eventData)
 {
     UIElement@ button = eventData["Element"].GetUIElement();
     LineEdit@ attrEdit = button.parent.children[0];
-    
+
     String fileName = attrEdit.text.Trimmed();
     if (fileName.empty)
         return;

+ 13 - 7
Bin/Data/Scripts/Editor/EditorGizmo.as

@@ -31,6 +31,13 @@ class GizmoAxis
 
     void Update(Ray cameraRay, float scale, bool drag)
     {
+        // Do not select when UI has modal element
+        if (ui.modalElement !is null)
+        {
+            selected = false;
+            return;
+        }
+        
         Vector3 closest = cameraRay.ClosestPoint(axisRay);
         Vector3 projected = axisRay.Project(closest);
         d = axisRay.Distance(closest);
@@ -49,7 +56,7 @@ class GizmoAxis
             lastD = d;
         }
     }
-    
+
     void Moved()
     {
         lastT = t;
@@ -98,7 +105,6 @@ void ShowGizmo()
     }
 }
 
-
 void UpdateGizmo()
 {
     UseGizmo();
@@ -270,7 +276,7 @@ void UseGizmo()
 
             moved = ScaleNodes(adjust);
         }
-        
+
         if (moved)
         {
             GizmoMoved();
@@ -330,7 +336,7 @@ bool MoveNodes(Vector3 adjust)
                 moved = true;
         }
     }
-    
+
     return moved;
 }
 
@@ -368,7 +374,7 @@ bool RotateNodes(Vector3 adjust)
             }
         }
     }
-    
+
     return moved;
 }
 
@@ -384,7 +390,7 @@ bool ScaleNodes(Vector3 adjust)
 
             Vector3 scale = node.scale;
             Vector3 oldScale = scale;
-            
+
             if (!scaleSnap)
                 scale += adjust;
             else
@@ -412,6 +418,6 @@ bool ScaleNodes(Vector3 adjust)
             node.scale = scale;
         }
     }
-    
+
     return moved;
 }

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

@@ -336,7 +336,7 @@ void ImportTundraScene(const String&in fileName)
             childNode.parent = parentNode;
     }
 
-    UpdateHierarchyWindowItem(editorScene, true);
+    UpdateHierarchyItem(editorScene, true);
     UpdateWindowTitle();
     assetMappings.Clear();
 }

+ 147 - 125
Bin/Data/Scripts/Editor/EditorNodeWindow.as

@@ -1,93 +1,131 @@
 // Urho3D editor attribute inspector window handling
 #include "Scripts/Editor/AttributeEditor.as"
 
-Window@ nodeWindow;
+Window@ attributeInspectorWindow;
+UIElement@ parentContainer;
 UIElement@ componentParentContainer;
+UIElement@ nodeContainer;
+XMLFile@ nodeXMLResource;
 XMLFile@ componentXMLResource;
 
 bool applyMaterialList = true;
 bool attributesDirty = false;
 
 const String STRIKED_OUT = "——";   // Two unicode EM DASH (U+2014)
+const ShortStringHash NODE_IDS_VAR("NodeIDs");
+const ShortStringHash COMPONENT_IDS_VAR("ComponentIDs");
+
+uint componentContainerStartIndex = 0;
+
+void AddNodeContainer()
+{
+    if (nodeContainer !is null)
+        return;
+
+    uint index = parentContainer.numChildren;
+    parentContainer.LoadXML(nodeXMLResource, uiStyle);
+    nodeContainer = GetContainer(index);
+    SubscribeToEvent(nodeContainer.GetChild("NewVarDropDown", true), "ItemSelected", "CreateNewVariable");
+    SubscribeToEvent(nodeContainer.GetChild("DeleteVarButton", true), "Released", "DeleteVariable");
+    ++componentContainerStartIndex;
+}
 
 void AddComponentContainer()
 {
-    componentParentContainer.LoadXML(componentXMLResource, uiStyle);
+    parentContainer.LoadXML(componentXMLResource, uiStyle);
+}
+
+void DeleteAllContainers()
+{
+    parentContainer.RemoveAllChildren();
+    nodeContainer = null;
+    componentContainerStartIndex = 0;
 }
 
-void DeleteAllComponentContainers()
+UIElement@ GetContainer(uint index)
 {
-    componentParentContainer.RemoveAllChildren();
+    return parentContainer.children[index];
 }
 
 UIElement@ GetComponentContainer(uint index)
 {
-    return componentParentContainer.children[index];
+    return parentContainer.children[componentContainerStartIndex + index];
 }
 
-void CreateNodeWindow()
+void CreateAttributeInspectorWindow()
 {
-    if (nodeWindow !is null)
+    if (attributeInspectorWindow !is null)
         return;
 
     InitResourcePicker();
     InitVectorStructs();
 
-    nodeWindow = ui.LoadLayout(cache.GetResource("XMLFile", "UI/EditorNodeWindow.xml"));
+    attributeInspectorWindow = ui.LoadLayout(cache.GetResource("XMLFile", "UI/EditorNodeWindow.xml"));
+    nodeXMLResource = cache.GetResource("XMLFile", "UI/EditorNode.xml");
     componentXMLResource = cache.GetResource("XMLFile", "UI/EditorComponent.xml");
-    componentParentContainer = nodeWindow.GetChild("ComponentParentContainer", true);
-    AddComponentContainer();
-    ui.root.AddChild(nodeWindow);
+    parentContainer = attributeInspectorWindow.GetChild("ParentContainer");
+    ui.root.AddChild(attributeInspectorWindow);
     int height = Min(ui.root.height - 60, 500);
-    nodeWindow.SetSize(300, height);
-    nodeWindow.SetPosition(ui.root.width - 20 - nodeWindow.width, 40);
-    nodeWindow.opacity = uiMaxOpacity;
-    nodeWindow.BringToFront();
-    UpdateNodeWindow();
-
-    SubscribeToEvent(nodeWindow.GetChild("CloseButton", true), "Released", "HideNodeWindow");
-    SubscribeToEvent(nodeWindow.GetChild("NewVarDropDown", true), "ItemSelected", "CreateNewVariable");
-    SubscribeToEvent(nodeWindow.GetChild("DeleteVarButton", true), "Released", "DeleteVariable");
-    SubscribeToEvent(nodeWindow, "LayoutUpdated", "HandleWindowLayoutUpdated");
+    attributeInspectorWindow.SetSize(300, height);
+    attributeInspectorWindow.SetPosition(ui.root.width - 20 - attributeInspectorWindow.width, 40);
+    attributeInspectorWindow.opacity = uiMaxOpacity;
+    attributeInspectorWindow.BringToFront();
+    UpdateAttributeInspector();
+
+    SubscribeToEvent(attributeInspectorWindow.GetChild("CloseButton", true), "Released", "HideAttributeInspectorWindow");
+    SubscribeToEvent(attributeInspectorWindow, "LayoutUpdated", "HandleWindowLayoutUpdated");
 }
 
-void HideNodeWindow()
+void HideAttributeInspectorWindow()
 {
-    nodeWindow.visible = false;
+    attributeInspectorWindow.visible = false;
 }
 
-bool ShowNodeWindow()
+bool ShowAttributeInspectorWindow()
 {
-    nodeWindow.visible = true;
-    nodeWindow.BringToFront();
+    attributeInspectorWindow.visible = true;
+    attributeInspectorWindow.BringToFront();
     return true;
 }
 
-void AdjustListViewChild(ListView@ list)
+void HandleWindowLayoutUpdated()
 {
-    // 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)
+    for (uint i = 0; i < parentContainer.numChildren; ++i)
     {
-        UIElement@ element = list.children[i];
-        if (!element.internal)
-            element.SetFixedWidth(width);
+        ListView@ list = GetContainer(i).GetChild("AttributeList");
+
+        // 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
+        // When window resize and so the list's width is changed, adjust the 'Is enabled' container width so that check box stays at the right most position
+        int width = list.width;
+        for (uint i = 0; i < list.numChildren; ++i)
+        {
+            UIElement@ element = list.children[i];
+            if (!element.internal)
+                element.SetFixedWidth(width);
+        }
     }
 }
 
-void HandleWindowLayoutUpdated()
+Array<Serializable@> ToSerializableArray(Array<Node@> nodes)
 {
-    AdjustListViewChild(nodeWindow.GetChild("NodeAttributeList"));
-    for (uint i = 0; i < componentParentContainer.numChildren; ++i)
-        AdjustListViewChild(GetComponentContainer(i).GetChild("ComponentAttributeList"));
+    Array<Serializable@> serializables;
+    for (uint i = 0; i < nodes.length; ++i)
+        serializables.Push(nodes[i]);
+    return serializables;
 }
 
-void UpdateNodeWindow()
+void UpdateAttributeInspector(bool fullUpdate = true)
 {
-    // If a resource pick was in progress, it cannot be completed now, as component was changed
-    PickResourceCanceled();
+    attributesDirty = false;
 
-    Text@ nodeTitle = nodeWindow.GetChild("NodeTitle", true);
+    if (fullUpdate)
+    {
+        DeleteAllContainers();
+        AddNodeContainer();
+        AddComponentContainer();
+    }
+
+    Text@ nodeTitle = nodeContainer.GetChild("NodeTitle");
     String nodeType;
     if (editNodes.length == 0)
         nodeTitle.text = "No node";
@@ -108,82 +146,65 @@ void UpdateNodeWindow()
     }
     IconizeUIElement(nodeTitle, nodeType);
 
-    UpdateAttributes(true);
-}
+    ListView@ list = nodeContainer.GetChild("AttributeList");
+    UpdateAttributes(ToSerializableArray(editNodes), list, fullUpdate);
 
-Array<Serializable@> ToSerializableArray(Array<Node@> nodes)
-{
-    Array<Serializable@> serializables;
-    for (uint i = 0; i < nodes.length; ++i)
-        serializables.Push(nodes[i]);
-    return serializables;
-}
+    if (fullUpdate)
+    {
+        //\TODO Avoid hardcoding
+        // Resize the node editor according to the number of variables, up to a certain maximum
+        uint maxAttrs = Clamp(list.contentElement.numChildren, MIN_NODE_ATTRIBUTES, MAX_NODE_ATTRIBUTES);
+        list.SetFixedHeight(maxAttrs * ATTR_HEIGHT + 2);
+        nodeContainer.SetFixedHeight(maxAttrs * ATTR_HEIGHT + 60);
+    }
 
-void UpdateAttributes(bool fullUpdate)
-{
-    attributesDirty = false;
+    if (editComponents.empty)
+    {
+        Text@ componentTitle = GetComponentContainer(0).GetChild("ComponentTitle");
+        if (selectedComponents.length <= 1)
+            componentTitle.text = "No component";
+        else
+            componentTitle.text = selectedComponents.length + " components";
 
-    if (nodeWindow !is null)
+        // Ensure there is no icon
+        IconizeUIElement(componentTitle, "");
+    }
+    else
     {
-        UpdateAttributes(ToSerializableArray(editNodes), nodeWindow.GetChild("NodeAttributeList", true), fullUpdate);
+        uint numEditableComponents = editComponents.length / numEditableComponentsPerNode;
+        String multiplierText;
+        if (numEditableComponents > 1)
+            multiplierText = " (" + numEditableComponents + "x)";
 
-        if (fullUpdate)
-            DeleteAllComponentContainers();
-        
-        if (editComponents.empty)
+        for (uint j = 0; j < numEditableComponentsPerNode; ++j)
         {
-            if (componentParentContainer.numChildren == 0)
+            if (j >= parentContainer.numChildren - componentContainerStartIndex)
                 AddComponentContainer();
-            
-            Text@ componentTitle = GetComponentContainer(0).GetChild("ComponentTitle");
-            if (selectedComponents.length <= 1)
-                componentTitle.text = "No component";
-            else
-                componentTitle.text = selectedComponents.length + " components";
-            
-            // Ensure there is no icon
-            IconizeUIElement(componentTitle, "");
-        }
-        else
-        {
-            uint numEditableComponents = editComponents.length / numEditableComponentsPerNode;
-            String multiplierText;
-            if (numEditableComponents > 1)
-                multiplierText = " (" + numEditableComponents + "x)";
-            
-            for (uint j = 0; j < numEditableComponentsPerNode; ++j)
-            {
-                if (j >= componentParentContainer.numChildren)
-                    AddComponentContainer();
-                
-                Text@ componentTitle = GetComponentContainer(j).GetChild("ComponentTitle");
-                componentTitle.text = GetComponentTitle(editComponents[j * numEditableComponents]) + multiplierText;
-                IconizeUIElement(componentTitle, editComponents[j * numEditableComponents].typeName);
-                SetIconEnabledColor(componentTitle, editComponents[j * numEditableComponents].enabledEffective);
-
-                Array<Serializable@> components;
-                for (uint i = 0; i < numEditableComponents; ++i)
-                    components.Push(editComponents[j * numEditableComponents + i]);
-                
-                UpdateAttributes(components, GetComponentContainer(j).GetChild("ComponentAttributeList"), fullUpdate);
-            }
-        }
 
-        UpdateNodeWindowIcons();
+            Text@ componentTitle = GetComponentContainer(j).GetChild("ComponentTitle");
+            componentTitle.text = GetComponentTitle(editComponents[j * numEditableComponents]) + multiplierText;
+            IconizeUIElement(componentTitle, editComponents[j * numEditableComponents].typeName);
+            SetIconEnabledColor(componentTitle, editComponents[j * numEditableComponents].enabledEffective);
+
+            Array<Serializable@> components;
+            for (uint i = 0; i < numEditableComponents; ++i)
+                components.Push(editComponents[j * numEditableComponents + i]);
+
+            UpdateAttributes(components, GetComponentContainer(j).GetChild("AttributeList"), fullUpdate);
+        }
     }
+
+    UpdateAttributeInspectorIcons();
 }
 
 void UpdateNodeAttributes()
 {
-    if (nodeWindow !is null)
-    {
-        UpdateAttributes(ToSerializableArray(editNodes), nodeWindow.GetChild("NodeAttributeList", true), false);
-    }
+    UpdateAttributes(ToSerializableArray(editNodes), nodeContainer.GetChild("AttributeList"), false);
 }
 
-void UpdateNodeWindowIcons()
+void UpdateAttributeInspectorIcons()
 {
-    Text@ nodeTitle = nodeWindow.GetChild("NodeTitle", true);
+    Text@ nodeTitle = attributeInspectorWindow.GetChild("NodeTitle", true);
     if (editNode !is null)
         SetIconEnabledColor(nodeTitle, editNode.enabled);
     else if (editNodes.length > 0)
@@ -198,21 +219,18 @@ void UpdateNodeWindowIcons()
                 break;
             }
         }
-        
+
         SetIconEnabledColor(nodeTitle, editNodes[0].enabled, !hasSameEnabledState);
     }
 
     if (!editComponents.empty)
     {
         uint numEditableComponents = editComponents.length / numEditableComponentsPerNode;
-        
+
         for (uint j = 0; j < numEditableComponentsPerNode; ++j)
         {
-            if (j >= componentParentContainer.numChildren)
-                return;
-            
             Text@ componentTitle = GetComponentContainer(j).GetChild("ComponentTitle");
-            
+
             bool enabledEffective = editComponents[j * numEditableComponents].enabledEffective;
             bool hasSameEnabledState = true;
             for (uint i = 1; i < numEditableComponents; ++i)
@@ -223,7 +241,7 @@ void UpdateNodeWindowIcons()
                     break;
                 }
             }
-            
+
             SetIconEnabledColor(componentTitle, enabledEffective, !hasSameEnabledState);
         }
     }
@@ -252,23 +270,23 @@ void SetAttributeEditorID(UIElement@ attrEdit, Array<Serializable@>@ serializabl
     {
         for (uint i = 0; i < serializables.length; ++i)
             ids.Push(Variant(cast<Node>(serializables[i]).id));
-        attrEdit.vars["NodeIDs"] = ids;
+        attrEdit.vars[NODE_IDS_VAR] = ids;
     }
     else
     {
         for (uint i = 0; i < serializables.length; ++i)
             ids.Push(Variant(cast<Component>(serializables[i]).id));
-        attrEdit.vars["ComponentIDs"] = ids;
+        attrEdit.vars[COMPONENT_IDS_VAR] = ids;
     }
 }
 
 Array<Serializable@>@ GetAttributeEditorTargets(UIElement@ attrEdit)
 {
     Array<Serializable@> ret;
-
-    if (attrEdit.vars.Contains("NodeIDs"))
+    Variant variant = attrEdit.GetVar(NODE_IDS_VAR);
+    if (!variant.empty)
     {
-        Array<Variant>@ ids = attrEdit.vars["NodeIDs"].GetVariantVector();
+        Array<Variant>@ ids = variant.GetVariantVector();
         for (uint i = 0; i < ids.length; ++i)
         {
             Node@ node = editorScene.GetNode(ids[i].GetUInt());
@@ -276,17 +294,21 @@ Array<Serializable@>@ GetAttributeEditorTargets(UIElement@ attrEdit)
                 ret.Push(node);
         }
     }
-    if (attrEdit.vars.Contains("ComponentIDs"))
+    else
     {
-        Array<Variant>@ ids = attrEdit.vars["ComponentIDs"].GetVariantVector();
-        for (uint i = 0; i < ids.length; ++i)
+        variant = attrEdit.GetVar(COMPONENT_IDS_VAR);
+        if (!variant.empty)
         {
-            Component@ component = editorScene.GetComponent(ids[i].GetUInt());
-            if (component !is null)
-                ret.Push(component);
+            Array<Variant>@ ids = variant.GetVariantVector();
+            for (uint i = 0; i < ids.length; ++i)
+            {
+                Component@ component = editorScene.GetComponent(ids[i].GetUInt());
+                if (component !is null)
+                    ret.Push(component);
+            }
         }
     }
-    
+
     return ret;
 }
 
@@ -296,7 +318,7 @@ void CreateNewVariable(StringHash eventType, VariantMap& eventData)
         return;
 
     DropDownList@ dropDown = eventData["Element"].GetUIElement();
-    LineEdit@ nameEdit = nodeWindow.GetChild("VarNameEdit", true);
+    LineEdit@ nameEdit = attributeInspectorWindow.GetChild("VarNameEdit", true);
     String sanitatedVarName = nameEdit.text.Trimmed().Replaced(";", "");
     if (sanitatedVarName.empty)
         return;
@@ -326,14 +348,14 @@ void CreateNewVariable(StringHash eventType, VariantMap& eventData)
         break;
     }
 
-    // If we overwrite an existing variable, must recreate the editor(s) for the correct type
+    // If we overwrite an existing variable, must recreate the attribute-editor(s) for the correct type
     bool overwrite = false;
     for (uint i = 0; i < editNodes.length; ++i)
     {
         overwrite = overwrite || editNodes[i].vars.Contains(sanitatedVarName);
         editNodes[i].vars[sanitatedVarName] = newValue;
     }
-    UpdateAttributes(overwrite);
+    UpdateAttributeInspector(overwrite);
 }
 
 void DeleteVariable(StringHash eventType, VariantMap& eventData)
@@ -341,7 +363,7 @@ void DeleteVariable(StringHash eventType, VariantMap& eventData)
     if (editNodes.length == 0)
         return;
 
-    LineEdit@ nameEdit = nodeWindow.GetChild("VarNameEdit", true);
+    LineEdit@ nameEdit = attributeInspectorWindow.GetChild("VarNameEdit", true);
     String sanitatedVarName = nameEdit.text.Trimmed().Replaced(";", "");
     if (sanitatedVarName.empty)
         return;
@@ -353,5 +375,5 @@ void DeleteVariable(StringHash eventType, VariantMap& eventData)
         erased = editNodes[i].vars.Erase(sanitatedVarName) || erased;
     }
     if (erased)
-        UpdateAttributes(false);
+        UpdateAttributeInspector(false);
 }

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

@@ -116,7 +116,7 @@ void EditUIMaxOpacity(StringHash eventType, VariantMap& eventData)
 void ToggleShowNonEditableAttribute(StringHash eventType, VariantMap& eventData)
 {
     showNonEditableAttribute = cast<CheckBox>(eventData["Element"].GetUIElement()).checked;
-    UpdateAttributes(true);
+    UpdateAttributeInspector(true);
 }
 
 void EditOriginalAttributeTextColor(StringHash eventType, VariantMap& eventData)
@@ -129,7 +129,7 @@ void EditOriginalAttributeTextColor(StringHash eventType, VariantMap& eventData)
         edit.text = String(normalTextColor.g);
     else if (edit.name == "OriginalAttributeTextColor.b")
         edit.text = String(normalTextColor.b);
-    UpdateAttributes(false);
+    UpdateAttributeInspector(false);
 }
 
 void EditModifiedAttributeTextColor(StringHash eventType, VariantMap& eventData)
@@ -142,7 +142,7 @@ void EditModifiedAttributeTextColor(StringHash eventType, VariantMap& eventData)
         edit.text = String(modifiedTextColor.g);
     else if (edit.name == "ModifiedAttributeTextColor.b")
         edit.text = String(modifiedTextColor.b);
-    UpdateAttributes(false);
+    UpdateAttributeInspector(false);
 }
 
 void EditNonEditableAttributeTextColor(StringHash eventType, VariantMap& eventData)
@@ -155,5 +155,5 @@ void EditNonEditableAttributeTextColor(StringHash eventType, VariantMap& eventDa
         edit.text = String(nonEditableTextColor.g);
     else if (edit.name == "NonEditableAttributeTextColor.b")
         edit.text = String(nonEditableTextColor.b);
-    UpdateAttributes(false);
+    UpdateAttributeInspector(false);
 }

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

@@ -73,8 +73,8 @@ bool ResetScene()
     runUpdate = false;
 
     UpdateWindowTitle();
-    UpdateHierarchyWindowItem(editorScene, true);
-    UpdateNodeWindow();
+    UpdateHierarchyItem(editorScene, true);
+    UpdateAttributeInspector();
 
     suppressSceneChanges = false;
 
@@ -145,8 +145,8 @@ bool LoadScene(const String&in fileName)
     runUpdate = false;
 
     UpdateWindowTitle();
-    UpdateHierarchyWindowItem(editorScene, true);
-    UpdateNodeWindow();
+    UpdateHierarchyItem(editorScene, true);
+    UpdateAttributeInspector();
 
     suppressSceneChanges = false;
 

+ 75 - 42
Bin/Data/Scripts/Editor/EditorSceneWindow.as

@@ -8,17 +8,24 @@ const uint NO_ITEM = M_MAX_UNSIGNED;
 const ShortStringHash SCENE_TYPE("Scene");
 const ShortStringHash NODE_TYPE("Node");
 const String NO_CHANGE(uint8(0));
+const ShortStringHash TYPE_VAR("Type");
+const ShortStringHash NODE_ID_VAR("NodeID");
+const ShortStringHash COMPONENT_ID_VAR("ComponentID");
+const ShortStringHash UI_ELEMENT_ID_VAR("__UIElementID");
 
 Window@ hierarchyWindow;
 ListView@ hierarchyList;
 
+// UIElement does not have unique ID, so use a running number to generate a new ID each time an item is inserted into hierarchy list
+uint uiElementNextID = 0;
+
 void CreateHierarchyWindow()
 {
     if (hierarchyWindow !is null)
         return;
 
     hierarchyWindow = ui.LoadLayout(cache.GetResource("XMLFile", "UI/EditorSceneWindow.xml"));
-    hierarchyList = hierarchyWindow.GetChild("NodeList");
+    hierarchyList = hierarchyWindow.GetChild("HierarchyList");
     ui.root.AddChild(hierarchyWindow);
     int height = Min(ui.root.height - 60, 500);
     hierarchyWindow.SetSize(300, height);
@@ -26,7 +33,7 @@ void CreateHierarchyWindow()
     hierarchyWindow.opacity = uiMaxOpacity;
     hierarchyWindow.BringToFront();
 
-    UpdateHierarchyWindowItem(editorScene);
+    UpdateHierarchyItem(editorScene);
 
     DropDownList@ newNodeList = hierarchyWindow.GetChild("NewNodeList", true);
     Array<String> newNodeChoices = {"Replicated", "Local"};
@@ -108,7 +115,7 @@ void EnableExpandCollapseButtons(bool enable)
     }
 }
 
-void UpdateHierarchyWindowItem(Serializable@ serializable, bool clear = false)
+void UpdateHierarchyItem(Serializable@ serializable, bool clear = false)
 {
     if (clear)
     {
@@ -126,15 +133,15 @@ void UpdateHierarchyWindowItem(Serializable@ serializable, bool clear = false)
     else if (serializable !is editorUIElement)
         parent = cast<UIElement>(serializable).parent;
     UIElement@ parentItem = hierarchyList.items[GetListIndex(parent)];
-    UpdateHierarchyWindowItem(GetListIndex(serializable), serializable, parentItem);
+    UpdateHierarchyItem(GetListIndex(serializable), serializable, parentItem);
 }
 
-uint UpdateHierarchyWindowItem(uint itemIndex, Serializable@ serializable, UIElement@ parentItem)
+uint UpdateHierarchyItem(uint itemIndex, Serializable@ serializable, UIElement@ parentItem)
 {
     // Whenever we're updating, disable layout update to optimize speed
     hierarchyList.contentElement.DisableLayoutUpdate();
 
-    String idVar;
+    ShortStringHash idVar;
     Variant id;
     int itemType = ITEM_NONE;
     if (serializable !is null)
@@ -191,7 +198,7 @@ uint UpdateHierarchyWindowItem(uint itemIndex, Serializable@ serializable, UIEle
         for (uint i = 0; i < node.numChildren; ++i)
         {
             Node@ childNode = node.children[i];
-            itemIndex = UpdateHierarchyWindowItem(itemIndex, childNode, text);
+            itemIndex = UpdateHierarchyItem(itemIndex, childNode, text);
         }
     }
     else
@@ -205,7 +212,7 @@ uint UpdateHierarchyWindowItem(uint itemIndex, Serializable@ serializable, UIEle
         for (uint i = 0; i < element.numChildren; ++i)
         {
             UIElement@ childElement = element.children[i];
-            itemIndex = UpdateHierarchyWindowItem(itemIndex, childElement, text);
+            itemIndex = UpdateHierarchyItem(itemIndex, childElement, text);
         }
     }
 
@@ -216,7 +223,7 @@ uint UpdateHierarchyWindowItem(uint itemIndex, Serializable@ serializable, UIEle
     return itemIndex;
 }
 
-void UpdateHierarchyWindowItemText(uint itemIndex, bool iconEnabled, const String&in textTitle = NO_CHANGE)
+void UpdateHierarchyItemText(uint itemIndex, bool iconEnabled, const String&in textTitle = NO_CHANGE)
 {
     Text@ text = hierarchyList.items[itemIndex];
     if (text is null)
@@ -232,9 +239,9 @@ void AddComponentItem(uint compItemIndex, Component@ component, UIElement@ paren
 {
     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[TYPE_VAR] = ITEM_COMPONENT;
+    text.vars[NODE_ID_VAR] = component.node.id;
+    text.vars[COMPONENT_ID_VAR] = component.id;
     text.text = GetComponentTitle(component);
 
     hierarchyList.InsertItem(compItemIndex, text, parentItem);
@@ -246,35 +253,37 @@ void SetID(Text@ text, Serializable@ serializable)
 {
     if (serializable.type == NODE_TYPE || serializable.type == SCENE_TYPE)
     {
-        text.vars["Type"] = ITEM_NODE;
-        text.vars["NodeID"] = cast<Node>(serializable).id;
+        text.vars[TYPE_VAR] = ITEM_NODE;
+        text.vars[NODE_ID_VAR] = cast<Node>(serializable).id;
     }
     else
     {
-        text.vars["Type"] = ITEM_UI_ELEMENT;
-        text.vars["ElementName"] = cast<UIElement>(serializable).name;
+        text.vars[TYPE_VAR] = ITEM_UI_ELEMENT;
+        // Store the generated ID into both the variant map of the actual object and the text item
+        cast<UIElement>(serializable).vars[UI_ELEMENT_ID_VAR] = uiElementNextID;
+        text.vars[UI_ELEMENT_ID_VAR] = uiElementNextID++;
     }
 }
 
-void GetID(Serializable@ serializable, String& idVar, Variant& id, int& itemType)
+void GetID(Serializable@ serializable, ShortStringHash& idVar, Variant& id, int& itemType)
 {
     if (serializable.type == NODE_TYPE || serializable.type == SCENE_TYPE)
     {
-        idVar = "NodeID";
+        idVar = NODE_ID_VAR;
         id = Variant(cast<Node>(serializable).id);
         itemType = ITEM_NODE;
     }
     else
     {
-        idVar = "ElementName";
-        id = Variant(cast<UIElement>(serializable).name);
+        idVar = UI_ELEMENT_ID_VAR;
+        id = cast<UIElement>(serializable).vars[UI_ELEMENT_ID_VAR];
         itemType = ITEM_UI_ELEMENT;
     }
 }
 
-bool MatchID(UIElement@ element, const String&in idVar, const Variant&in id, int itemType)
+bool MatchID(UIElement@ element, const ShortStringHash&in idVar, const Variant&in id, int itemType)
 {
-    return element.vars["Type"].GetInt() == itemType && element.vars[idVar] == id;
+    return element.vars[TYPE_VAR].GetInt() == itemType && element.vars[idVar] == id;
 }
 
 uint GetListIndex(Serializable@ serializable)
@@ -284,7 +293,7 @@ uint GetListIndex(Serializable@ serializable)
 
     uint numItems = hierarchyList.numItems;
 
-    String idVar;
+    ShortStringHash idVar;
     Variant id;
     int itemType = ITEM_NONE;
     GetID(serializable, idVar, id, itemType);
@@ -299,13 +308,22 @@ uint GetListIndex(Serializable@ serializable)
     return NO_ITEM;
 }
 
+UIElement@ GetListUIElement(uint index)
+{
+    UIElement@ item = hierarchyList.items[index];
+    if (item is null)
+        return null;
+
+    return editorUIElement.GetChild(UI_ELEMENT_ID_VAR, item.vars[UI_ELEMENT_ID_VAR], true);
+}
+
 Node@ GetListNode(uint index)
 {
     UIElement@ item = hierarchyList.items[index];
     if (item is null)
         return null;
 
-    return editorScene.GetNode(item.vars["NodeID"].GetUInt());
+    return editorScene.GetNode(item.vars[NODE_ID_VAR].GetUInt());
 }
 
 Component@ GetListComponent(uint index)
@@ -319,10 +337,10 @@ Component@ GetListComponent(UIElement@ item)
     if (item is null)
         return null;
 
-    if (item.vars["Type"].GetInt() != ITEM_COMPONENT)
+    if (item.vars[TYPE_VAR].GetInt() != ITEM_COMPONENT)
         return null;
 
-    return editorScene.GetComponent(item.vars["ComponentID"].GetUInt());
+    return editorScene.GetComponent(item.vars[COMPONENT_ID_VAR].GetUInt());
 }
 
 uint GetComponentListIndex(Component@ component)
@@ -334,7 +352,7 @@ uint GetComponentListIndex(Component@ component)
     for (uint i = 0; i < numItems; ++i)
     {
         UIElement@ item = hierarchyList.items[i];
-        if (item.vars["Type"].GetInt() == ITEM_COMPONENT && item.vars["ComponentID"].GetUInt() == component.id)
+        if (item.vars[TYPE_VAR].GetInt() == ITEM_COMPONENT && item.vars[COMPONENT_ID_VAR].GetUInt() == component.id)
             return i;
     }
 
@@ -488,7 +506,7 @@ void HandleHierarchyListSelectionChange()
     {
         uint index = indices[i];
         UIElement@ item = hierarchyList.items[index];
-        int type = item.vars["Type"].GetInt();
+        int type = item.vars[TYPE_VAR].GetInt();
         if (type == ITEM_COMPONENT)
         {
             Component@ comp = GetListComponent(index);
@@ -501,11 +519,19 @@ void HandleHierarchyListSelectionChange()
             if (node !is null)
                 selectedNodes.Push(node);
         }
+        else if (type == ITEM_UI_ELEMENT)
+        {
+            UIElement@ element = GetListUIElement(index);
+            if (element !is null && element !is editorUIElement)
+                selectedUIElements.Push(element);
+        }
     }
 
-    // If only one node selected, use it for editing
+    // If only one node/UIElement selected, use it for editing
     if (selectedNodes.length == 1)
         editNode = selectedNodes[0];
+    if (selectedUIElements.length == 1)
+        editUIElement = selectedUIElements[0];
 
     // If selection contains only components, and they have a common node, use it for editing
     if (selectedNodes.empty && !selectedComponents.empty)
@@ -589,8 +615,13 @@ void HandleHierarchyListSelectionChange()
             editNodes.Erase(0);
     }
 
+    if (selectedUIElements.empty && editUIElement !is null)
+        editUIElements.Push(editUIElement);
+    else
+        editUIElements = selectedUIElements;
+
     PositionGizmo();
-    UpdateNodeWindow();
+    UpdateAttributeInspector();
 }
 
 void HandleHierarchyListItemDoubleClick(StringHash eventType, VariantMap& eventData)
@@ -615,8 +646,8 @@ void HandleDragDropFinish(StringHash eventType, VariantMap& eventData)
     if (!accept)
         return;
 
-    Node@ sourceNode = editorScene.GetNode(source.vars["NodeID"].GetUInt());
-    Node@ targetNode = editorScene.GetNode(target.vars["NodeID"].GetUInt());
+    Node@ sourceNode = editorScene.GetNode(source.vars[NODE_ID_VAR].GetUInt());
+    Node@ targetNode = editorScene.GetNode(target.vars[NODE_ID_VAR].GetUInt());
 
     // If target is null, parent to scene
     if (targetNode is null)
@@ -635,10 +666,12 @@ bool TestDragDrop(UIElement@ source, UIElement@ target)
     // Test for validity of reparenting by drag and drop
     Node@ sourceNode;
     Node@ targetNode;
-    if (source.vars.Contains("NodeID"))
-        sourceNode = editorScene.GetNode(source.vars["NodeID"].GetUInt());
-    if (target.vars.Contains("NodeID"))
-        editorScene.GetNode(target.vars["NodeID"].GetUInt());
+    Variant variant = source.GetVar(NODE_ID_VAR);
+    if (!variant.empty)
+        sourceNode = editorScene.GetNode(variant.GetUInt());
+    variant = target.GetVar(NODE_ID_VAR);
+    if (!variant.empty)
+        targetNode = editorScene.GetNode(variant.GetUInt());
 
     if (sourceNode is null)
         return false;
@@ -739,7 +772,7 @@ void HandleNodeAdded(StringHash eventType, VariantMap& eventData)
         return;
 
     Node@ node = eventData["Node"].GetNode();
-    UpdateHierarchyWindowItem(node);
+    UpdateHierarchyItem(node);
 }
 
 void HandleNodeRemoved(StringHash eventType, VariantMap& eventData)
@@ -749,7 +782,7 @@ void HandleNodeRemoved(StringHash eventType, VariantMap& eventData)
 
     Node@ node = eventData["Node"].GetNode();
     uint index = GetListIndex(node);
-    UpdateHierarchyWindowItem(index, null, null);
+    UpdateHierarchyItem(index, null, null);
 }
 
 void HandleComponentAdded(StringHash eventType, VariantMap& eventData)
@@ -758,7 +791,7 @@ void HandleComponentAdded(StringHash eventType, VariantMap& eventData)
         return;
 
     Node@ node = eventData["Node"].GetNode();
-    UpdateHierarchyWindowItem(node);
+    UpdateHierarchyItem(node);
 }
 
 void HandleComponentRemoved(StringHash eventType, VariantMap& eventData)
@@ -781,7 +814,7 @@ void HandleNodeNameChanged(StringHash eventType, VariantMap& eventData)
         return;
 
     Node@ node = eventData["Node"].GetNode();
-    UpdateHierarchyWindowItemText(GetListIndex(node), node.enabled, GetNodeTitle(node));
+    UpdateHierarchyItemText(GetListIndex(node), node.enabled, GetNodeTitle(node));
 }
 
 void HandleNodeEnabledChanged(StringHash eventType, VariantMap& eventData)
@@ -790,7 +823,7 @@ void HandleNodeEnabledChanged(StringHash eventType, VariantMap& eventData)
         return;
 
     Node@ node = eventData["Node"].GetNode();
-    UpdateHierarchyWindowItemText(GetListIndex(node), node.enabled);
+    UpdateHierarchyItemText(GetListIndex(node), node.enabled);
     attributesDirty = true;
 }
 
@@ -800,6 +833,6 @@ void HandleComponentEnabledChanged(StringHash eventType, VariantMap& eventData)
         return;
 
     Component@ component = eventData["Component"].GetComponent();
-    UpdateHierarchyWindowItemText(GetComponentListIndex(component), component.enabledEffective);
+    UpdateHierarchyItemText(GetComponentListIndex(component), component.enabledEffective);
     attributesDirty = true;
 }

+ 10 - 13
Bin/Data/Scripts/Editor/EditorUI.as

@@ -12,7 +12,7 @@ const ShortStringHash TEXT_TYPE("Text");
 const ShortStringHash CURSOR_TYPE("Cursor");
 
 const String TEMP_SCENE_NAME("_tempscene_.xml");
-const String CALLBACK_VAR("Callback");
+const ShortStringHash CALLBACK_VAR("Callback");
 
 const int SHOW_POPUP_INDICATOR = -1;
 
@@ -46,7 +46,7 @@ void CreateUI()
     CreateCursor();
     CreateMenuBar();
     CreateHierarchyWindow();
-    CreateNodeWindow();
+    CreateAttributeInspectorWindow();
     CreateEditorSettingsDialog();
     CreateEditorPreferencesDialog();
     CreateStatsBar();
@@ -181,12 +181,9 @@ void CreateMenuBar()
         Menu@ menu = CreateMenu("Create");
         Window@ popup = menu.popup;
         popup.vars["Popup"] = "Create";
-        popup.AddChild(CreateMenuItem("Box", @PickBuiltinObject));
-        popup.AddChild(CreateMenuItem("Cone", @PickBuiltinObject));
-        popup.AddChild(CreateMenuItem("Cylinder", @PickBuiltinObject));
-        popup.AddChild(CreateMenuItem("Plane", @PickBuiltinObject));
-        popup.AddChild(CreateMenuItem("Pyramid", @PickBuiltinObject));
-        popup.AddChild(CreateMenuItem("Sphere", @PickBuiltinObject));
+        String[] objects = { "Box", "Cone", "Cylinder", "Plane", "Pyramid", "Sphere" };
+        for (uint i = 0; i < objects.length; ++i)
+            popup.AddChild(CreateMenuItem(objects[i], @PickBuiltinObject));
         uiMenuBar.AddChild(menu);
     }
 
@@ -216,7 +213,7 @@ void CreateMenuBar()
         Window@ popup = menu.popup;
         popup.vars["Popup"] = "View";
         popup.AddChild(CreateMenuItem("Hierarchy", @ShowHierarchyWindow, 'H', QUAL_CTRL));
-        popup.AddChild(CreateMenuItem("Attribute inspector", @ShowNodeWindow, 'I', QUAL_CTRL));
+        popup.AddChild(CreateMenuItem("Attribute inspector", @ShowAttributeInspectorWindow, 'I', QUAL_CTRL));
         popup.AddChild(CreateMenuItem("Editor settings", @ShowEditorSettingsDialog));
         popup.AddChild(CreateMenuItem("Editor preferences", @ShowEditorPreferencesDialog));
         popup.AddChild(CreateMenuDivider());
@@ -364,8 +361,9 @@ void HandleMenuSelected(StringHash eventType, VariantMap& eventData)
     HandlePopup(menu);
 
     // Execute the callback if available
-    if (menu.vars.Contains(CALLBACK_VAR))
-        menuCallbacks[menu.vars[CALLBACK_VAR].GetUInt()]();
+    Variant variant = menu.GetVar(CALLBACK_VAR);
+    if (!variant.empty)
+        menuCallbacks[variant.GetUInt()]();
 }
 
 Menu@ CreateMenuItem(const String&in title, MENU_CALLBACK@ callback = null, int accelKey = 0, int accelQual = 0)
@@ -521,7 +519,6 @@ void CreateFileSelector(const String&in title, const String&in ok, const String&
     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);
 }
 
@@ -886,5 +883,5 @@ void UpdateDirtyUI()
 {
     // Perform some event-triggered updates latently in case a large hierarchy was changed
     if (attributesDirty)
-        UpdateAttributes(false);
+        UpdateAttributeInspector(false);
 }

+ 18 - 15
Bin/Data/Scripts/Editor/EditorUIElement.as

@@ -11,8 +11,8 @@ Array<UIElement@> editUIElements;
 
 bool suppressUIElementChanges = false;
 
-const String FILENAME_VAR("__fileName");
-const String MODIFIED_VAR("__modified");
+const ShortStringHash FILENAME_VAR("__FileName");
+const ShortStringHash MODIFIED_VAR("__Modified");
 
 void ClearUIElementSelection()
 {
@@ -21,15 +21,15 @@ void ClearUIElementSelection()
     editUIElements.Clear();
 }
 
-void CreateUIElement()
+void CreateRootUIElement()
 {
     // Create a root UIElement only once here, do not confuse this with ui.root itself
     editorUIElement = ui.root.CreateChild("UIElement");
     editorUIElement.name = "UI";
     editorUIElement.SetSize(graphics.width, graphics.height);
-    editorUIElement.traversalMode = TM_DEPTH_FIRST;
+    editorUIElement.traversalMode = TM_DEPTH_FIRST;     // This is needed for root element to prevent artifacts
 
-    UpdateHierarchyWindowItem(editorUIElement);
+    UpdateHierarchyItem(editorUIElement);
 }
 
 bool NewUIElement()
@@ -74,18 +74,20 @@ void OpenUIElement(const String&in fileName)
 
     suppressUIElementChanges = true;
 
-    // If uiElementDefaultStyle is not set then use the editor's own default style
+    // If uiElementDefaultStyle is not set then automatically fallback to use the editor's own default style
     UIElement@ element = ui.LoadLayout(xmlFile, uiElementDefaultStyle);
     if (element !is null)
     {
         element.vars[FILENAME_VAR] = fileName;
         element.vars[MODIFIED_VAR] = false;
+
+        // \todo: should not always centered
         CenterDialog(element);
         editorUIElement.AddChild(element);
     }
 
-    UpdateHierarchyWindowItem(element, true);
-    UpdateNodeWindow();
+    UpdateHierarchyItem(element, true);
+    UpdateAttributeInspector();
 
     suppressSceneChanges = false;
 }
@@ -97,27 +99,28 @@ bool CloseUIElement()
     for (uint i = 0; i < selectedUIElements.length; ++i)
     {
         UIElement@ element = selectedUIElements[i];
-        while (!element.vars.Contains("FILENAME_VAR"))
+        while (!element.vars.Contains(FILENAME_VAR))
             element = element.parent;
         element.Remove();
-        UpdateHierarchyWindowItem(element);
+
+        UpdateHierarchyItem(GetListIndex(element), null, null);
     }
 
     suppressUIElementChanges = false;
-    
+
     return true;
 }
 
 bool CloseAllUIElements()
 {
     suppressUIElementChanges = true;
-    
+
     editorUIElement.RemoveAllChildren();
-    UpdateHierarchyWindowItem(editorUIElement, true);
-    UpdateNodeWindow();
+    UpdateHierarchyItem(editorUIElement, true);
+    UpdateAttributeInspector();
 
     suppressUIElementChanges = false;
-    
+
     return true;
 }
 

+ 4 - 0
Bin/Data/Scripts/Editor/EditorView.as

@@ -378,6 +378,10 @@ void ViewMouseClick()
 
 void ViewRaycast(bool mouseClick)
 {
+    // Ignore if UI has modal element
+    if (ui.modalElement !is null)
+        return;
+
     DebugRenderer@ debug = editorScene.debugRenderer;
     IntVector2 pos = ui.cursorPosition;
     Component@ selected;

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

@@ -427,6 +427,9 @@
         <attribute name="Layout Mode" value="vertical" />
         <attribute name="Layout Spacing" value="4" />
         <attribute name="Layout Border" value="6 6 6 6" />
+        <attribute name="Is Modal" value="true" />
+        <attribute name="Modal Frame Color" value="0.45 0.70 0.45" />
+        <attribute name="Modal Frame Size" value="2 2" />
     </element>
     <element type="FileSelectorButton">
         <attribute name="Min Size" value="80 22" />

+ 2 - 2
Bin/Data/UI/EditorComponent.xml

@@ -1,6 +1,6 @@
 <element>
     <element>
-        <attribute name="Name" value="ComponentChildContainer" />
+        <attribute name="Name" value="ComponentContainer" />
         <attribute name="Layout Mode" value="Vertical" />
         <attribute name="Layout Spacing" value="4" />
         <element type="BorderImage" style="EditorDivider" />
@@ -10,7 +10,7 @@
             <attribute name="Max Size" value="2147483647 17" />
         </element>
         <element type="ListView">
-            <attribute name="Name" value="ComponentAttributeList" />
+            <attribute name="Name" value="AttributeList" />
             <attribute name="Clip Children" value="false" />
         </element>
     </element>

+ 76 - 0
Bin/Data/UI/EditorNode.xml

@@ -0,0 +1,76 @@
+<element>
+    <element>
+        <attribute name="Name" value="NodeContainer" />
+        <attribute name="Layout Mode" value="Vertical" />
+        <attribute name="Layout Spacing" value="4" />
+        <element type="BorderImage" style="EditorDivider" />
+        <element type="Text">
+            <attribute name="Name" value="NodeTitle" />
+            <attribute name="Min Size" value="0 17" />
+            <attribute name="Max Size" value="2147483647 17" />
+        </element>
+        <element type="ListView">
+            <attribute name="Name" value="AttributeList" />
+            <attribute name="Highlight Mode" value="Always" />
+            <attribute name="Clip Children" value="false" />
+        </element>
+        <element>
+            <attribute name="Min Size" value="0 17" />
+            <attribute name="Max Size" value="2147483647 17" />
+            <attribute name="Layout Mode" value="Horizontal" />
+            <attribute name="Layout Spacing" value="4" />
+            <element type="LineEdit">
+                <attribute name="Name" value="VarNameEdit" />
+            </element>
+            <element type="DropDownList">
+                <attribute name="Name" value="NewVarDropDown" />
+                <attribute name="Min Size" value="50 17" />
+                <attribute name="Max Size" value="50 17" />
+                <element type="Window" internal="true" popup="true">
+                    <element type="ListView" internal="true">
+                        <element type="BorderImage" internal="true">
+                            <element internal="true">
+                                <element type="Text" style="FileSelectorFilterText">
+                                    <attribute name="Text" value="Int" />
+                                </element>
+                                <element type="Text" style="FileSelectorFilterText">
+                                    <attribute name="Text" value="Bool" />
+                                </element>
+                                <element type="Text" style="FileSelectorFilterText">
+                                    <attribute name="Text" value="Float" />
+                                </element>
+                                <element type="Text" style="FileSelectorFilterText">
+                                    <attribute name="Text" value="String" />
+                                </element>
+                                <element type="Text" style="FileSelectorFilterText">
+                                    <attribute name="Text" value="Vector3" />
+                                </element>
+                                <element type="Text" style="FileSelectorFilterText">
+                                    <attribute name="Text" value="Color" />
+                                </element>
+                            </element>
+                        </element>
+                    </element>
+                </element>
+                <element internal="true">
+                    <attribute name="Is Visible" value="false" />
+                </element>
+                <element type="Text">
+                    <attribute name="Text" value="New" />
+                    <attribute name="Text Alignment" value="Center" />
+                </element>
+            </element>
+            <element type="Button">
+                <attribute name="Name" value="DeleteVarButton" />
+                <attribute name="Min Size" value="50 17" />
+                <attribute name="Max Size" value="50 17" />
+                <attribute name="Layout Mode" value="Vertical" />
+                <attribute name="Layout Border" value="1 1 1 1" />
+                <element type="Text">
+                    <attribute name="Text" value="Del" />
+                    <attribute name="Text Alignment" value="Center" />
+                </element>
+            </element>
+        </element>
+    </element>
+</element>

+ 2 - 73
Bin/Data/UI/EditorNodeWindow.xml

@@ -1,5 +1,5 @@
 <element type="Window">
-    <attribute name="Name" value="NodeWindow" />
+    <attribute name="Name" value="AttributeInspectorWindow" />
     <attribute name="Is Movable" value="true" />
     <attribute name="Is Resizable" value="true" />
     <attribute name="Resize Border" value="6 6 6 6" />
@@ -17,79 +17,8 @@
             <attribute name="Name" value="CloseButton" />
         </element>
     </element>
-    <element type="BorderImage" style="EditorDivider" />
-    <element type="Text">
-        <attribute name="Name" value="NodeTitle" />
-        <attribute name="Min Size" value="0 17" />
-        <attribute name="Max Size" value="2147483647 17" />
-    </element>
-    <element type="ListView">
-        <attribute name="Name" value="NodeAttributeList" />
-        <attribute name="Highlight Mode" value="Always" />
-        <attribute name="Min Size" value="0 74" />
-        <attribute name="Max Size" value="2147483647 74" />
-        <attribute name="Clip Children" value="false" />
-    </element>
-    <element>
-        <attribute name="Min Size" value="0 17" />
-        <attribute name="Max Size" value="2147483647 17" />
-        <attribute name="Layout Mode" value="Horizontal" />
-        <attribute name="Layout Spacing" value="4" />
-        <element type="LineEdit">
-            <attribute name="Name" value="VarNameEdit" />
-        </element>
-        <element type="DropDownList">
-            <attribute name="Name" value="NewVarDropDown" />
-            <attribute name="Min Size" value="50 17" />
-            <attribute name="Max Size" value="50 17" />
-            <element type="Window" internal="true" popup="true">
-                <element type="ListView" internal="true">
-                    <element type="BorderImage" internal="true">
-                        <element internal="true">
-                            <element type="Text" style="FileSelectorFilterText">
-                                <attribute name="Text" value="Int" />
-                            </element>
-                            <element type="Text" style="FileSelectorFilterText">
-                                <attribute name="Text" value="Bool" />
-                            </element>
-                            <element type="Text" style="FileSelectorFilterText">
-                                <attribute name="Text" value="Float" />
-                            </element>
-                            <element type="Text" style="FileSelectorFilterText">
-                                <attribute name="Text" value="String" />
-                            </element>
-                            <element type="Text" style="FileSelectorFilterText">
-                                <attribute name="Text" value="Vector3" />
-                            </element>
-                            <element type="Text" style="FileSelectorFilterText">
-                                <attribute name="Text" value="Color" />
-                            </element>
-                        </element>
-                    </element>
-                </element>
-            </element>    
-            <element internal="true">
-                <attribute name="Is Visible" value="false" />
-            </element>
-            <element type="Text">
-                <attribute name="Text" value="New" />
-                <attribute name="Text Alignment" value="Center" />
-            </element>
-        </element>
-        <element type="Button">
-            <attribute name="Name" value="DeleteVarButton" />
-            <attribute name="Min Size" value="50 17" />
-            <attribute name="Max Size" value="50 17" />
-            <attribute name="Layout Mode" value="Vertical" />
-            <attribute name="Layout Border" value="1 1 1 1" />
-            <element type="Text">
-                <attribute name="Text" value="Del" />
-                <attribute name="Text Alignment" value="Center" />
-            </element>
-        </element>
-    </element>
     <element>
-        <attribute name="Name" value="ComponentParentContainer" />
+        <attribute name="Name" value="ParentContainer" />
         <attribute name="Layout Mode" value="Vertical" />
         <attribute name="Layout Spacing" value="4" />
     </element>

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

@@ -1,5 +1,5 @@
 <element type="Window">
-    <attribute name="Name" value="SceneWindow" />
+    <attribute name="Name" value="HierarchyWindow" />
     <attribute name="Is Movable" value="true" />
     <attribute name="Is Resizable" value="true" />
     <attribute name="Resize Border" value="6 6 6 6" />
@@ -57,7 +57,7 @@
         </element>
     </element>
     <element type="ListView">
-        <attribute name="Name" value="NodeList" />
+        <attribute name="Name" value="HierarchyList" />
         <attribute name="Hierarchy Mode" value="true" />
         <attribute name="Base Indent" value="1" />
         <attribute name="Highlight Mode" value="Always" />

+ 36 - 1
Docs/ScriptAPI.dox

@@ -746,7 +746,6 @@ Methods:<br>
 - void FromString(const String&, const String&)
 - void FromString(VariantType, const String&)
 - String ToString() const
-- bool IsZero() const
 - VectorBuffer GetBuffer() const
 - Node@ GetNode() const
 - Component@ GetComponent() const
@@ -758,6 +757,8 @@ Methods:<br>
 - PhysicsWorld@ GetPhysicsWorld() const
 
 Properties:<br>
+- bool zero (readonly)
+- bool empty (readonly)
 - VariantType type (readonly)
 - String typeName (readonly)
 
@@ -3150,7 +3151,9 @@ Methods:<br>
 - void RemoveAllChildren()
 - void Remove()
 - UIElement@ GetChild(const String&, bool arg1 = false) const
+- UIElement@ GetChild(const ShortStringHash&, const Variant&, bool arg2 = false) const
 - UIElement@[]@ GetChildren(bool arg0 = false) const
+- const Variant& GetVar(const ShortStringHash&)
 - IntVector2 ScreenToElement(const IntVector2&)
 - IntVector2 ElementToScreen(const IntVector2&)
 - bool IsInside(IntVector2, bool)
@@ -3256,7 +3259,9 @@ Methods:<br>
 - void RemoveAllChildren()
 - void Remove()
 - UIElement@ GetChild(const String&, bool arg1 = false) const
+- UIElement@ GetChild(const ShortStringHash&, const Variant&, bool arg2 = false) const
 - UIElement@[]@ GetChildren(bool arg0 = false) const
+- const Variant& GetVar(const ShortStringHash&)
 - IntVector2 ScreenToElement(const IntVector2&)
 - IntVector2 ElementToScreen(const IntVector2&)
 - bool IsInside(IntVector2, bool)
@@ -3365,7 +3370,9 @@ Methods:<br>
 - void RemoveAllChildren()
 - void Remove()
 - UIElement@ GetChild(const String&, bool arg1 = false) const
+- UIElement@ GetChild(const ShortStringHash&, const Variant&, bool arg2 = false) const
 - UIElement@[]@ GetChildren(bool arg0 = false) const
+- const Variant& GetVar(const ShortStringHash&)
 - void SetPosition(float, float)
 - void SetHotSpot(int, int)
 - void SetScale(float, float)
@@ -3453,7 +3460,9 @@ Methods:<br>
 - void RemoveAllChildren()
 - void Remove()
 - UIElement@ GetChild(const String&, bool arg1 = false) const
+- UIElement@ GetChild(const ShortStringHash&, const Variant&, bool arg2 = false) const
 - UIElement@[]@ GetChildren(bool arg0 = false) const
+- const Variant& GetVar(const ShortStringHash&)
 - IntVector2 ScreenToElement(const IntVector2&)
 - IntVector2 ElementToScreen(const IntVector2&)
 - bool IsInside(IntVector2, bool)
@@ -3574,7 +3583,9 @@ Methods:<br>
 - void RemoveAllChildren()
 - void Remove()
 - UIElement@ GetChild(const String&, bool arg1 = false) const
+- UIElement@ GetChild(const ShortStringHash&, const Variant&, bool arg2 = false) const
 - UIElement@[]@ GetChildren(bool arg0 = false) const
+- const Variant& GetVar(const ShortStringHash&)
 - IntVector2 ScreenToElement(const IntVector2&)
 - IntVector2 ElementToScreen(const IntVector2&)
 - bool IsInside(IntVector2, bool)
@@ -3691,7 +3702,9 @@ Methods:<br>
 - void RemoveAllChildren()
 - void Remove()
 - UIElement@ GetChild(const String&, bool arg1 = false) const
+- UIElement@ GetChild(const ShortStringHash&, const Variant&, bool arg2 = false) const
 - UIElement@[]@ GetChildren(bool arg0 = false) const
+- const Variant& GetVar(const ShortStringHash&)
 - IntVector2 ScreenToElement(const IntVector2&)
 - IntVector2 ElementToScreen(const IntVector2&)
 - bool IsInside(IntVector2, bool)
@@ -3807,7 +3820,9 @@ Methods:<br>
 - void RemoveAllChildren()
 - void Remove()
 - UIElement@ GetChild(const String&, bool arg1 = false) const
+- UIElement@ GetChild(const ShortStringHash&, const Variant&, bool arg2 = false) const
 - UIElement@[]@ GetChildren(bool arg0 = false) const
+- const Variant& GetVar(const ShortStringHash&)
 - IntVector2 ScreenToElement(const IntVector2&)
 - IntVector2 ElementToScreen(const IntVector2&)
 - bool IsInside(IntVector2, bool)
@@ -3927,7 +3942,9 @@ Methods:<br>
 - void RemoveAllChildren()
 - void Remove()
 - UIElement@ GetChild(const String&, bool arg1 = false) const
+- UIElement@ GetChild(const ShortStringHash&, const Variant&, bool arg2 = false) const
 - UIElement@[]@ GetChildren(bool arg0 = false) const
+- const Variant& GetVar(const ShortStringHash&)
 - IntVector2 ScreenToElement(const IntVector2&)
 - IntVector2 ElementToScreen(const IntVector2&)
 - bool IsInside(IntVector2, bool)
@@ -4045,7 +4062,9 @@ Methods:<br>
 - void RemoveAllChildren()
 - void Remove()
 - UIElement@ GetChild(const String&, bool arg1 = false) const
+- UIElement@ GetChild(const ShortStringHash&, const Variant&, bool arg2 = false) const
 - UIElement@[]@ GetChildren(bool arg0 = false) const
+- const Variant& GetVar(const ShortStringHash&)
 - IntVector2 ScreenToElement(const IntVector2&)
 - IntVector2 ElementToScreen(const IntVector2&)
 - bool IsInside(IntVector2, bool)
@@ -4161,7 +4180,9 @@ Methods:<br>
 - void RemoveAllChildren()
 - void Remove()
 - UIElement@ GetChild(const String&, bool arg1 = false) const
+- UIElement@ GetChild(const ShortStringHash&, const Variant&, bool arg2 = false) const
 - UIElement@[]@ GetChildren(bool arg0 = false) const
+- const Variant& GetVar(const ShortStringHash&)
 - IntVector2 ScreenToElement(const IntVector2&)
 - IntVector2 ElementToScreen(const IntVector2&)
 - bool IsInside(IntVector2, bool)
@@ -4306,7 +4327,9 @@ Methods:<br>
 - void RemoveAllChildren()
 - void Remove()
 - UIElement@ GetChild(const String&, bool arg1 = false) const
+- UIElement@ GetChild(const ShortStringHash&, const Variant&, bool arg2 = false) const
 - UIElement@[]@ GetChildren(bool arg0 = false) const
+- const Variant& GetVar(const ShortStringHash&)
 - IntVector2 ScreenToElement(const IntVector2&)
 - IntVector2 ElementToScreen(const IntVector2&)
 - bool IsInside(IntVector2, bool)
@@ -4428,7 +4451,9 @@ Methods:<br>
 - void RemoveAllChildren()
 - void Remove()
 - UIElement@ GetChild(const String&, bool arg1 = false) const
+- UIElement@ GetChild(const ShortStringHash&, const Variant&, bool arg2 = false) const
 - UIElement@[]@ GetChildren(bool arg0 = false) const
+- const Variant& GetVar(const ShortStringHash&)
 - IntVector2 ScreenToElement(const IntVector2&)
 - IntVector2 ElementToScreen(const IntVector2&)
 - bool IsInside(IntVector2, bool)
@@ -4553,7 +4578,9 @@ Methods:<br>
 - void RemoveAllChildren()
 - void Remove()
 - UIElement@ GetChild(const String&, bool arg1 = false) const
+- UIElement@ GetChild(const ShortStringHash&, const Variant&, bool arg2 = false) const
 - UIElement@[]@ GetChildren(bool arg0 = false) const
+- const Variant& GetVar(const ShortStringHash&)
 - IntVector2 ScreenToElement(const IntVector2&)
 - IntVector2 ElementToScreen(const IntVector2&)
 - bool IsInside(IntVector2, bool)
@@ -4681,7 +4708,9 @@ Methods:<br>
 - void RemoveAllChildren()
 - void Remove()
 - UIElement@ GetChild(const String&, bool arg1 = false) const
+- UIElement@ GetChild(const ShortStringHash&, const Variant&, bool arg2 = false) const
 - UIElement@[]@ GetChildren(bool arg0 = false) const
+- const Variant& GetVar(const ShortStringHash&)
 - IntVector2 ScreenToElement(const IntVector2&)
 - IntVector2 ElementToScreen(const IntVector2&)
 - bool IsInside(IntVector2, bool)
@@ -4820,7 +4849,9 @@ Methods:<br>
 - void RemoveAllChildren()
 - void Remove()
 - UIElement@ GetChild(const String&, bool arg1 = false) const
+- UIElement@ GetChild(const ShortStringHash&, const Variant&, bool arg2 = false) const
 - UIElement@[]@ GetChildren(bool arg0 = false) const
+- const Variant& GetVar(const ShortStringHash&)
 - IntVector2 ScreenToElement(const IntVector2&)
 - IntVector2 ElementToScreen(const IntVector2&)
 - bool IsInside(IntVector2, bool)
@@ -4896,6 +4927,9 @@ Properties:<br>
 - bool movable
 - bool resizable
 - IntRect resizeBorder
+- bool modal
+- Color modalFrameColor
+- IntVector2 modalFrameSize
 
 
 FileSelector
@@ -4949,6 +4983,7 @@ Properties:<br>
 - Cursor@ cursor
 - IntVector2 cursorPosition (readonly)
 - UIElement@ focusElement
+- UIElement@ modalElement (readonly)
 - UIElement@ frontElement (readonly)
 - UIElement@ root (readonly)
 - bool nonFocusedMouseWheel

+ 87 - 87
Engine/Core/Variant.cpp

@@ -62,38 +62,38 @@ static const String typeNames[] =
 Variant& Variant::operator = (const Variant& rhs)
 {
     SetType(rhs.GetType());
-    
+
     switch (type_)
     {
     case VAR_STRING:
         *(reinterpret_cast<String*>(&value_)) = *(reinterpret_cast<const String*>(&rhs.value_));
         break;
-        
+
     case VAR_BUFFER:
         *(reinterpret_cast<PODVector<unsigned char>*>(&value_)) = *(reinterpret_cast<const PODVector<unsigned char>*>(&rhs.value_));
         break;
-    
+
     case VAR_RESOURCEREF:
         *(reinterpret_cast<ResourceRef*>(&value_)) = *(reinterpret_cast<const ResourceRef*>(&rhs.value_));
         break;
-        
+
     case VAR_RESOURCEREFLIST:
         *(reinterpret_cast<ResourceRefList*>(&value_)) = *(reinterpret_cast<const ResourceRefList*>(&rhs.value_));
         break;
-        
+
     case VAR_VARIANTVECTOR:
         *(reinterpret_cast<VariantVector*>(&value_)) = *(reinterpret_cast<const VariantVector*>(&rhs.value_));
         break;
-        
+
     case VAR_VARIANTMAP:
         *(reinterpret_cast<VariantMap*>(&value_)) = *(reinterpret_cast<const VariantMap*>(&rhs.value_));
         break;
-        
+
     default:
         value_ = rhs.value_;
         break;
     }
-    
+
     return *this;
 }
 
@@ -101,57 +101,57 @@ bool Variant::operator == (const Variant& rhs) const
 {
     if (type_ != rhs.type_)
         return false;
-    
+
     switch (type_)
     {
     case VAR_INT:
         return value_.int_ == rhs.value_.int_;
-        
+
     case VAR_BOOL:
         return value_.bool_ == rhs.value_.bool_;
-        
+
     case VAR_FLOAT:
         return value_.float_ == rhs.value_.float_;
-        
+
     case VAR_VECTOR2:
         return *(reinterpret_cast<const Vector2*>(&value_)) == *(reinterpret_cast<const Vector2*>(&rhs.value_));
-        
+
     case VAR_VECTOR3:
         return *(reinterpret_cast<const Vector3*>(&value_)) == *(reinterpret_cast<const Vector3*>(&rhs.value_));
-        
+
     case VAR_VECTOR4:
     case VAR_QUATERNION:
     case VAR_COLOR:
         // Hack: use the Vector4 compare for all these classes, as they have the same memory structure
         return *(reinterpret_cast<const Vector4*>(&value_)) == *(reinterpret_cast<const Vector4*>(&rhs.value_));
-        
+
     case VAR_STRING:
         return *(reinterpret_cast<const String*>(&value_)) == *(reinterpret_cast<const String*>(&rhs.value_));
-        
+
     case VAR_BUFFER:
         return *(reinterpret_cast<const PODVector<unsigned char>*>(&value_)) == *(reinterpret_cast<const PODVector<unsigned char>*>(&rhs.value_));
-        
+
     case VAR_PTR:
         return value_.ptr_ == rhs.value_.ptr_;
-        
+
     case VAR_RESOURCEREF:
         return *(reinterpret_cast<const ResourceRef*>(&value_)) == *(reinterpret_cast<const ResourceRef*>(&rhs.value_));
-        
+
     case VAR_RESOURCEREFLIST:
         return *(reinterpret_cast<const ResourceRefList*>(&value_)) == *(reinterpret_cast<const ResourceRefList*>(&rhs.value_));
-        
+
     case VAR_VARIANTVECTOR:
         return *(reinterpret_cast<const VariantVector*>(&value_)) == *(reinterpret_cast<const VariantVector*>(&rhs.value_));
-        
+
     case VAR_VARIANTMAP:
         return *(reinterpret_cast<const VariantMap*>(&value_)) == *(reinterpret_cast<const VariantMap*>(&rhs.value_));
-        
+
     case VAR_INTRECT:
         return *(reinterpret_cast<const IntRect*>(&value_)) == *(reinterpret_cast<const IntRect*>(&rhs.value_));
-        
+
     case VAR_INTVECTOR2:
         return *(reinterpret_cast<const IntVector2*>(&value_)) == *(reinterpret_cast<const IntVector2*>(&rhs.value_));
-        
+
     default:
         return true;
     }
@@ -179,39 +179,39 @@ void Variant::FromString(VariantType type, const char* value)
     case VAR_INT:
         *this = ToInt(value);
         break;
-        
+
     case VAR_BOOL:
         *this = ToBool(value);
         break;
-        
+
     case VAR_FLOAT:
         *this = ToFloat(value);
         break;
-        
+
     case VAR_VECTOR2:
         *this = ToVector2(value);
         break;
-        
+
     case VAR_VECTOR3:
         *this = ToVector3(value);
         break;
-        
+
     case VAR_VECTOR4:
         *this = ToVector4(value);
         break;
-        
+
     case VAR_QUATERNION:
         *this = ToQuaternion(value);
         break;
-        
+
     case VAR_COLOR:
         *this = ToColor(value);
         break;
-        
+
     case VAR_STRING:
         *this = value;
         break;
-        
+
     case VAR_BUFFER:
         {
             SetType(VAR_BUFFER);
@@ -222,11 +222,11 @@ void Variant::FromString(VariantType type, const char* value)
                 buffer[i] = ToInt(values[i]);
         }
         break;
-        
+
     case VAR_PTR:
         *this = (void*)0;
         break;
-        
+
     case VAR_RESOURCEREF:
         {
             Vector<String> values = String::Split(value, ';');
@@ -239,7 +239,7 @@ void Variant::FromString(VariantType type, const char* value)
             }
         }
         break;
-        
+
     case VAR_RESOURCEREFLIST:
         {
             Vector<String> values = String::Split(value, ';');
@@ -254,15 +254,15 @@ void Variant::FromString(VariantType type, const char* value)
             }
         }
         break;
-        
+
     case VAR_INTRECT:
         *this = ToIntRect(value);
         break;
-        
+
     case VAR_INTVECTOR2:
         *this = ToIntVector2(value);
         break;
-        
+
     default:
         SetType(VAR_NONE);
     }
@@ -272,7 +272,7 @@ void Variant::SetBuffer(const void* data, unsigned size)
 {
     if (size && !data)
         size = 0;
-    
+
     SetType(VAR_BUFFER);
     PODVector<unsigned char>& buffer = *(reinterpret_cast<PODVector<unsigned char>*>(&value_));
     buffer.Resize(size);
@@ -291,31 +291,31 @@ String Variant::ToString() const
     {
     case VAR_INT:
         return String(value_.int_);
-        
+
     case VAR_BOOL:
         return String(value_.bool_);
-        
+
     case VAR_FLOAT:
         return String(value_.float_);
-        
+
     case VAR_VECTOR2:
         return (reinterpret_cast<const Vector2*>(&value_))->ToString();
-        
+
     case VAR_VECTOR3:
         return (reinterpret_cast<const Vector3*>(&value_))->ToString();
-        
+
     case VAR_VECTOR4:
         return (reinterpret_cast<const Vector4*>(&value_))->ToString();
-        
+
     case VAR_QUATERNION:
         return (reinterpret_cast<const Quaternion*>(&value_))->ToString();
-        
+
     case VAR_COLOR:
         return (reinterpret_cast<const Color*>(&value_))->ToString();
-        
+
     case VAR_STRING:
         return *(reinterpret_cast<const String*>(&value_));
-        
+
     case VAR_BUFFER:
         {
             const PODVector<unsigned char>& buffer = *(reinterpret_cast<const PODVector<unsigned char>*>(&value_));
@@ -328,17 +328,17 @@ String Variant::ToString() const
             }
             return ret;
         }
-        
+
     case VAR_PTR:
         // Pointer serialization not supported (convert to null)
         return String(0);
-        
+
     case VAR_INTRECT:
         return (reinterpret_cast<const IntRect*>(&value_))->ToString();
-        
+
     case VAR_INTVECTOR2:
         return (reinterpret_cast<const IntVector2*>(&value_))->ToString();
-        
+
     default:
         // VAR_RESOURCEREF, VAR_RESOURCEREFLIST, VAR_VARIANTVECTOR, VAR_VARIANTMAP
         // Reference string serialization requires hash-to-name mapping from the context & subsystems. Can not support here
@@ -353,41 +353,41 @@ bool Variant::IsZero() const
     {
     case VAR_INT:
         return value_.int_ == 0;
-        
+
     case VAR_BOOL:
         return value_.bool_ == false;
-        
+
     case VAR_FLOAT:
         return value_.float_ == 0.0f;
-        
+
     case VAR_VECTOR2:
         return *reinterpret_cast<const Vector2*>(&value_) == Vector2::ZERO;
-        
+
     case VAR_VECTOR3:
         return *reinterpret_cast<const Vector3*>(&value_) == Vector3::ZERO;
-        
+
     case VAR_VECTOR4:
         return *reinterpret_cast<const Vector4*>(&value_) == Vector4::ZERO;
-        
+
     case VAR_QUATERNION:
         return *reinterpret_cast<const Quaternion*>(&value_) == Quaternion::IDENTITY;
-        
+
     case VAR_COLOR:
         // WHITE is considered empty (i.e. default) color in the Color class definition
         return *reinterpret_cast<const Color*>(&value_) == Color::WHITE;
-        
+
     case VAR_STRING:
         return reinterpret_cast<const String*>(&value_)->Empty();
-        
+
     case VAR_BUFFER:
         return reinterpret_cast<const PODVector<unsigned char>*>(&value_)->Empty();
-        
+
     case VAR_PTR:
         return value_.ptr_ == 0;
-        
+
     case VAR_RESOURCEREF:
         return reinterpret_cast<const ResourceRef*>(&value_)->id_ == StringHash::ZERO;
-        
+
     case VAR_RESOURCEREFLIST:
     {
         Vector<StringHash> ids = reinterpret_cast<const ResourceRefList*>(&value_)->ids_;
@@ -398,21 +398,21 @@ bool Variant::IsZero() const
         }
         return true;
     }
-    
+
     case VAR_VARIANTVECTOR:
         return reinterpret_cast<const VariantVector*>(&value_)->Empty();
-        
+
     case VAR_VARIANTMAP:
         return reinterpret_cast<const VariantMap*>(&value_)->Empty();
-        
+
     case VAR_INTRECT:
         return *reinterpret_cast<const IntRect*>(&value_) == IntRect::ZERO;
-        
+
     case VAR_INTVECTOR2:
         return *reinterpret_cast<const IntVector2*>(&value_) == IntVector2::ZERO;
-        
+
     default:
-        return false;
+        return true;
     }
 }
 
@@ -420,65 +420,65 @@ void Variant::SetType(VariantType newType)
 {
     if (type_ == newType)
         return;
-    
+
     switch (type_)
     {
     case VAR_STRING:
         (reinterpret_cast<String*>(&value_))->~String();
         break;
-        
+
     case VAR_BUFFER:
         (reinterpret_cast<PODVector<unsigned char>*>(&value_))->~PODVector<unsigned char>();
         break;
-        
+
     case VAR_RESOURCEREF:
         (reinterpret_cast<ResourceRef*>(&value_))->~ResourceRef();
         break;
-        
+
     case VAR_RESOURCEREFLIST:
         (reinterpret_cast<ResourceRefList*>(&value_))->~ResourceRefList();
         break;
-        
+
     case VAR_VARIANTVECTOR:
         (reinterpret_cast<VariantVector*>(&value_))->~VariantVector();
         break;
-        
+
     case VAR_VARIANTMAP:
         (reinterpret_cast<VariantMap*>(&value_))->~VariantMap();
         break;
-        
+
     default:
         break;
     }
-    
+
     type_ = newType;
-    
+
     switch (type_)
     {
     case VAR_STRING:
         new(reinterpret_cast<String*>(&value_)) String();
         break;
-        
+
     case VAR_BUFFER:
         new(reinterpret_cast<PODVector<unsigned char>*>(&value_)) PODVector<unsigned char>();
         break;
-        
+
     case VAR_RESOURCEREF:
         new(reinterpret_cast<ResourceRef*>(&value_)) ResourceRef();
         break;
-        
+
     case VAR_RESOURCEREFLIST:
         new(reinterpret_cast<ResourceRefList*>(&value_)) ResourceRefList();
         break;
-        
+
     case VAR_VARIANTVECTOR:
         new(reinterpret_cast<VariantVector*>(&value_)) VariantVector();
         break;
-        
+
     case VAR_VARIANTMAP:
         new(reinterpret_cast<VariantMap*>(&value_)) VariantMap();
         break;
-        
+
     default:
         break;
     }
@@ -648,7 +648,7 @@ VariantType Variant::GetTypeFromName(const char* typeName)
             return (VariantType)index;
         ++index;
     }
-    
+
     return VAR_NONE;
 }
 

+ 2 - 2
Engine/Core/Variant.h

@@ -150,7 +150,7 @@ struct ResourceRefList
     
     /// Object type.
     ShortStringHash type_;
-    /// List of object identifiers (name hashes.)
+    /// List of object identifiers (name hashes).
     Vector<StringHash> ids_;
     
     /// Test for equality with another reference list.
@@ -191,7 +191,7 @@ public:
         *this = (int)value;
     }
     
-    /// Construct from a string hash (convert to integer.)
+    /// Construct from a string hash (convert to integer).
     Variant(const StringHash& value) :
         type_(VAR_NONE)
     {

+ 2 - 0
Engine/Engine/APITemplates.h

@@ -854,7 +854,9 @@ template <class T> void RegisterUIElement(asIScriptEngine* engine, const char* c
     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);
+    engine->RegisterObjectMethod(className, "UIElement@+ GetChild(const ShortStringHash&in, const Variant&in, bool recursive = false) const", asMETHODPR(T, GetChild, (const ShortStringHash&, const Variant&, bool) const, UIElement*), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "Array<UIElement@>@ GetChildren(bool recursive = false) const", asFUNCTION(UIElementGetChildren), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod(className, "const Variant& GetVar(const ShortStringHash&in)", asMETHOD(T, GetVar), asCALL_THISCALL);
     if (!isSprite)
     {
         engine->RegisterObjectMethod(className, "IntVector2 ScreenToElement(const IntVector2&in)", asMETHOD(T, ScreenToElement), asCALL_THISCALL);

+ 20 - 14
Engine/Engine/CoreAPI.cpp

@@ -108,7 +108,7 @@ static void RegisterStringHash(asIScriptEngine* engine)
     engine->RegisterObjectMethod("StringHash", "StringHash opAdd(const StringHash&in) const", asMETHOD(StringHash, operator +), asCALL_THISCALL);
     engine->RegisterObjectMethod("StringHash", "String ToString() const", asMETHOD(StringHash, ToString), asCALL_THISCALL);
     engine->RegisterObjectMethod("StringHash", "uint get_value()", asMETHOD(StringHash, Value), asCALL_THISCALL);
-    
+
     engine->RegisterObjectType("ShortStringHash", sizeof(ShortStringHash), asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS_CAK);
     engine->RegisterObjectBehaviour("ShortStringHash", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ConstructShortStringHash), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectBehaviour("ShortStringHash", asBEHAVE_CONSTRUCT, "void f(const ShortStringHash&in)", asFUNCTION(ConstructShortStringHashCopyShort), asCALL_CDECL_OBJLAST);
@@ -174,7 +174,7 @@ static void ResourceRefListSetId(unsigned index, const StringHash& id, ResourceR
         asGetActiveContext()->SetException("Index out of bounds");
         return;
     }
-    
+
     ptr->ids_[index] = id;
 }
 
@@ -185,7 +185,7 @@ static StringHash ResourceRefListGetId(unsigned index, ResourceRefList* ptr)
         asGetActiveContext()->SetException("Index out of bounds");
         return StringHash();
     }
-    
+
     return ptr->ids_[index];
 }
 
@@ -334,6 +334,11 @@ static CScriptArray* VariantGetVariantVector(Variant* ptr)
     return VectorToArray<Variant>(ptr->GetVariantVector(), "Array<Variant>");
 }
 
+static bool IsVariantEmpty(Variant* ptr)
+{
+    return ptr->GetType() == VAR_NONE;
+}
+
 static void ConstructVariantMap(VariantMap* ptr)
 {
     new(ptr) VariantMap();
@@ -409,7 +414,7 @@ static void RegisterVariant(asIScriptEngine* engine)
     engine->RegisterEnumValue("VariantType", "VAR_VARIANTMAP", VAR_VARIANTMAP);
     engine->RegisterEnumValue("VariantType", "VAR_INTRECT", VAR_INTRECT);
     engine->RegisterEnumValue("VariantType", "VAR_INTVECTOR2", VAR_INTVECTOR2);
-    
+
     engine->RegisterObjectType("ResourceRef", sizeof(ResourceRef), asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS_CAK);
     engine->RegisterObjectBehaviour("ResourceRef", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ConstructResourceRef), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectBehaviour("ResourceRef", asBEHAVE_CONSTRUCT, "void f(const ResourceRef&in)", asFUNCTION(ConstructResourceRefCopy), asCALL_CDECL_OBJLAST);
@@ -418,7 +423,7 @@ static void RegisterVariant(asIScriptEngine* engine)
     engine->RegisterObjectMethod("ResourceRef", "bool opEquals(const ResourceRef&in) const", asMETHOD(ResourceRef, operator ==), asCALL_THISCALL);
     engine->RegisterObjectProperty("ResourceRef", "ShortStringHash type", offsetof(ResourceRef, type_));
     engine->RegisterObjectProperty("ResourceRef", "StringHash id", offsetof(ResourceRef, id_));
-    
+
     engine->RegisterObjectType("ResourceRefList", sizeof(ResourceRefList), asOBJ_VALUE | asOBJ_APP_CLASS_CDAK);
     engine->RegisterObjectBehaviour("ResourceRefList", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ConstructResourceRefList), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectBehaviour("ResourceRefList", asBEHAVE_CONSTRUCT, "void f(const ResourceRefList&in)", asFUNCTION(ConstructResourceRefListCopy), asCALL_CDECL_OBJLAST);
@@ -431,7 +436,7 @@ static void RegisterVariant(asIScriptEngine* engine)
     engine->RegisterObjectMethod("ResourceRefList", "void set_ids(uint, const StringHash&in) const", asFUNCTION(ResourceRefListSetId), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("ResourceRefList", "StringHash get_ids(uint) const", asFUNCTION(ResourceRefListGetId), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectProperty("ResourceRefList", "ShortStringHash type", offsetof(ResourceRef, type_));
-    
+
     engine->RegisterObjectType("Variant", sizeof(Variant), asOBJ_VALUE | asOBJ_APP_CLASS_CDAK);
     engine->RegisterObjectType("VariantMap", sizeof(VariantMap), asOBJ_VALUE | asOBJ_APP_CLASS_CDAK);
     engine->RegisterObjectBehaviour("Variant", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ConstructVariant), asCALL_CDECL_OBJLAST);
@@ -517,10 +522,11 @@ static void RegisterVariant(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Variant", "void FromString(const String&in, const String&in)", asMETHODPR(Variant, FromString, (const String&, const String&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("Variant", "void FromString(VariantType, const String&in)", asMETHODPR(Variant, FromString, (VariantType, const String&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("Variant", "String ToString() const", asMETHOD(Variant, ToString), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Variant", "bool IsZero() const", asMETHOD(Variant, IsZero), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Variant", "bool get_zero() const", asMETHOD(Variant, IsZero), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Variant", "bool get_empty() const", asFUNCTION(IsVariantEmpty), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Variant", "VariantType get_type() const", asMETHOD(Variant, GetType), asCALL_THISCALL);
     engine->RegisterObjectMethod("Variant", "const String& get_typeName() const", asMETHODPR(Variant, GetTypeName, () const, const String&), asCALL_THISCALL);
-    
+
     engine->RegisterObjectBehaviour("VariantMap", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ConstructVariantMap), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectBehaviour("VariantMap", asBEHAVE_CONSTRUCT, "void f(const VariantMap&in)", asFUNCTION(ConstructVariantMapCopy), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectBehaviour("VariantMap", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(DestructVariantMap), asCALL_CDECL_OBJLAST);
@@ -611,7 +617,7 @@ static void RegisterTimer(asIScriptEngine* engine)
     engine->RegisterObjectBehaviour("Timer", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ConstructTimer), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Timer", "uint GetMSec(bool)", asMETHOD(Timer, GetMSec), asCALL_THISCALL);
     engine->RegisterObjectMethod("Timer", "void Reset()", asMETHOD(Timer, Reset), asCALL_THISCALL);
-     
+
     RegisterObject<Time>(engine, "Time");
     engine->RegisterObjectMethod("Time", "uint get_frameNumber() const", asMETHOD(Time, GetFrameNumber), asCALL_THISCALL);
     engine->RegisterObjectMethod("Time", "float get_timeStep() const", asMETHOD(Time, GetTimeStep), asCALL_THISCALL);
@@ -715,12 +721,12 @@ static void UnsubscribeFromAllEventsExcept(CScriptArray* exceptions)
     Object* listener = GetScriptContextEventListenerObject();
     if (!listener || !exceptions)
         return;
-    
+
     unsigned numExceptions = exceptions->GetSize();
     PODVector<StringHash> destExceptions(numExceptions);
     for (unsigned i = 0; i < numExceptions; ++i)
         destExceptions[i] = StringHash(*(static_cast<String*>(exceptions->At(i))));
-    
+
     listener->UnsubscribeFromAllEventsExcept(destExceptions, true);
 }
 
@@ -766,9 +772,9 @@ void RegisterObject(asIScriptEngine* engine)
     engine->RegisterObjectProperty("AttributeInfo", "String name", offsetof(AttributeInfo, name_));
     engine->RegisterObjectProperty("AttributeInfo", "Variant defaultValue", offsetof(AttributeInfo, defaultValue_));
     engine->RegisterObjectProperty("AttributeInfo", "uint mode", offsetof(AttributeInfo, mode_));
-    
+
     RegisterObject<Object>(engine, "Object");
-    
+
     engine->RegisterGlobalFunction("void SendEvent(const String&in, VariantMap& eventData = VariantMap())", asFUNCTION(SendEvent), asCALL_CDECL);
     engine->RegisterGlobalFunction("void SubscribeToEvent(const String&in, const String&in)", asFUNCTION(SubscribeToEvent), asCALL_CDECL);
     engine->RegisterGlobalFunction("void SubscribeToEvent(Object@+, const String&in, const String&in)", asFUNCTION(SubscribeToSenderEvent), asCALL_CDECL);
@@ -779,7 +785,7 @@ void RegisterObject(asIScriptEngine* engine)
     engine->RegisterGlobalFunction("void UnsubscribeFromAllEventsExcept(Array<String>@+)", asFUNCTION(UnsubscribeFromAllEventsExcept), asCALL_CDECL);
     engine->RegisterGlobalFunction("Object@+ GetEventSender()", asFUNCTION(GetEventSender), asCALL_CDECL);
     engine->RegisterGlobalFunction("const String& GetTypeName(ShortStringHash)", asFUNCTION(GetTypeName), asCALL_CDECL);
-    
+
     engine->RegisterObjectType("WeakHandle", sizeof(WeakPtr<Object>), asOBJ_VALUE | asOBJ_APP_CLASS_CDAK);
     engine->RegisterObjectBehaviour("WeakHandle", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ConstructWeakHandle), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectBehaviour("WeakHandle", asBEHAVE_CONSTRUCT, "void f(const WeakHandle&in)", asFUNCTION(ConstructWeakHandleCopy), asCALL_CDECL_OBJLAST);

+ 7 - 0
Engine/Engine/UIAPI.cpp

@@ -419,6 +419,12 @@ static void RegisterWindow(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Window", "bool get_resizable() const", asMETHOD(Window, IsResizable), asCALL_THISCALL);
     engine->RegisterObjectMethod("Window", "void set_resizeBorder(const IntRect&in)", asMETHODPR(Window, SetResizeBorder, (const IntRect&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("Window", "const IntRect& get_resizeBorder() const", asMETHOD(Window, GetResizeBorder), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Window", "void set_modal(bool)", asMETHOD(Window, SetModal), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Window", "bool get_modal() const", asMETHOD(Window, IsModal), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Window", "void set_modalFrameColor(const Color&in)", asMETHOD(Window, SetModalFrameColor), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Window", "const Color& get_modalFrameColor() const", asMETHOD(Window, GetModalFrameColor), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Window", "void set_modalFrameSize(const IntVector2&in)", asMETHOD(Window, SetModalFrameSize), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Window", "const IntVector2& get_modalFrameSize() const", asMETHOD(Window, GetModalFrameSize), asCALL_THISCALL);
 }
 
 static void FileSelectorSetFilters(CScriptArray* filters, unsigned defaultIndex, FileSelector* ptr)
@@ -535,6 +541,7 @@ static void RegisterUI(asIScriptEngine* engine)
     engine->RegisterObjectMethod("UI", "IntVector2 get_cursorPosition()", asMETHOD(UI, GetCursorPosition), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "void set_focusElement(UIElement@+)", asMETHOD(UI, SetFocusElement), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "UIElement@+ get_focusElement() const", asMETHOD(UI, GetFocusElement), asCALL_THISCALL);
+    engine->RegisterObjectMethod("UI", "UIElement@+ get_modalElement() const", asMETHOD(UI, GetModalElement), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "UIElement@+ get_frontElement() const", asMETHOD(UI, GetFrontElement), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "UIElement@+ get_root() const", asMETHOD(UI, GetRoot), asCALL_THISCALL);
     engine->RegisterGlobalFunction("UI@+ get_ui()", asFUNCTION(GetUI), asCALL_CDECL);

+ 8 - 0
Engine/UI/Menu.cpp

@@ -25,6 +25,7 @@
 #include "InputEvents.h"
 #include "Log.h"
 #include "Menu.h"
+#include "UI.h"
 #include "UIEvents.h"
 
 #include "DebugNew.h"
@@ -362,7 +363,14 @@ void Menu::HandleKeyDown(StringHash eventType, VariantMap& eventData)
     // Activate if accelerator key pressed
     if (eventData[P_KEY].GetInt() == acceleratorKey_ && (acceleratorQualifiers_ == QUAL_ANY || eventData[P_QUALIFIERS].GetInt() ==
         acceleratorQualifiers_) && eventData[P_REPEAT].GetBool() == false)
+    {
+        // Ignore if UI has modal element
+        UI* ui = GetSubsystem<UI>();
+        if (ui->GetModalElement())
+            return;
+
         HandlePressedReleased(eventType, eventData);
+    }
 }
 
 }

+ 153 - 115
Engine/UI/UI.cpp

@@ -82,10 +82,10 @@ UI::UI(Context* context) :
     SubscribeToEvent(E_TOUCHMOVE, HANDLER(UI, HandleTouchMove));
     SubscribeToEvent(E_KEYDOWN, HANDLER(UI, HandleKeyDown));
     SubscribeToEvent(E_CHAR, HANDLER(UI, HandleChar));
-    
+
     // Delay SubscribeToEvent(E_POSTUPDATE, HANDLER(UI, HandlePostUpdate)) and
     // SubscribeToEvent(E_RENDERUPDATE, HANDLER(UI, HandleRenderUpdate)) until UI is initialized
-    
+
     // Try to initialize right now, but skip if screen mode is not yet set
     Initialize();
 }
@@ -106,7 +106,7 @@ void UI::SetCursor(Cursor* cursor)
     {
         rootElement_->AddChild(cursor);
         cursor_ = cursor;
-        
+
         IntVector2 pos = cursor_->GetPosition();
         const IntVector2& rootSize = rootElement_->GetSize();
         pos.x_ = Clamp(pos.x_, 0, rootSize.x_ - 1);
@@ -118,47 +118,71 @@ void UI::SetCursor(Cursor* cursor)
 void UI::SetFocusElement(UIElement* element)
 {
     using namespace FocusChanged;
-    
+
     VariantMap eventData;
     eventData[P_CLICKEDELEMENT] = (void*)element;
-    
+
     if (element)
     {
         // Return if already has focus
         if (focusElement_ == element)
             return;
-        
+
+        // Only allow child elements of the modal element to receive focus
+        if (modalElement_)
+        {
+            UIElement* topLevel = element->GetParent();
+            while (topLevel && topLevel->GetParent() != rootElement_)
+                topLevel = topLevel->GetParent();
+            if (topLevel != modalElement_)
+                return;
+        }
+
         // Search for an element in the hierarchy that can alter focus. If none found, exit
         element = GetFocusableElement(element);
         if (!element)
             return;
     }
-    
+
     // Remove focus from the old element
     if (focusElement_)
     {
         UIElement* oldFocusElement = focusElement_;
         focusElement_.Reset();
-        
+
         VariantMap focusEventData;
         focusEventData[Defocused::P_ELEMENT] = oldFocusElement;
         oldFocusElement->SendEvent(E_DEFOCUSED, focusEventData);
     }
-    
+
     // Then set focus to the new
     if (element && element->GetFocusMode() >= FM_FOCUSABLE)
     {
         focusElement_ = element;
-        
+
         VariantMap focusEventData;
         focusEventData[Focused::P_ELEMENT] = element;
         element->SendEvent(E_FOCUSED, focusEventData);
     }
-    
+
     eventData[P_ELEMENT] = (void*)element;
     SendEvent(E_FOCUSCHANGED, eventData);
 }
 
+bool UI::SetModalElement(UIElement* modalElement)
+{
+    // Only allow one modal element at a time
+    if (modalElement_)
+        return false;
+
+    // Currently only allow modal window
+    if (modalElement && modalElement->GetType() != Window::GetTypeStatic())
+        return false;
+
+    modalElement_ = modalElement;
+    return true;
+}
+
 void UI::Clear()
 {
     rootElement_->RemoveAllChildren();
@@ -169,18 +193,18 @@ void UI::Clear()
 void UI::Update(float timeStep)
 {
     assert(rootElement_);
-    
+
     PROFILE(UpdateUI);
-    
+
     if (cursor_ && cursor_->IsVisible())
     {
         IntVector2 pos = cursor_->GetPosition();
         WeakPtr<UIElement> element(GetElementAt(pos));
-        
+
         bool dragSource = dragElement_ && (dragElement_->GetDragDropMode() & DD_SOURCE) != 0;
         bool dragTarget = element && (element->GetDragDropMode() & DD_TARGET) != 0;
         bool dragDropTest = dragSource && dragTarget && element != dragElement_;
-        
+
         // Hover effect
         // If a drag is going on, transmit hover only to the element being dragged, unless it's a drop target
         if (element && element->IsEnabled())
@@ -190,7 +214,7 @@ void UI::Update(float timeStep)
         }
         else
             cursor_->SetShape(CS_NORMAL);
-        
+
         // Drag and drop test
         if (dragDropTest)
         {
@@ -198,7 +222,7 @@ void UI::Update(float timeStep)
             if (accept)
             {
                 using namespace DragDropTest;
-                
+
                 VariantMap eventData;
                 eventData[P_SOURCE] = (void*)dragElement_.Get();
                 eventData[P_TARGET] = (void*)element.Get();
@@ -206,13 +230,13 @@ void UI::Update(float timeStep)
                 SendEvent(E_DRAGDROPTEST, eventData);
                 accept = eventData[P_ACCEPT].GetBool();
             }
-            
+
             cursor_->SetShape(accept ? CS_ACCEPTDROP : CS_REJECTDROP);
         }
         else if (dragSource)
             cursor_->SetShape(dragElement_ == element ? CS_ACCEPTDROP : CS_REJECTDROP);
     }
-    
+
     // Touch hover
     Input* input = GetSubsystem<Input>();
     if (input)
@@ -226,16 +250,16 @@ void UI::Update(float timeStep)
                 element->OnHover(element->ScreenToElement(touch->position_), touch->position_, MOUSEB_LEFT, 0, 0);
         }
     }
-    
+
     Update(timeStep, rootElement_);
 }
 
 void UI::RenderUpdate()
 {
     assert(rootElement_ && graphics_);
-    
+
     PROFILE(GetUIBatches);
-    
+
     // If the OS cursor is visible, do not render the UI's own cursor
     bool osCursorVisible = GetSubsystem<Input>()->IsMouseVisible();
     bool uiCursorVisible = false;
@@ -244,7 +268,7 @@ void UI::RenderUpdate()
         uiCursorVisible = cursor_->IsVisible();
         cursor_->SetTempVisible(false);
     }
-    
+
     // Get rendering batches from the UI elements
     batches_.Clear();
     vertexData_.Clear();
@@ -260,24 +284,24 @@ void UI::Render()
 {
     // Engine does not render when window is closed or device is lost
     assert(graphics_ && graphics_->IsInitialized() && !graphics_->IsDeviceLost());
-    
+
     PROFILE(RenderUI);
-    
+
     if (vertexData_.Empty())
         return;
-    
+
     // Update quad geometry into the vertex buffer
     unsigned numVertices = vertexData_.Size() / UI_VERTEX_SIZE;
     // Resize the vertex buffer if too small or much too large
     if (vertexBuffer_->GetVertexCount() < numVertices || vertexBuffer_->GetVertexCount() > numVertices * 2)
         vertexBuffer_->SetSize(numVertices, MASK_POSITION | MASK_COLOR | MASK_TEXCOORD1, true);
-    
+
     vertexBuffer_->SetData(&vertexData_[0]);
-    
+
     Vector2 invScreenSize(1.0f / (float)graphics_->GetWidth(), 1.0f / (float)graphics_->GetHeight());
     Vector2 scale(2.0f * invScreenSize.x_, -2.0f * invScreenSize.y_);
     Vector2 offset(-1.0f, 1.0f);
-    
+
     Matrix4 projection(Matrix4::IDENTITY);
     projection.m00_ = scale.x_;
     projection.m03_ = offset.x_;
@@ -286,25 +310,25 @@ void UI::Render()
     projection.m22_ = 1.0f;
     projection.m23_ = 0.0f;
     projection.m33_ = 1.0f;
-    
+
     graphics_->ClearParameterSources();
     graphics_->SetCullMode(CULL_CCW);
     graphics_->SetDepthTest(CMP_ALWAYS);
     graphics_->SetDepthWrite(false);
     graphics_->SetStencilTest(false);
     graphics_->ResetRenderTargets();
-    
+
     ShaderVariation* ps = 0;
     ShaderVariation* vs = 0;
-    
+
     unsigned alphaFormat = Graphics::GetAlphaFormat();
-    
+
     for (unsigned i = 0; i < batches_.Size(); ++i)
     {
         const UIBatch& batch = batches_[i];
         if (batch.vertexStart_ == batch.vertexEnd_)
             continue;
-        
+
         if (!batch.texture_)
         {
             ps = noTexturePS_;
@@ -314,7 +338,7 @@ void UI::Render()
         {
             // If texture contains only an alpha channel, use alpha shader (for fonts)
             vs = diffTextureVS_;
-            
+
             if (batch.texture_->GetFormat() == alphaFormat)
                 ps = alphaTexturePS_;
             else if (batch.blendMode_ != BLEND_ALPHA && batch.blendMode_ != BLEND_ADDALPHA && batch.blendMode_ != BLEND_PREMULALPHA)
@@ -322,7 +346,7 @@ void UI::Render()
             else
                 ps = diffTexturePS_;
         }
-        
+
         graphics_->SetShaders(vs, ps);
         if (graphics_->NeedParameterUpdate(SP_OBJECTTRANSFORM, this))
             graphics_->SetShaderParameter(VSP_MODEL, Matrix3x4::IDENTITY);
@@ -330,7 +354,7 @@ void UI::Render()
             graphics_->SetShaderParameter(VSP_VIEWPROJ, projection);
         if (graphics_->NeedParameterUpdate(SP_MATERIAL, this))
             graphics_->SetShaderParameter(PSP_MATDIFFCOLOR, Color(1.0f, 1.0f, 1.0f, 1.0f));
-        
+
         graphics_->SetBlendMode(batch.blendMode_);
         graphics_->SetScissorTest(true, batch.scissor_);
         graphics_->SetTexture(0, batch.texture_);
@@ -352,28 +376,28 @@ SharedPtr<UIElement> UI::LoadLayout(Deserializer& source, XMLFile* styleFile)
 SharedPtr<UIElement> UI::LoadLayout(XMLFile* file, XMLFile* styleFile)
 {
     PROFILE(LoadUILayout);
-    
+
     SharedPtr<UIElement> root;
-    
+
     if (!file)
     {
         LOGERROR("Null UI layout XML file");
         return root;
     }
-    
+
     LOGDEBUG("Loading UI layout " + file->GetName());
-    
+
     XMLElement rootElem = file->GetRoot("element");
     if (!rootElem)
     {
         LOGERROR("No root UI element in " + file->GetName());
         return root;
     }
-    
+
     String typeName = rootElem.GetAttribute("type");
     if (typeName.Empty())
         typeName = "UIElement";
-    
+
     root = DynamicCast<UIElement>(context_->CreateObject(typeName));
     if (!root)
     {
@@ -395,7 +419,7 @@ SharedPtr<UIElement> UI::LoadLayout(XMLFile* file, XMLFile* styleFile)
 bool UI::SaveLayout(Serializer& dest, UIElement* element)
 {
     PROFILE(SaveUILayout);
-    
+
     if (element)
         return element->SaveXML(dest);
     else
@@ -415,7 +439,22 @@ void UI::SetNonFocusedMouseWheel(bool nonFocusedMouseWheel)
 UIElement* UI::GetElementAt(const IntVector2& position, bool enabledOnly)
 {
     UIElement* result = 0;
-    GetElementAt(result, rootElement_, position, enabledOnly);
+    if (modalElement_)
+    {
+        // Create temporary root which only contains the modal element
+        SharedPtr<UIElement> fakeRoot(new UIElement(context_));
+
+        // Special care is needed to insert into fakeRoot without altering the actual modal element's parent
+        Vector<SharedPtr<UIElement> >& children = const_cast<Vector<SharedPtr<UIElement> >& >(fakeRoot->GetChildren());
+        children.Push(SharedPtr<UIElement>(modalElement_));
+
+        GetElementAt(result, fakeRoot, position, enabledOnly);
+
+        // Special care is needed to clear the children vector before the shared pointer goes out of scope to prevent it from detaching modal element
+        children.Clear();
+    }
+    else
+        GetElementAt(result, rootElement_, position, enabledOnly);
     return result;
 }
 
@@ -424,23 +463,22 @@ UIElement* UI::GetElementAt(int x, int y, bool enabledOnly)
     return GetElementAt(IntVector2(x, y), enabledOnly);
 }
 
-UIElement* UI::GetFocusElement() const
-{
-    return focusElement_;
-}
-
 UIElement* UI::GetFrontElement() const
 {
+    // If modal element is set then return it
+    if (modalElement_)
+        return modalElement_;
+
     const Vector<SharedPtr<UIElement> >& rootChildren = rootElement_->GetChildren();
     int maxPriority = M_MIN_INT;
     UIElement* front = 0;
-    
+
     for (unsigned i = 0; i < rootChildren.Size(); ++i)
     {
         // Do not take into account input-disabled elements, hidden elements or those that are always in the front
         if (!rootChildren[i]->IsEnabled() || !rootChildren[i]->IsVisible() || !rootChildren[i]->GetBringToBack())
             continue;
-        
+
         int priority = rootChildren[i]->GetPriority();
         if (priority > maxPriority)
         {
@@ -448,7 +486,7 @@ UIElement* UI::GetFrontElement() const
             front = rootChildren[i];
         }
     }
-    
+
     return front;
 }
 
@@ -464,27 +502,27 @@ void UI::Initialize()
 {
     Graphics* graphics = GetSubsystem<Graphics>();
     Renderer* renderer = GetSubsystem<Renderer>();
-    
+
     if (!graphics || !graphics->IsInitialized() || !renderer)
         return;
-    
+
     PROFILE(InitUI);
-    
+
     graphics_ = graphics;
-    
+
     rootElement_->SetSize(graphics->GetWidth(), graphics->GetHeight());
-    
+
     noTextureVS_ = renderer->GetVertexShader("Basic_VCol");
     diffTextureVS_ = renderer->GetVertexShader("Basic_DiffVCol");
     noTexturePS_ = renderer->GetPixelShader("Basic_VCol");
     diffTexturePS_ = renderer->GetPixelShader("Basic_DiffVCol");
     diffMaskTexturePS_ = renderer->GetPixelShader("Basic_DiffAlphaMaskVCol");
     alphaTexturePS_ = renderer->GetPixelShader("Basic_AlphaVCol");
-    
+
     vertexBuffer_ = new VertexBuffer(context_);
-    
+
     initialized_ = true;
-    
+
     SubscribeToEvent(E_POSTUPDATE, HANDLER(UI, HandlePostUpdate));
     SubscribeToEvent(E_RENDERUPDATE, HANDLER(UI, HandleRenderUpdate));
 
@@ -494,7 +532,7 @@ void UI::Initialize()
 void UI::Update(float timeStep, UIElement* element)
 {
     element->Update(timeStep);
-    
+
     const Vector<SharedPtr<UIElement> >& children = element->GetChildren();
     for (Vector<SharedPtr<UIElement> >::ConstIterator i = children.Begin(); i != children.End(); ++i)
         Update(timeStep, *i);
@@ -506,12 +544,12 @@ void UI::GetBatches(UIElement* element, IntRect currentScissor)
     element->AdjustScissor(currentScissor);
     if (currentScissor.left_ == currentScissor.right_ || currentScissor.top_ == currentScissor.bottom_)
         return;
-    
+
     element->SortChildren();
     const Vector<SharedPtr<UIElement> >& children = element->GetChildren();
     if (children.Empty())
         return;
-    
+
     // 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();
@@ -554,16 +592,16 @@ void UI::GetElementAt(UIElement*& result, UIElement* current, const IntVector2&
 {
     if (!current)
         return;
-    
+
     current->SortChildren();
     const Vector<SharedPtr<UIElement> >& children = current->GetChildren();
     LayoutMode parentLayoutMode = current->GetLayoutMode();
-    
+
     for (unsigned i = 0; i < children.Size(); ++i)
     {
         UIElement* element = children[i];
         bool hasChildren = element->GetNumChildren() > 0;
-        
+
         if (element != cursor_.Get() && element->IsVisible())
         {
             if (element->IsInside(position, true))
@@ -572,7 +610,7 @@ void UI::GetElementAt(UIElement*& result, UIElement* current, const IntVector2&
                 // are sorted from lowest to highest priority, the topmost match should remain
                 if (element->IsEnabled() || !enabledOnly)
                     result = element;
-                
+
                 if (hasChildren)
                     GetElementAt(result, element, position, enabledOnly);
                 // Layout optimization: if the element has no children, can break out after the first match
@@ -595,7 +633,7 @@ void UI::GetElementAt(UIElement*& result, UIElement* current, const IntVector2&
                         int screenPos = (parentLayoutMode == LM_HORIZONTAL) ? element->GetScreenPosition().x_ :
                             element->GetScreenPosition().y_;
                         int layoutMinSize = current->GetLayoutMinSize();
-                        
+
                         if (screenPos < 0 && layoutMinSize > 0)
                         {
                             unsigned toSkip = -screenPos / layoutMinSize;
@@ -633,7 +671,7 @@ UIElement* UI::GetFocusableElement(UIElement* element)
 void UI::HandleScreenMode(StringHash eventType, VariantMap& eventData)
 {
     using namespace ScreenMode;
-    
+
     if (!initialized_)
         Initialize();
     else
@@ -644,14 +682,14 @@ void UI::HandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
 {
     mouseButtons_ = eventData[MouseButtonDown::P_BUTTONS].GetInt();
     qualifiers_ = eventData[MouseButtonDown::P_QUALIFIERS].GetInt();
-    
+
     if (cursor_ && cursor_->IsVisible())
     {
         int button = eventData[MouseButtonDown::P_BUTTON].GetInt();
 
         IntVector2 pos = cursor_->GetPosition();
         WeakPtr<UIElement> element(GetElementAt(pos));
-        
+
         if (element)
         {
             // Handle focusing & bringing to front
@@ -660,10 +698,10 @@ void UI::HandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
                 SetFocusElement(element);
                 element->BringToFront();
             }
-            
+
             // Handle click
             element->OnClick(element->ScreenToElement(pos), pos, mouseButtons_, qualifiers_, cursor_);
-            
+
             // Handle start of drag. OnClick() may have caused destruction of the element, so check the pointer again
             if (element && !dragElement_ && mouseButtons_ == MOUSEB_LEFT)
             {
@@ -676,9 +714,9 @@ void UI::HandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
             // If clicked over no element, or a disabled element, lose focus
             SetFocusElement(0);
         }
-        
+
         using namespace UIMouseClick;
-        
+
         VariantMap eventData;
         eventData[UIMouseClick::P_ELEMENT] = (void*)element.Get();
         eventData[UIMouseClick::P_X] = pos.x_;
@@ -693,20 +731,20 @@ void UI::HandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
 void UI::HandleMouseButtonUp(StringHash eventType, VariantMap& eventData)
 {
     using namespace MouseButtonUp;
-    
+
     mouseButtons_ = eventData[P_BUTTONS].GetInt();
     qualifiers_ = eventData[P_QUALIFIERS].GetInt();
-    
+
     if (cursor_ && (cursor_->IsVisible() || dragElement_))
     {
         IntVector2 pos = cursor_->GetPosition();
-        
+
         if (dragElement_ && !mouseButtons_)
         {
             if (dragElement_->IsEnabled() && dragElement_->IsVisible())
             {
                 dragElement_->OnDragEnd(dragElement_->ScreenToElement(pos), pos, cursor_);
-                
+
                 // Drag and drop finish
                 bool dragSource = dragElement_ && (dragElement_->GetDragDropMode() & DD_SOURCE) != 0;
                 if (dragSource)
@@ -714,16 +752,16 @@ void UI::HandleMouseButtonUp(StringHash eventType, VariantMap& eventData)
                     WeakPtr<UIElement> target(GetElementAt(pos));
                     bool dragTarget = target && (target->GetDragDropMode() & DD_TARGET) != 0;
                     bool dragDropFinish = dragSource && dragTarget && target != dragElement_;
-                    
+
                     if (dragDropFinish)
                     {
                         bool accept = target->OnDragDropFinish(dragElement_);
-                        
+
                         // OnDragDropFinish() may have caused destruction of the elements, so check the pointers again
                         if (accept && dragElement_ && target)
                         {
                             using namespace DragDropFinish;
-                            
+
                             VariantMap eventData;
                             eventData[P_SOURCE] = (void*)dragElement_.Get();
                             eventData[P_TARGET] = (void*)target.Get();
@@ -733,7 +771,7 @@ void UI::HandleMouseButtonUp(StringHash eventType, VariantMap& eventData)
                     }
                 }
             }
-            
+
             dragElement_.Reset();
         }
     }
@@ -742,15 +780,15 @@ void UI::HandleMouseButtonUp(StringHash eventType, VariantMap& eventData)
 void UI::HandleMouseMove(StringHash eventType, VariantMap& eventData)
 {
     using namespace MouseMove;
-    
+
     mouseButtons_ = eventData[P_BUTTONS].GetInt();
     qualifiers_ = eventData[P_QUALIFIERS].GetInt();
-    
+
     if (cursor_)
     {
         Input* input = GetSubsystem<Input>();
         const IntVector2& rootSize = rootElement_->GetSize();
-        
+
         if (!input->IsMouseVisible())
         {
             // Relative mouse motion: move cursor only when visible
@@ -769,7 +807,7 @@ void UI::HandleMouseMove(StringHash eventType, VariantMap& eventData)
             // Absolute mouse motion: move always
             cursor_->SetPosition(IntVector2(eventData[P_X].GetInt(), eventData[P_Y].GetInt()));
         }
-        
+
         if (dragElement_ && mouseButtons_)
         {
             IntVector2 pos = cursor_->GetPosition();
@@ -787,13 +825,13 @@ void UI::HandleMouseMove(StringHash eventType, VariantMap& eventData)
 void UI::HandleMouseWheel(StringHash eventType, VariantMap& eventData)
 {
     using namespace MouseWheel;
-    
+
     mouseButtons_ = eventData[P_BUTTONS].GetInt();
     qualifiers_ = eventData[P_QUALIFIERS].GetInt();
     int delta = eventData[P_WHEEL].GetInt();
-    
+
     UIElement* element;
-    if (!nonFocusedMouseWheel_&& (element = GetFocusElement()))
+    if (!nonFocusedMouseWheel_&& (element = focusElement_))
         element->OnWheel(delta, mouseButtons_, qualifiers_);
     else
     {
@@ -827,19 +865,19 @@ void UI::HandleMouseWheel(StringHash eventType, VariantMap& eventData)
 void UI::HandleTouchBegin(StringHash eventType, VariantMap& eventData)
 {
     using namespace TouchBegin;
-    
+
     IntVector2 pos(eventData[P_X].GetInt(), eventData[P_Y].GetInt());
     WeakPtr<UIElement> element(GetElementAt(pos));
-    
+
     if (element)
     {
         // Handle focusing & bringing to front
         SetFocusElement(element);
         element->BringToFront();
-        
+
         // Handle click
         element->OnClick(element->ScreenToElement(pos), pos, MOUSEB_LEFT, 0, 0);
-        
+
         // Handle start of drag. OnClick() may have caused destruction of the element, so check the pointer again
         if (element && !dragElement_ )
         {
@@ -852,9 +890,9 @@ void UI::HandleTouchBegin(StringHash eventType, VariantMap& eventData)
         // If clicked over no element, or a disabled element, lose focus
         SetFocusElement(0);
     }
-    
+
     using namespace UIMouseClick;
-    
+
     VariantMap clickEventData;
     clickEventData[UIMouseClick::P_ELEMENT] = (void*)element.Get();
     clickEventData[UIMouseClick::P_X] = pos.x_;
@@ -868,20 +906,20 @@ void UI::HandleTouchBegin(StringHash eventType, VariantMap& eventData)
 void UI::HandleTouchEnd(StringHash eventType, VariantMap& eventData)
 {
     using namespace TouchEnd;
-    
+
     IntVector2 pos(eventData[P_X].GetInt(), eventData[P_Y].GetInt());
-    
+
     // Transmit hover end to the position where the finger was lifted
     UIElement* element = GetElementAt(pos);
     if (element && element->IsEnabled())
         element->OnHover(element->ScreenToElement(pos), pos, 0, 0, 0);
-    
+
     if (dragElement_)
     {
         if (dragElement_->IsEnabled() && dragElement_->IsVisible())
         {
             dragElement_->OnDragEnd(dragElement_->ScreenToElement(pos), pos, cursor_);
-            
+
             // Drag and drop finish
             bool dragSource = dragElement_ && (dragElement_->GetDragDropMode() & DD_SOURCE) != 0;
             if (dragSource)
@@ -889,16 +927,16 @@ void UI::HandleTouchEnd(StringHash eventType, VariantMap& eventData)
                 WeakPtr<UIElement> target(GetElementAt(pos));
                 bool dragTarget = target && (target->GetDragDropMode() & DD_TARGET) != 0;
                 bool dragDropFinish = dragSource && dragTarget && target != dragElement_;
-                
+
                 if (dragDropFinish)
                 {
                     bool accept = target->OnDragDropFinish(dragElement_);
-                    
+
                     // OnDragDropFinish() may have caused destruction of the elements, so check the pointers again
                     if (accept && dragElement_ && target)
                     {
                         using namespace DragDropFinish;
-                        
+
                         VariantMap eventData;
                         eventData[P_SOURCE] = (void*)dragElement_.Get();
                         eventData[P_TARGET] = (void*)target.Get();
@@ -908,7 +946,7 @@ void UI::HandleTouchEnd(StringHash eventType, VariantMap& eventData)
                 }
             }
         }
-        
+
         dragElement_.Reset();
     }
 }
@@ -916,9 +954,9 @@ void UI::HandleTouchEnd(StringHash eventType, VariantMap& eventData)
 void UI::HandleTouchMove(StringHash eventType, VariantMap& eventData)
 {
     using namespace TouchMove;
-    
+
     IntVector2 pos(eventData[P_X].GetInt(), eventData[P_Y].GetInt());
-    
+
     if (dragElement_)
     {
         if (dragElement_->IsEnabled() && dragElement_->IsVisible())
@@ -934,12 +972,12 @@ void UI::HandleTouchMove(StringHash eventType, VariantMap& eventData)
 void UI::HandleKeyDown(StringHash eventType, VariantMap& eventData)
 {
     using namespace KeyDown;
-    
+
     mouseButtons_ = eventData[P_BUTTONS].GetInt();
     qualifiers_ = eventData[P_QUALIFIERS].GetInt();
     int key = eventData[P_KEY].GetInt();
-    
-    UIElement* element = GetFocusElement();
+
+    UIElement* element = focusElement_;
     if (element)
     {
         // Switch focus between focusable elements in the same top level window
@@ -981,11 +1019,11 @@ void UI::HandleKeyDown(StringHash eventType, VariantMap& eventData)
 void UI::HandleChar(StringHash eventType, VariantMap& eventData)
 {
     using namespace Char;
-    
+
     mouseButtons_ = eventData[P_BUTTONS].GetInt();
     qualifiers_ = eventData[P_QUALIFIERS].GetInt();
-    
-    UIElement* element = GetFocusElement();
+
+    UIElement* element = focusElement_;
     if (element)
         element->OnChar(eventData[P_CHAR].GetInt(), mouseButtons_, qualifiers_);
 }
@@ -993,7 +1031,7 @@ void UI::HandleChar(StringHash eventType, VariantMap& eventData)
 void UI::HandlePostUpdate(StringHash eventType, VariantMap& eventData)
 {
     using namespace PostUpdate;
-    
+
     Update(eventData[P_TIMESTEP].GetFloat());
 }
 
@@ -1005,7 +1043,7 @@ void UI::HandleRenderUpdate(StringHash eventType, VariantMap& eventData)
 void RegisterUILibrary(Context* context)
 {
     Font::RegisterObject(context);
-    
+
     UIElement::RegisterObject(context);
     BorderImage::RegisterObject(context);
     Sprite::RegisterObject(context);

+ 12 - 6
Engine/UI/UI.h

@@ -41,17 +41,19 @@ class XMLFile;
 class UI : public Object
 {
     OBJECT(UI);
-    
+
 public:
     /// Construct.
     UI(Context* context);
     /// Destruct.
     virtual ~UI();
-    
+
     /// Set cursor UI element.
     void SetCursor(Cursor* cursor);
     /// Set focused UI element.
     void SetFocusElement(UIElement* element);
+    /// Set modal element. Until it is dismissed, all the inputs and events are only sent to this modal element. Return true when successful.
+    bool SetModalElement(UIElement* modalElement);
     /// Clear the UI (excluding the cursor.)
     void Clear();
     /// Update the UI logic. Called by HandlePostUpdate().
@@ -70,7 +72,7 @@ public:
     void SetClipBoardText(const String& text);
     /// Set mouse wheel handling flag.
     void SetNonFocusedMouseWheel(bool nonFocusedMouseWheel);
-    
+
     /// Return root UI element.
     UIElement* GetRoot() const { return rootElement_; }
     /// Return cursor.
@@ -80,7 +82,9 @@ public:
     /// Return UI element at screen coordinates.
     UIElement* GetElementAt(int x, int y, bool enabledOnly = true);
     /// Return focused element.
-    UIElement* GetFocusElement() const;
+    UIElement* GetFocusElement() const { return focusElement_; }
+    /// Return modal element.
+    UIElement* GetModalElement() const { return modalElement_; }
     /// Return topmost enabled root-level element.
     UIElement* GetFrontElement() const;
     /// Return cursor position.
@@ -89,7 +93,7 @@ public:
     const String& GetClipBoardText() const { return clipBoard_; }
     /// Return mouse wheel handling flag.
     bool IsNonFocusedMouseWheel() const { return nonFocusedMouseWheel_; }
-    
+
 private:
     /// Initialize when screen mode initially se.
     void Initialize();
@@ -125,7 +129,7 @@ private:
     void HandlePostUpdate(StringHash eventType, VariantMap& eventData);
     /// Handle render update event.
     void HandleRenderUpdate(StringHash eventType, VariantMap& eventData);
-    
+
     /// Graphics subsystem.
     WeakPtr<Graphics> graphics_;
     /// Vertex shader for no texture.
@@ -148,6 +152,8 @@ private:
     WeakPtr<UIElement> dragElement_;
     /// Currently focused element
     WeakPtr<UIElement> focusElement_;
+    /// Modal element.
+    WeakPtr<UIElement> modalElement_;
     /// UI rendering batches.
     PODVector<UIBatch> batches_;
     /// UI rendering vertex data.

+ 19 - 1
Engine/UI/UIElement.cpp

@@ -1228,6 +1228,24 @@ UIElement* UIElement::GetChild(const String& name, bool recursive) const
     return 0;
 }
 
+UIElement* UIElement::GetChild(const ShortStringHash& key, const Variant& value, bool recursive) const
+{
+    for (Vector<SharedPtr<UIElement> >::ConstIterator i = children_.Begin(); i != children_.End(); ++i)
+    {
+        if ((*i)->GetVar(key) == value)
+            return *i;
+
+        if (recursive)
+        {
+            UIElement* element = (*i)->GetChild(key, value, true);
+            if (element)
+                return element;
+        }
+    }
+
+    return 0;
+}
+
 UIElement* UIElement::GetRoot() const
 {
     UIElement* root = parent_;
@@ -1250,7 +1268,7 @@ const Color& UIElement::GetDerivedColor() const
     return derivedColor_;
 }
 
-const Variant& UIElement::GetVar(ShortStringHash key) const
+const Variant& UIElement::GetVar(const ShortStringHash& key) const
 {
     VariantMap::ConstIterator i = vars_.Find(key);
     if (i != vars_.End())

+ 3 - 1
Engine/UI/UIElement.h

@@ -378,6 +378,8 @@ public:
     UIElement* GetChild(unsigned index) const;
     /// Return child element by name.
     UIElement* GetChild(const String& name, bool recursive = false) const;
+    /// Return child element by variable.
+    UIElement* GetChild(const ShortStringHash& key, const Variant& value, bool recursive = false) const;
     /// Return immediate child elements.
     const Vector<SharedPtr<UIElement> >& GetChildren() const { return children_; }
     /// Return child elements either recursively or non-recursively.
@@ -389,7 +391,7 @@ public:
     /// Return derived color. Only valid when no gradient.
     const Color& GetDerivedColor() const;
     /// Return a user variable.
-    const Variant& GetVar(ShortStringHash key) const;
+    const Variant& GetVar(const ShortStringHash& key) const;
     /// Return all user variables.
     const VariantMap& GetVars() const { return vars_; }
     

+ 74 - 24
Engine/UI/Window.cpp

@@ -24,6 +24,7 @@
 #include "Context.h"
 #include "Cursor.h"
 #include "InputEvents.h"
+#include "UI.h"
 #include "Window.h"
 
 #include "DebugNew.h"
@@ -40,7 +41,11 @@ Window::Window(Context* context) :
     movable_(false),
     resizable_(false),
     resizeBorder_(DEFAULT_RESIZE_BORDER, DEFAULT_RESIZE_BORDER, DEFAULT_RESIZE_BORDER, DEFAULT_RESIZE_BORDER),
-    dragMode_(DRAG_NONE)
+    dragMode_(DRAG_NONE),
+    modal_(false),
+    modalFrameColor_(Color::TRANSPARENT),
+    modalFrameSize_(IntVector2::ZERO)
+
 {
     bringToFront_ = true;
     clipChildren_ = true;
@@ -54,14 +59,42 @@ Window::~Window()
 void Window::RegisterObject(Context* context)
 {
     context->RegisterFactory<Window>();
-    
+
     REF_ACCESSOR_ATTRIBUTE(Window, VAR_INTRECT, "Resize Border", GetResizeBorder, SetResizeBorder, IntRect, IntRect(DEFAULT_RESIZE_BORDER, \
         DEFAULT_RESIZE_BORDER, DEFAULT_RESIZE_BORDER, DEFAULT_RESIZE_BORDER), AM_FILE);
     ACCESSOR_ATTRIBUTE(Window, VAR_BOOL, "Is Movable", IsMovable, SetMovable, bool, false, AM_FILE);
     ACCESSOR_ATTRIBUTE(Window, VAR_BOOL, "Is Resizable", IsResizable, SetResizable, bool, false, AM_FILE);
+    ACCESSOR_ATTRIBUTE(Window, VAR_BOOL, "Is Modal", IsModal, SetModal, bool, false, AM_FILE);
+    REF_ACCESSOR_ATTRIBUTE(Window, VAR_COLOR, "Modal Frame Color", GetModalFrameColor, SetModalFrameColor, Color, Color::TRANSPARENT, AM_FILE);
+    REF_ACCESSOR_ATTRIBUTE(Window, VAR_INTVECTOR2, "Modal Frame Size", GetModalFrameSize, SetModalFrameSize, IntVector2, IntVector2::ZERO, AM_FILE);
     COPY_BASE_ATTRIBUTES(Window, BorderImage);
 }
 
+void Window::GetBatches(PODVector<UIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor)
+{
+    BorderImage::GetBatches(batches, vertexData, currentScissor);
+
+    if (modal_)
+    {
+        UIBatch batch(this, BLEND_ALPHA, currentScissor, 0, &vertexData);
+
+        int x = GetIndentWidth();
+        IntVector2 size = GetSize();
+        size.x_ -= x;
+
+        // Left
+        batch.AddQuad(x - modalFrameSize_.x_, 0, modalFrameSize_.x_, size.y_, 0, 0, 0, 0, modalFrameColor_);
+        // Top
+        batch.AddQuad(x - modalFrameSize_.x_, -modalFrameSize_.y_, size.x_ + 2 * modalFrameSize_.x_, modalFrameSize_.y_, 0, 0, 0, 0, modalFrameColor_);
+        // Right
+        batch.AddQuad(size.x_, 0, modalFrameSize_.x_, size.y_, 0, 0, 0, 0, modalFrameColor_);
+        // Bottom
+        batch.AddQuad(x - modalFrameSize_.x_, size.y_, size.x_ + 2 * modalFrameSize_.x_, modalFrameSize_.y_, 0, 0, 0, 0, modalFrameColor_);
+
+        UIBatch::AddOrMerge(batch, batches);
+    }
+}
+
 void Window::OnHover(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor)
 {
     if (dragMode_ == DRAG_NONE)
@@ -80,7 +113,7 @@ void Window::OnDragBegin(const IntVector2& position, const IntVector2& screenPos
         dragMode_ = DRAG_NONE;
         return;
     }
-    
+
     dragBeginCursor_ = screenPosition;
     dragBeginPosition_ = GetPosition();
     dragBeginSize_ = GetSize();
@@ -92,53 +125,53 @@ void Window::OnDragMove(const IntVector2& position, const IntVector2& screenPosi
 {
     if (dragMode_ == DRAG_NONE)
         return;
-    
+
     IntVector2 delta = screenPosition - dragBeginCursor_;
 
     const IntVector2& position_ = GetPosition();
     const IntVector2& size_ = GetSize();
     const IntVector2& minSize_ = GetMinSize();
     const IntVector2& maxSize_ = GetMaxSize();
-    
+
     switch (dragMode_)
     {
     case DRAG_MOVE:
         SetPosition(dragBeginPosition_ + delta);
         break;
-        
+
     case DRAG_RESIZE_TOPLEFT:
-        SetPosition(Clamp(dragBeginPosition_.x_ + delta.x_, position_.x_ - (maxSize_.x_ - size_.x_), position_.x_ + (size_.x_ - minSize_.x_)), 
+        SetPosition(Clamp(dragBeginPosition_.x_ + delta.x_, position_.x_ - (maxSize_.x_ - size_.x_), position_.x_ + (size_.x_ - minSize_.x_)),
             Clamp(dragBeginPosition_.y_ + delta.y_, position_.y_ - (maxSize_.y_ - size_.y_), position_.y_ + (size_.y_ - minSize_.y_)));
         SetSize(dragBeginSize_ - delta);
         break;
-        
+
     case DRAG_RESIZE_TOP:
         SetPosition(dragBeginPosition_.x_, Clamp(dragBeginPosition_.y_ + delta.y_, position_.y_ - (maxSize_.y_ - size_.y_), position_.y_ + (size_.y_ - minSize_.y_)));
         SetSize(dragBeginSize_.x_, dragBeginSize_.y_ - delta.y_);
         break;
-        
+
     case DRAG_RESIZE_TOPRIGHT:
         SetPosition(dragBeginPosition_.x_, dragBeginPosition_.y_ + delta.y_);
         SetSize(dragBeginSize_.x_ + delta.x_, dragBeginSize_.y_ - delta.y_);
         break;
-        
+
     case DRAG_RESIZE_RIGHT:
         SetSize(dragBeginSize_.x_ + delta.x_, dragBeginSize_.y_);
         break;
-        
+
     case DRAG_RESIZE_BOTTOMRIGHT:
         SetSize(dragBeginSize_ + delta);
         break;
-        
+
     case DRAG_RESIZE_BOTTOM:
         SetSize(dragBeginSize_.x_, dragBeginSize_.y_ + delta.y_);
         break;
-        
+
     case DRAG_RESIZE_BOTTOMLEFT:
         SetPosition(Clamp(dragBeginPosition_.x_ + delta.x_, position_.x_ - (maxSize_.x_ - size_.x_), position_.x_ + (size_.x_ - minSize_.x_)), dragBeginPosition_.y_);
         SetSize(dragBeginSize_.x_ - delta.x_, dragBeginSize_.y_ + delta.y_);
         break;
-        
+
     case DRAG_RESIZE_LEFT:
         SetPosition(Clamp(dragBeginPosition_.x_ + delta.x_, position_.x_ - (maxSize_.x_ - size_.x_), position_.x_ + (size_.x_ - minSize_.x_)), dragBeginPosition_.y_);
         SetSize(dragBeginSize_.x_ - delta.x_, dragBeginSize_.y_);
@@ -147,7 +180,7 @@ void Window::OnDragMove(const IntVector2& position, const IntVector2& screenPosi
     default:
         break;
     }
-    
+
     ValidatePosition();
     SetCursorShape(dragMode_, cursor);
 }
@@ -175,10 +208,27 @@ void Window::SetResizeBorder(const IntRect& rect)
     resizeBorder_.bottom_ = Max(rect.bottom_, 0);
 }
 
+void Window::SetModal(bool modal)
+{
+    UI* ui = GetSubsystem<UI>();
+    if (ui->SetModalElement(modal ? this : 0))
+        modal_ = modal;
+}
+
+void Window::SetModalFrameColor(const Color& color)
+{
+    modalFrameColor_ = color;
+}
+
+void Window::SetModalFrameSize(const IntVector2& size)
+{
+    modalFrameSize_ = size;
+}
+
 WindowDragMode Window::GetDragMode(const IntVector2& position) const
 {
     WindowDragMode mode = DRAG_NONE;
-    
+
     // Top row
     if (position.y_ < resizeBorder_.top_)
     {
@@ -220,7 +270,7 @@ WindowDragMode Window::GetDragMode(const IntVector2& position) const
                 mode = DRAG_RESIZE_RIGHT;
         }
     }
-    
+
     return mode;
 }
 
@@ -228,7 +278,7 @@ void Window::SetCursorShape(WindowDragMode mode, Cursor* cursor) const
 {
     if (!cursor)
         return;
-    
+
     switch (mode)
     {
     case DRAG_NONE:
@@ -240,7 +290,7 @@ void Window::SetCursorShape(WindowDragMode mode, Cursor* cursor) const
     case DRAG_RESIZE_BOTTOM:
         cursor->SetShape(CS_RESIZEVERTICAL);
         break;
-        
+
     case DRAG_RESIZE_LEFT:
     case DRAG_RESIZE_RIGHT:
         cursor->SetShape(CS_RESIZEHORIZONTAL);
@@ -250,12 +300,12 @@ void Window::SetCursorShape(WindowDragMode mode, Cursor* cursor) const
     case DRAG_RESIZE_BOTTOMLEFT:
         cursor->SetShape(CS_RESIZEDIAGONAL_TOPRIGHT);
         break;
-        
+
     case DRAG_RESIZE_TOPLEFT:
     case DRAG_RESIZE_BOTTOMRIGHT:
         cursor->SetShape(CS_RESIZEDIAGONAL_TOPLEFT);
         break;
-    
+
     default:
         break;
     }
@@ -266,14 +316,14 @@ void Window::ValidatePosition()
     // Check that window does not go more than halfway outside its parent in either dimension
     if (!parent_)
         return;
-    
+
     const IntVector2& parentSize = parent_->GetSize();
     IntVector2 position = GetPosition();
     IntVector2 halfSize = GetSize() / 2;
-    
+
     position.x_ = Clamp(position.x_, -halfSize.x_, parentSize.x_ - halfSize.x_);
     position.y_ = Clamp(position.y_, -halfSize.y_, parentSize.y_ - halfSize.y_);
-    
+
     SetPosition(position);
 }
 

+ 31 - 6
Engine/UI/Window.h

@@ -46,7 +46,7 @@ enum WindowDragMode
 class Window : public BorderImage
 {
     OBJECT(Window);
-    
+
 public:
     /// Construct.
     Window(Context* context);
@@ -54,7 +54,10 @@ public:
     virtual ~Window();
     /// Register object factory.
     static void RegisterObject(Context* context);
-    
+
+    /// Return UI rendering batches.
+    virtual void GetBatches(PODVector<UIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor);
+
     /// React to mouse hover.
     virtual void OnHover(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor);
     /// React to mouse drag begin.
@@ -63,21 +66,33 @@ public:
     virtual void OnDragMove(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor);
     /// React to mouse drag end.
     virtual void OnDragEnd(const IntVector2& position, const IntVector2& screenPosition, Cursor* cursor);
-    
+
     /// Set whether can be moved.
     void SetMovable(bool enable);
     /// Set whether can be resized.
     void SetResizable(bool enable);
     /// Set resize area width at edges.
     void SetResizeBorder(const IntRect& rect);
-    
+    /// Set modal flag. When the modal flag is set, the focused window needs to be dismissed first to allow other UI elements to gain focus.
+    void SetModal(bool modal);
+    /// Set modal frame color.
+    void SetModalFrameColor(const Color& color);
+    /// Set modal frame size.
+    void SetModalFrameSize(const IntVector2& size);
+
     /// Return whether is movable.
     bool IsMovable() const { return movable_; }
     /// Return whether is resizable.
     bool IsResizable() const { return resizable_; }
     /// Return resize area width at edges.
     const IntRect& GetResizeBorder() const { return resizeBorder_; }
-    
+    /// Return modal flag.
+    bool IsModal() const { return modal_; }
+    /// Get modal frame color.
+    const Color& GetModalFrameColor() const { return modalFrameColor_; }
+    /// Get modal frame size.
+    const IntVector2& GetModalFrameSize() const { return modalFrameSize_; }
+
 protected:
     /// Identify drag mode (move/resize.)
     WindowDragMode GetDragMode(const IntVector2& position) const;
@@ -87,7 +102,7 @@ protected:
     void ValidatePosition();
     /// Check whether alignment supports moving and resizing.
     bool CheckAlignment() const;
-    
+
     /// Movable flag.
     bool movable_;
     /// Resizable flag.
@@ -102,6 +117,16 @@ protected:
     IntVector2 dragBeginPosition_;
     /// Original size at drag begin.
     IntVector2 dragBeginSize_;
+    /// Modal flag.
+    bool modal_;
+    /// Modal frame color, used when modal flag is set.
+    Color modalFrameColor_;
+    /// Modal frame size, used when modal flag is set.
+    IntVector2 modalFrameSize_;
+
+private:
+    /// Handle modal window being focused.
+    void HandleModalFocused(StringHash eventType, VariantMap& eventData);
 };
 
 }