Explorar el Código

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 hace 12 años
padre
commit
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);
 };
 
 }