Browse Source

Fixed UI-element debug draw overdrawing the modal element and menu popup. Refactored UI subsystem to support multiple modal elements and auto-dismissal of modal elements when ESC is pressed. The 'Is Modal' attribute of Window UI-element type can be tested in Editor.

Wei Tjong Yao 12 years ago
parent
commit
05fd7419a7

+ 3 - 3
Bin/Data/Scripts/Editor/AttributeEditor.as

@@ -493,7 +493,7 @@ void LoadAttributeEditor(UIElement@ parent, const Variant&in value, const Attrib
             // Reevaluate aach variant in the vector
             // Reevaluate aach variant in the vector
             for (uint i = 0; i < values.length; ++i)
             for (uint i = 0; i < values.length; ++i)
             {
             {
-                Array<Variant>@ vector = values[i].GetVariantVector();                
+                Array<Variant>@ vector = values[i].GetVariantVector();
                 if (subIndex < vector.length)
                 if (subIndex < vector.length)
                 {
                 {
                     Variant value = vector[subIndex];
                     Variant value = vector[subIndex];
@@ -529,7 +529,7 @@ void LoadAttributeEditor(UIElement@ parent, const Variant&in value, const Attrib
             Variant firstValue = map[keys[subIndex]];
             Variant firstValue = map[keys[subIndex]];
             bool sameValue = true;
             bool sameValue = true;
             Array<Variant> varValues;
             Array<Variant> varValues;
-            
+
             // Reevaluate each variant in the map
             // Reevaluate each variant in the map
             for (uint i = 0; i < values.length; ++i)
             for (uint i = 0; i < values.length; ++i)
             {
             {
@@ -781,7 +781,7 @@ void EditAttribute(StringHash eventType, VariantMap& eventData)
     // If not an intermediate edit, reload the editor fields with validated values
     // 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)
     // (attributes may have interactions; therefore we load everything, not just the value being edited)
     if (!intermediateEdit)
     if (!intermediateEdit)
-        UpdateAttributeInspector(false);
+        attributesDirty = true;
 }
 }
 
 
 // Resource picker functionality
 // Resource picker functionality

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

@@ -37,7 +37,7 @@ class GizmoAxis
     void Update(Ray cameraRay, float scale, bool drag)
     void Update(Ray cameraRay, float scale, bool drag)
     {
     {
         // Do not select when UI has modal element
         // Do not select when UI has modal element
-        if (ui.modalElement !is null)
+        if (ui.HasModalElement())
         {
         {
             selected = false;
             selected = false;
             return;
             return;

+ 14 - 0
Bin/Data/Scripts/Editor/EditorNodeWindow.as

@@ -308,8 +308,22 @@ void PostEditAttribute(Array<Serializable@>@ serializables, uint index, const Ar
 
 
     SaveEditActionGroup(group);
     SaveEditActionGroup(group);
 
 
+    // If a UI-element changing its 'Is Modal' attribute, clear the hierarchy list selection
+    bool saveModalElement = false;
+    if (serializables[0].attributeInfos[index].name == "Is Modal")
+    {
+        hierarchyList.ClearSelection();
+        saveModalElement = true;
+    }
+
     for (uint i = 0; i < serializables.length; ++i)
     for (uint i = 0; i < serializables.length; ++i)
+    {
         PostEditAttribute(serializables[i], index);
         PostEditAttribute(serializables[i], index);
+
+        // Need to save a reference of the modal element being tested as otherwise there is no way to get it back when it is being dismissed by ESC key
+        if (saveModalElement)
+            modalUIElements.Push(serializables[i]);
+    }
 }
 }
 
 
 void PostEditAttribute(Serializable@ serializable, uint index)
 void PostEditAttribute(Serializable@ serializable, uint index)

+ 28 - 11
Bin/Data/Scripts/Editor/EditorUI.as

@@ -13,7 +13,6 @@ const ShortStringHash CURSOR_TYPE("Cursor");
 
 
 const String TEMP_SCENE_NAME("_tempscene_.xml");
 const String TEMP_SCENE_NAME("_tempscene_.xml");
 const ShortStringHash CALLBACK_VAR("Callback");
 const ShortStringHash CALLBACK_VAR("Callback");
-const ShortStringHash POPUP_ORIGIN_VAR("Origin");
 const ShortStringHash INDENT_MODIFIED_BY_ICON_VAR("IconIndented");
 const ShortStringHash INDENT_MODIFIED_BY_ICON_VAR("IconIndented");
 
 
 const int SHOW_POPUP_INDICATOR = -1;
 const int SHOW_POPUP_INDICATOR = -1;
@@ -126,6 +125,12 @@ void CreateMenuBar()
     uiMenuBar.SetLayout(LM_HORIZONTAL);
     uiMenuBar.SetLayout(LM_HORIZONTAL);
     uiMenuBar.opacity = uiMaxOpacity;
     uiMenuBar.opacity = uiMaxOpacity;
     uiMenuBar.SetFixedWidth(graphics.width);
     uiMenuBar.SetFixedWidth(graphics.width);
+
+    BorderImage@ logo = BorderImage("Logo");
+    logo.texture = cache.GetResource("Texture2D", "Textures/Logo.png");
+    logo.SetFixedWidth(50);
+    uiMenuBar.AddChild(logo);
+
     ui.root.AddChild(uiMenuBar);
     ui.root.AddChild(uiMenuBar);
 
 
     {
     {
@@ -799,22 +804,23 @@ void HandleKeyDown(StringHash eventType, VariantMap& eventData)
 
 
     if (key == KEY_ESC)
     if (key == KEY_ESC)
     {
     {
-        UIElement@ front = ui.frontElement;
-        if (uiFileSelector !is null && front is uiFileSelector.window)
-            CloseFileSelector();
-        else if (uiHidden)
+        if (uiHidden)
             UnhideUI();
             UnhideUI();
         else if (console.visible)
         else if (console.visible)
             console.visible = false;
             console.visible = false;
-        else if (front is settingsDialog || front is preferencesDialog)
+        else
         {
         {
-            ui.focusElement = null;
-            front.visible = false;
+            UIElement@ front = ui.frontElement;
+            if (front is settingsDialog || front is preferencesDialog)
+            {
+                ui.focusElement = null;
+                front.visible = false;
+            }
         }
         }
     }
     }
 
 
     // Ignore other keys when UI has a modal element
     // Ignore other keys when UI has a modal element
-    else if (ui.modalElement !is null)
+    else if (ui.HasModalElement())
         return;
         return;
 
 
     else if (key == KEY_F1)
     else if (key == KEY_F1)
@@ -878,8 +884,8 @@ void FadeUI(bool fade = true)
     Array<UIElement@> children = ui.root.GetChildren();
     Array<UIElement@> children = ui.root.GetChildren();
     for (uint i = 0; i < children.length; ++i)
     for (uint i = 0; i < children.length; ++i)
     {
     {
-        // Texts, popup&modal windows, and editorUIElement are excluded
-        if (children[i].type != TEXT_TYPE && children[i] !is editorUIElement && children[i] !is ui.modalElement && !children[i].vars.Contains(POPUP_ORIGIN_VAR))
+        // Texts, popup&modal windows (which are anyway only in ui.modalRoot), and editorUIElement are excluded
+        if (children[i].type != TEXT_TYPE && children[i] !is editorUIElement)
             children[i].opacity = opacity;
             children[i].opacity = opacity;
     }
     }
 }
 }
@@ -984,4 +990,15 @@ void UpdateDirtyUI()
     // Perform some event-triggered updates latently in case a large hierarchy was changed
     // Perform some event-triggered updates latently in case a large hierarchy was changed
     if (attributesFullDirty || attributesDirty)
     if (attributesFullDirty || attributesDirty)
         UpdateAttributeInspector(attributesFullDirty);
         UpdateAttributeInspector(attributesFullDirty);
+    
+    for (uint i = 0; i < modalUIElements.length; ++i)
+    {
+        // If it is detached then reparent it to editor root UI element
+        if (modalUIElements[i].parent is null)
+        {
+            editorUIElement.AddChild(modalUIElements[i]);
+            modalUIElements.Erase(i);
+            break;  // Only one at a time
+        }
+    }
 }
 }

+ 5 - 7
Bin/Data/Scripts/Editor/EditorUIElement.as

@@ -8,6 +8,7 @@ String childElementFileName;
 UIElement@ editUIElement;
 UIElement@ editUIElement;
 Array<Serializable@> selectedUIElements;
 Array<Serializable@> selectedUIElements;
 Array<Serializable@> editUIElements;
 Array<Serializable@> editUIElements;
+Array<UIElement@> modalUIElements;
 
 
 Array<XMLFile@> uiElementCopyBuffer;
 Array<XMLFile@> uiElementCopyBuffer;
 
 
@@ -76,13 +77,10 @@ void OpenUIElement(const String&in fileName)
     ui.cursor.shape = CS_BUSY;
     ui.cursor.shape = CS_BUSY;
 
 
     // Check if the UI element has been opened before
     // Check if the UI element has been opened before
-    for (uint i = 0; i < editorUIElement.numChildren; ++i)
+    if (editorUIElement.GetChild(FILENAME_VAR, Variant(fileName)) !is null)
     {
     {
-        if (editorUIElement.children[i].vars[FILENAME_VAR] == fileName)
-        {
-            log.Warning("UI element is already opened: " + fileName);
-            return;
-        }
+        log.Warning("UI element is already opened: " + fileName);
+        return;
     }
     }
 
 
     // Always load from the filesystem, not from resource paths
     // Always load from the filesystem, not from resource paths
@@ -208,7 +206,7 @@ bool SaveUIElementWithExistingName()
     Variant fileNameVar = editUIElement.GetVar(FILENAME_VAR);
     Variant fileNameVar = editUIElement.GetVar(FILENAME_VAR);
     if (fileNameVar.empty)  // Only top level UI-element has this variable
     if (fileNameVar.empty)  // Only top level UI-element has this variable
         return false;
         return false;
-    
+
     if (fileNameVar.GetString().empty)
     if (fileNameVar.GetString().empty)
         return PickFile();  // No name yet, so pick one
         return PickFile();  // No name yet, so pick one
     else
     else

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

@@ -362,12 +362,9 @@ void HandlePostRenderUpdate()
     for (uint i = 0; i < selectedComponents.length; ++i)
     for (uint i = 0; i < selectedComponents.length; ++i)
         selectedComponents[i].DrawDebugGeometry(debug, false);
         selectedComponents[i].DrawDebugGeometry(debug, false);
 
 
-    // Visualize the currently selected UI-elements but only when UI does not have modal element
-    if (ui.modalElement is null)
-    {
-        for (uint i = 0; i < selectedUIElements.length; ++i)
-            ui.DebugDraw(selectedUIElements[i]);
-    }
+    // Visualize the currently selected UI-elements
+    for (uint i = 0; i < selectedUIElements.length; ++i)
+        ui.DebugDraw(selectedUIElements[i]);
 
 
     if (renderingDebug)
     if (renderingDebug)
         renderer.DrawDebugGeometry(false);
         renderer.DrawDebugGeometry(false);
@@ -387,7 +384,7 @@ void ViewMouseClick()
 void ViewRaycast(bool mouseClick)
 void ViewRaycast(bool mouseClick)
 {
 {
     // Ignore if UI has modal element
     // Ignore if UI has modal element
-    if (ui.modalElement !is null)
+    if (ui.HasModalElement())
         return;
         return;
 
 
     // Do not raycast / change selection if hovering over the gizmo
     // Do not raycast / change selection if hovering over the gizmo

BIN
Bin/Data/Textures/Logo.png


+ 2 - 1
Docs/ScriptAPI.dox

@@ -5028,6 +5028,7 @@ Methods:<br>
 - bool SaveLayout(File@, UIElement@)
 - bool SaveLayout(File@, UIElement@)
 - UIElement@ GetElementAt(const IntVector2&, bool arg1 = true)
 - UIElement@ GetElementAt(const IntVector2&, bool arg1 = true)
 - UIElement@ GetElementAt(int, int, bool arg2 = true)
 - UIElement@ GetElementAt(int, int, bool arg2 = true)
+- bool HasModalElement() const
 
 
 Properties:<br>
 Properties:<br>
 - ShortStringHash type (readonly)
 - ShortStringHash type (readonly)
@@ -5037,9 +5038,9 @@ Properties:<br>
 - Cursor@ cursor
 - Cursor@ cursor
 - IntVector2 cursorPosition (readonly)
 - IntVector2 cursorPosition (readonly)
 - UIElement@ focusElement
 - UIElement@ focusElement
-- UIElement@ modalElement (readonly)
 - UIElement@ frontElement (readonly)
 - UIElement@ frontElement (readonly)
 - UIElement@ root (readonly)
 - UIElement@ root (readonly)
+- UIElement@ modalRoot (readonly)
 - bool nonFocusedMouseWheel
 - bool nonFocusedMouseWheel
 
 
 
 

+ 3 - 2
Engine/Engine/UIAPI.cpp

@@ -539,14 +539,15 @@ static void RegisterUI(asIScriptEngine* engine)
     engine->RegisterObjectMethod("UI", "bool SaveLayout(File@+, UIElement@+)", asFUNCTION(UISaveLayout), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("UI", "bool SaveLayout(File@+, UIElement@+)", asFUNCTION(UISaveLayout), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("UI", "UIElement@+ GetElementAt(const IntVector2&in, bool activeOnly = true)", asMETHODPR(UI, GetElementAt, (const IntVector2&, bool), UIElement*), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "UIElement@+ GetElementAt(const IntVector2&in, bool activeOnly = true)", asMETHODPR(UI, GetElementAt, (const IntVector2&, bool), UIElement*), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "UIElement@+ GetElementAt(int, int, bool activeOnly = true)", asMETHODPR(UI, GetElementAt, (int, int, bool), UIElement*), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "UIElement@+ GetElementAt(int, int, bool activeOnly = true)", asMETHODPR(UI, GetElementAt, (int, int, bool), UIElement*), asCALL_THISCALL);
+    engine->RegisterObjectMethod("UI", "bool HasModalElement() const", asMETHOD(UI, HasModalElement), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "void set_cursor(Cursor@+)", asMETHOD(UI, SetCursor), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "void set_cursor(Cursor@+)", asMETHOD(UI, SetCursor), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "Cursor@+ get_cursor() const", asMETHOD(UI, GetCursor), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "Cursor@+ get_cursor() const", asMETHOD(UI, GetCursor), asCALL_THISCALL);
-    engine->RegisterObjectMethod("UI", "IntVector2 get_cursorPosition()", asMETHOD(UI, GetCursorPosition), asCALL_THISCALL);
+    engine->RegisterObjectMethod("UI", "IntVector2 get_cursorPosition() const", asMETHOD(UI, GetCursorPosition), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "void set_focusElement(UIElement@+)", asMETHOD(UI, SetFocusElement), 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_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_frontElement() const", asMETHOD(UI, GetFrontElement), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "UIElement@+ get_root() const", asMETHOD(UI, GetRoot), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "UIElement@+ get_root() const", asMETHOD(UI, GetRoot), asCALL_THISCALL);
+    engine->RegisterObjectMethod("UI", "UIElement@+ get_modalRoot() const", asMETHOD(UI, GetRootModalElement), asCALL_THISCALL);
     engine->RegisterGlobalFunction("UI@+ get_ui()", asFUNCTION(GetUI), asCALL_CDECL);
     engine->RegisterGlobalFunction("UI@+ get_ui()", asFUNCTION(GetUI), asCALL_CDECL);
     engine->RegisterObjectMethod("UI", "void set_nonFocusedMouseWheel(bool)", asMETHOD(UI, SetNonFocusedMouseWheel), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "void set_nonFocusedMouseWheel(bool)", asMETHOD(UI, SetNonFocusedMouseWheel), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "bool get_nonFocusedMouseWheel() const", asMETHOD(UI, IsNonFocusedMouseWheel), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "bool get_nonFocusedMouseWheel() const", asMETHOD(UI, IsNonFocusedMouseWheel), asCALL_THISCALL);

+ 16 - 11
Engine/UI/Menu.cpp

@@ -27,6 +27,7 @@
 #include "Menu.h"
 #include "Menu.h"
 #include "UI.h"
 #include "UI.h"
 #include "UIEvents.h"
 #include "UIEvents.h"
+#include "Window.h"
 
 
 #include "DebugNew.h"
 #include "DebugNew.h"
 
 
@@ -54,7 +55,7 @@ Menu::Menu(Context* context) :
 
 
 Menu::~Menu()
 Menu::~Menu()
 {
 {
-    if (popup_)
+    if (popup_ && showPopup_)
         ShowPopup(false);
         ShowPopup(false);
 }
 }
 
 
@@ -240,6 +241,13 @@ void Menu::SetPopup(UIElement* popup)
     if (popup == this)
     if (popup == this)
         return;
         return;
 
 
+    // Currently only allow popup 'window'
+    if (popup->GetType() != Window::GetTypeStatic())
+    {
+        LOGERROR("Could not set popup element of type " + popup->GetTypeName() + ", only support popup window for now");
+        return;
+    }
+
     if (popup_ && !popup)
     if (popup_ && !popup)
         ShowPopup(false);
         ShowPopup(false);
 
 
@@ -267,18 +275,13 @@ void Menu::ShowPopup(bool enable)
 
 
     if (enable)
     if (enable)
     {
     {
-        // Find the UI root element for showing the popup
-        UIElement* root = GetRoot();
-        if (!root)
-            return;
-
         OnShowPopup();
         OnShowPopup();
 
 
-        if (popup_->GetParent() != root)
-            root->AddChild(popup_);
+        popup_->SetVar(VAR_ORIGIN, (void*)this);
+        static_cast<Window*>(popup_.Get())->SetModal(true);
+
         popup_->SetPosition(GetScreenPosition() + popupOffset_);
         popup_->SetPosition(GetScreenPosition() + popupOffset_);
         popup_->SetVisible(true);
         popup_->SetVisible(true);
-        popup_->SetVar(VAR_ORIGIN, (void*)this);
         popup_->BringToFront();
         popup_->BringToFront();
     }
     }
     else
     else
@@ -293,7 +296,9 @@ void Menu::ShowPopup(bool enable)
                 menu->ShowPopup(false);
                 menu->ShowPopup(false);
         }
         }
 
 
-        popup_->SetVar(VAR_ORIGIN, Variant::EMPTY);
+        static_cast<Window*>(popup_.Get())->SetModal(false);
+        const_cast<VariantMap&>(popup_->GetVars()).Erase(VAR_ORIGIN);
+
         popup_->SetVisible(false);
         popup_->SetVisible(false);
         popup_->Remove();
         popup_->Remove();
     }
     }
@@ -393,7 +398,7 @@ void Menu::HandleKeyDown(StringHash eventType, VariantMap& eventData)
     {
     {
         // Ignore if UI has modal element
         // Ignore if UI has modal element
         UI* ui = GetSubsystem<UI>();
         UI* ui = GetSubsystem<UI>();
-        if (ui->GetModalElement())
+        if (ui->HasModalElement())
             return;
             return;
 
 
         HandlePressedReleased(eventType, eventData);
         HandlePressedReleased(eventType, eventData);

+ 128 - 54
Engine/UI/UI.cpp

@@ -57,22 +57,27 @@ namespace Urho3D
 {
 {
 
 
 ShortStringHash VAR_ORIGIN("Origin");
 ShortStringHash VAR_ORIGIN("Origin");
+const ShortStringHash VAR_ORIGINAL_PARENT("OriginalParent");
+const ShortStringHash VAR_PARENT_CHANGED("ParentChanged");
 
 
 OBJECTTYPESTATIC(UI);
 OBJECTTYPESTATIC(UI);
 
 
 UI::UI(Context* context) :
 UI::UI(Context* context) :
     Object(context),
     Object(context),
     rootElement_(new UIElement(context)),
     rootElement_(new UIElement(context)),
+    rootModalElement_(new UIElement(context)),
     mouseButtons_(0),
     mouseButtons_(0),
     qualifiers_(0),
     qualifiers_(0),
     initialized_(false),
     initialized_(false),
     #ifdef WIN32
     #ifdef WIN32
-    nonFocusedMouseWheel_(false)    // Default MS Windows behaviour
+    nonFocusedMouseWheel_(false),    // Default MS Windows behaviour
     #else
     #else
-    nonFocusedMouseWheel_(true)     // Default Mac OS X and Linux behaviour
+    nonFocusedMouseWheel_(true),     // Default Mac OS X and Linux behaviour
     #endif
     #endif
+    nonModalBatchSize_(0)
 {
 {
     rootElement_->SetTraversalMode(TM_DEPTH_FIRST);
     rootElement_->SetTraversalMode(TM_DEPTH_FIRST);
+    rootModalElement_->SetTraversalMode(TM_DEPTH_FIRST);
 
 
     SubscribeToEvent(E_SCREENMODE, HANDLER(UI, HandleScreenMode));
     SubscribeToEvent(E_SCREENMODE, HANDLER(UI, HandleScreenMode));
     SubscribeToEvent(E_MOUSEBUTTONDOWN, HANDLER(UI, HandleMouseButtonDown));
     SubscribeToEvent(E_MOUSEBUTTONDOWN, HANDLER(UI, HandleMouseButtonDown));
@@ -131,12 +136,12 @@ void UI::SetFocusElement(UIElement* element)
             return;
             return;
 
 
         // Only allow child elements of the modal element to receive focus
         // Only allow child elements of the modal element to receive focus
-        if (modalElement_)
+        if (HasModalElement())
         {
         {
             UIElement* topLevel = element->GetParent();
             UIElement* topLevel = element->GetParent();
             while (topLevel && topLevel->GetParent() != rootElement_)
             while (topLevel && topLevel->GetParent() != rootElement_)
                 topLevel = topLevel->GetParent();
                 topLevel = topLevel->GetParent();
-            if (topLevel != modalElement_)
+            if (topLevel)   // If parented to non-modal root then ignore
                 return;
                 return;
         }
         }
 
 
@@ -173,32 +178,88 @@ void UI::SetFocusElement(UIElement* element)
 
 
 bool UI::SetModalElement(UIElement* modalElement, bool enable)
 bool UI::SetModalElement(UIElement* modalElement, bool enable)
 {
 {
-    // Only allow one modal element at a time, only the currently active modal element can disable itself
-    if (modalElement_ && modalElement != modalElement_)
-        return false;
-
-    // The modal element must be parented to root
-    if (modalElement->GetParent() != rootElement_)
+    if (!modalElement)
         return false;
         return false;
 
 
     // Currently only allow modal window
     // Currently only allow modal window
-    if (modalElement && modalElement->GetType() != Window::GetTypeStatic())
+    if (modalElement->GetType() != Window::GetTypeStatic())
         return false;
         return false;
 
 
-    modalElement_ = enable ? modalElement : 0;
-    return true;
+    assert(rootModalElement_);
+    const Vector<SharedPtr<UIElement> >& children = rootModalElement_->GetChildren();
+    if (enable)
+    {
+        // Make sure it is not already the child of the root modal element
+        for (unsigned i = 0; i < children.Size(); ++i)
+        {
+            if (children[i] == modalElement)
+                return false;
+        }
+
+        // Adopt modal root as parent
+        modalElement->SetVar(VAR_ORIGINAL_PARENT, modalElement->GetParent());
+        modalElement->SetParent(rootModalElement_);
+
+        // If it is a popup element, bring along its top-level parent
+        UIElement* originElement = static_cast<UIElement*>(modalElement->GetVar(VAR_ORIGIN).GetPtr());
+        if (originElement)
+        {
+            UIElement* element = originElement;
+            while (element && element->GetParent() != rootElement_)
+                element = element->GetParent();
+            if (element)
+            {
+                originElement->SetVar(VAR_PARENT_CHANGED, element);
+                element->SetVar(VAR_ORIGINAL_PARENT, element->GetParent());
+                element->SetParent(rootModalElement_);
+            }
+        }
+
+        return true;
+    }
+    else
+    {
+        // Only the modal element can disable itself
+        for (unsigned i = 0; i < children.Size(); ++i)
+        {
+            if (children[i] == modalElement)
+            {
+                // Revert back to original parent
+                modalElement->SetParent(static_cast<UIElement*>(modalElement->GetVar(VAR_ORIGINAL_PARENT).GetPtr()));
+                const_cast<VariantMap&>(modalElement->GetVars()).Erase(VAR_ORIGINAL_PARENT);
+
+                // If it is a popup element, revert back its top-level parent to its original parent
+                UIElement* originElement = static_cast<UIElement*>(modalElement->GetVar(VAR_ORIGIN).GetPtr());
+                if (originElement)
+                {
+                    UIElement* element = static_cast<UIElement*>(originElement->GetVar(VAR_PARENT_CHANGED).GetPtr());
+                    if (element)
+                    {
+                        const_cast<VariantMap&>(originElement->GetVars()).Erase(VAR_PARENT_CHANGED);
+                        element->SetParent(static_cast<UIElement*>(element->GetVar(VAR_ORIGINAL_PARENT).GetPtr()));
+                        const_cast<VariantMap&>(element->GetVars()).Erase(VAR_ORIGINAL_PARENT);
+                    }
+                }
+
+                return true;
+            }
+        }
+
+        return false;
+    }
 }
 }
 
 
 void UI::Clear()
 void UI::Clear()
 {
 {
     rootElement_->RemoveAllChildren();
     rootElement_->RemoveAllChildren();
+    rootModalElement_->RemoveAllChildren();
     if (cursor_)
     if (cursor_)
         rootElement_->AddChild(cursor_);
         rootElement_->AddChild(cursor_);
 }
 }
 
 
 void UI::Update(float timeStep)
 void UI::Update(float timeStep)
 {
 {
-    assert(rootElement_);
+    assert(rootElement_ && rootModalElement_);
 
 
     PROFILE(UpdateUI);
     PROFILE(UpdateUI);
 
 
@@ -258,11 +319,12 @@ void UI::Update(float timeStep)
     }
     }
 
 
     Update(timeStep, rootElement_);
     Update(timeStep, rootElement_);
+    Update(timeStep, rootModalElement_);
 }
 }
 
 
 void UI::RenderUpdate()
 void UI::RenderUpdate()
 {
 {
-    assert(rootElement_ && graphics_);
+    assert(rootElement_ && rootModalElement_ && graphics_);
 
 
     PROFILE(GetUIBatches);
     PROFILE(GetUIBatches);
 
 
@@ -275,11 +337,18 @@ void UI::RenderUpdate()
         cursor_->SetTempVisible(false);
         cursor_->SetTempVisible(false);
     }
     }
 
 
-    // Get rendering batches from the UI elements
+    // Get rendering batches from the non-modal UI elements
     batches_.Clear();
     batches_.Clear();
     vertexData_.Clear();
     vertexData_.Clear();
     const IntVector2& rootSize = rootElement_->GetSize();
     const IntVector2& rootSize = rootElement_->GetSize();
-    GetBatches(rootElement_, IntRect(0, 0, rootSize.x_, rootSize.y_));
+    IntRect currentScissor = IntRect(0, 0, rootSize.x_, rootSize.y_);
+    GetBatches(rootElement_, currentScissor);
+
+    // Save the batch size of the non-modal batches for later use
+    nonModalBatchSize_ = batches_.Size();
+
+    // Get rendering batches from the modal UI elements
+    GetBatches(rootModalElement_, currentScissor);
 
 
     // Restore UI cursor visibility state
     // Restore UI cursor visibility state
     if (osCursorVisible && cursor_)
     if (osCursorVisible && cursor_)
@@ -289,8 +358,12 @@ void UI::RenderUpdate()
 void UI::Render()
 void UI::Render()
 {
 {
     PROFILE(RenderUI);
     PROFILE(RenderUI);
-    Render(batches_, vertexData_);
-    Render(debugDrawBatches_, debugDrawVertexData_);
+    // Render non-modal batches
+    Render(batches_, vertexData_, 0, nonModalBatchSize_);
+    // Render debug draw
+    Render(debugDrawBatches_, debugDrawVertexData_, 0, debugDrawBatches_.Size());
+    // Render modal batches
+    Render(batches_, vertexData_, nonModalBatchSize_, batches_.Size());
 
 
     // Clear the debug draw batches and data
     // Clear the debug draw batches and data
     debugDrawBatches_.Clear();
     debugDrawBatches_.Clear();
@@ -388,7 +461,7 @@ void UI::SetNonFocusedMouseWheel(bool nonFocusedMouseWheel)
 UIElement* UI::GetElementAt(const IntVector2& position, bool enabledOnly)
 UIElement* UI::GetElementAt(const IntVector2& position, bool enabledOnly)
 {
 {
     UIElement* result = 0;
     UIElement* result = 0;
-    GetElementAt(result, rootElement_, position, enabledOnly);
+    GetElementAt(result, HasModalElement() ? rootModalElement_ : rootElement_, position, enabledOnly);
     return result;
     return result;
 }
 }
 
 
@@ -399,10 +472,6 @@ UIElement* UI::GetElementAt(int x, int y, bool enabledOnly)
 
 
 UIElement* UI::GetFrontElement() const
 UIElement* UI::GetFrontElement() const
 {
 {
-    // If modal element is set then return it
-    if (modalElement_)
-        return modalElement_;
-
     const Vector<SharedPtr<UIElement> >& rootChildren = rootElement_->GetChildren();
     const Vector<SharedPtr<UIElement> >& rootChildren = rootElement_->GetChildren();
     int maxPriority = M_MIN_INT;
     int maxPriority = M_MIN_INT;
     UIElement* front = 0;
     UIElement* front = 0;
@@ -424,12 +493,14 @@ UIElement* UI::GetFrontElement() const
     return front;
     return front;
 }
 }
 
 
-IntVector2 UI::GetCursorPosition()
+IntVector2 UI::GetCursorPosition() const
 {
 {
-    if (!cursor_)
-        return IntVector2::ZERO;
-    else
-        return cursor_->GetPosition();
+    return cursor_ ? cursor_->GetPosition() : IntVector2::ZERO;
+}
+
+bool UI::HasModalElement() const
+{
+    return rootModalElement_->GetNumChildren() > 0;
 }
 }
 
 
 void UI::Initialize()
 void UI::Initialize()
@@ -445,6 +516,7 @@ void UI::Initialize()
     graphics_ = graphics;
     graphics_ = graphics;
 
 
     rootElement_->SetSize(graphics->GetWidth(), graphics->GetHeight());
     rootElement_->SetSize(graphics->GetWidth(), graphics->GetHeight());
+    rootModalElement_->SetSize(rootElement_->GetSize());
 
 
     noTextureVS_ = renderer->GetVertexShader("Basic_VCol");
     noTextureVS_ = renderer->GetVertexShader("Basic_VCol");
     diffTextureVS_ = renderer->GetVertexShader("Basic_DiffVCol");
     diffTextureVS_ = renderer->GetVertexShader("Basic_DiffVCol");
@@ -472,7 +544,7 @@ void UI::Update(float timeStep, UIElement* element)
         Update(timeStep, *i);
         Update(timeStep, *i);
 }
 }
 
 
-void UI::Render(const PODVector<UIBatch>& batches, const PODVector<float>& vertexData)
+void UI::Render(const PODVector<UIBatch>& batches, const PODVector<float>& vertexData, unsigned batchStart, unsigned batchSize)
 {
 {
     // Engine does not render when window is closed or device is lost
     // Engine does not render when window is closed or device is lost
     assert(graphics_ && graphics_->IsInitialized() && !graphics_->IsDeviceLost());
     assert(graphics_ && graphics_->IsInitialized() && !graphics_->IsDeviceLost());
@@ -513,7 +585,7 @@ void UI::Render(const PODVector<UIBatch>& batches, const PODVector<float>& verte
 
 
     unsigned alphaFormat = Graphics::GetAlphaFormat();
     unsigned alphaFormat = Graphics::GetAlphaFormat();
 
 
-    for (unsigned i = 0; i < batches.Size(); ++i)
+    for (unsigned i = batchStart; i < batchSize; ++i)
     {
     {
         const UIBatch& batch = batches[i];
         const UIBatch& batch = batches[i];
         if (batch.vertexStart_ == batch.vertexEnd_)
         if (batch.vertexStart_ == batch.vertexEnd_)
@@ -616,28 +688,6 @@ void UI::GetElementAt(UIElement*& result, UIElement* current, const IntVector2&
     for (unsigned i = 0; i < children.Size(); ++i)
     for (unsigned i = 0; i < children.Size(); ++i)
     {
     {
         UIElement* element = children[i];
         UIElement* element = children[i];
-        // If has a modal element, skip other elements from the root level
-        // Exception: if the element has the "origin" element in its variables it is a popup and should not be skipped
-        // if the origin element belongs to the modal element's hierarchy
-        if (current == rootElement_ && modalElement_ && element != modalElement_)
-        {
-            bool shouldSkip = true;
-            UIElement* originElement = static_cast<UIElement*>(element->GetVar(VAR_ORIGIN).GetPtr());
-
-            while (originElement)
-            {
-                if (originElement == modalElement_)
-                {
-                    shouldSkip = false;
-                    break;
-                }
-                originElement = originElement->GetParent();
-            }
-
-            if (shouldSkip)
-                continue;
-        }
-
         bool hasChildren = element->GetNumChildren() > 0;
         bool hasChildren = element->GetNumChildren() > 0;
 
 
         if (element != cursor_.Get() && element->IsVisible())
         if (element != cursor_.Get() && element->IsVisible())
@@ -713,7 +763,10 @@ void UI::HandleScreenMode(StringHash eventType, VariantMap& eventData)
     if (!initialized_)
     if (!initialized_)
         Initialize();
         Initialize();
     else
     else
+    {
         rootElement_->SetSize(eventData[P_WIDTH].GetInt(), eventData[P_HEIGHT].GetInt());
         rootElement_->SetSize(eventData[P_WIDTH].GetInt(), eventData[P_HEIGHT].GetInt());
+        rootModalElement_->SetSize(rootElement_->GetSize());
+    }
 }
 }
 
 
 void UI::HandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
 void UI::HandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
@@ -1015,6 +1068,27 @@ void UI::HandleKeyDown(StringHash eventType, VariantMap& eventData)
     qualifiers_ = eventData[P_QUALIFIERS].GetInt();
     qualifiers_ = eventData[P_QUALIFIERS].GetInt();
     int key = eventData[P_KEY].GetInt();
     int key = eventData[P_KEY].GetInt();
 
 
+    // Dismiss modal element if any when ESC key is pressed
+    if (key == KEY_ESC && HasModalElement())
+    {
+        UIElement* element = rootModalElement_->GetChild(rootModalElement_->GetNumChildren() - 1);
+        if (element->GetVars().Contains(VAR_ORIGIN))
+            // If it is a popup, dismiss by defocusing it
+            SetFocusElement(0);
+        else
+        {
+            // If it is a modal window, by resetting its modal flag and auto-remove it
+            Window* window = dynamic_cast<Window*>(element);
+            if (window)
+            {
+                window->SetModal(false);
+                window->Remove();
+            }
+        }
+
+        return;
+    }
+
     UIElement* element = focusElement_;
     UIElement* element = focusElement_;
     if (element)
     if (element)
     {
     {
@@ -1022,7 +1096,7 @@ void UI::HandleKeyDown(StringHash eventType, VariantMap& eventData)
         if (key == KEY_TAB)
         if (key == KEY_TAB)
         {
         {
             UIElement* topLevel = element->GetParent();
             UIElement* topLevel = element->GetParent();
-            while (topLevel && topLevel->GetParent() != rootElement_)
+            while (topLevel && topLevel->GetParent() != rootElement_ && topLevel->GetParent() != rootModalElement_)
                 topLevel = topLevel->GetParent();
                 topLevel = topLevel->GetParent();
             if (topLevel)
             if (topLevel)
             {
             {

+ 12 - 8
Engine/UI/UI.h

@@ -52,7 +52,7 @@ public:
     void SetCursor(Cursor* cursor);
     void SetCursor(Cursor* cursor);
     /// Set focused UI element.
     /// Set focused UI element.
     void SetFocusElement(UIElement* 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. Only the current modal element can clear its modal status.
+    /// Set modal element. Until all the modal elements are dismissed, all the inputs and events are only sent to them. Return true when successful. Only the modal element can clear its modal status or when it is being destructed.
     bool SetModalElement(UIElement* modalElement, bool enable);
     bool SetModalElement(UIElement* modalElement, bool enable);
     /// Clear the UI (excluding the cursor.)
     /// Clear the UI (excluding the cursor.)
     void Clear();
     void Clear();
@@ -77,6 +77,8 @@ public:
 
 
     /// Return root UI element.
     /// Return root UI element.
     UIElement* GetRoot() const { return rootElement_; }
     UIElement* GetRoot() const { return rootElement_; }
+    /// Return root modal element.
+    UIElement* GetRootModalElement() const { return rootModalElement_; }
     /// Return cursor.
     /// Return cursor.
     Cursor* GetCursor() const { return cursor_; }
     Cursor* GetCursor() const { return cursor_; }
     /// Return UI element at screen coordinates.
     /// Return UI element at screen coordinates.
@@ -85,16 +87,16 @@ public:
     UIElement* GetElementAt(int x, int y, bool enabledOnly = true);
     UIElement* GetElementAt(int x, int y, bool enabledOnly = true);
     /// Return focused element.
     /// Return focused element.
     UIElement* GetFocusElement() const { return focusElement_; }
     UIElement* GetFocusElement() const { return focusElement_; }
-    /// Return modal element.
-    UIElement* GetModalElement() const { return modalElement_; }
-    /// Return topmost enabled root-level element.
+    /// Return topmost enabled root-level non-modal element.
     UIElement* GetFrontElement() const;
     UIElement* GetFrontElement() const;
     /// Return cursor position.
     /// Return cursor position.
-    IntVector2 GetCursorPosition();
+    IntVector2 GetCursorPosition() const;
     /// Return clipboard text.
     /// Return clipboard text.
     const String& GetClipBoardText() const { return clipBoard_; }
     const String& GetClipBoardText() const { return clipBoard_; }
     /// Return mouse wheel handling flag.
     /// Return mouse wheel handling flag.
     bool IsNonFocusedMouseWheel() const { return nonFocusedMouseWheel_; }
     bool IsNonFocusedMouseWheel() const { return nonFocusedMouseWheel_; }
+    /// Return true when UI has modal element(s).
+    bool HasModalElement() const;
 
 
 private:
 private:
     /// Initialize when screen mode initially se.
     /// Initialize when screen mode initially se.
@@ -102,7 +104,7 @@ private:
     /// Update UI element logic recursively.
     /// Update UI element logic recursively.
     void Update(float timeStep, UIElement* element);
     void Update(float timeStep, UIElement* element);
     /// Render the batches.
     /// Render the batches.
-    void Render(const PODVector<UIBatch>& batches, const PODVector<float>& vertexData);
+    void Render(const PODVector<UIBatch>& batches, const PODVector<float>& vertexData, unsigned batchStart, unsigned batchSize);
     /// Generate batches from an UI element recursively.
     /// Generate batches from an UI element recursively.
     void GetBatches(UIElement* element, IntRect currentScissor);
     void GetBatches(UIElement* element, IntRect currentScissor);
     /// Return UI element at screen position recursively.
     /// Return UI element at screen position recursively.
@@ -150,14 +152,14 @@ private:
     SharedPtr<ShaderVariation> alphaTexturePS_;
     SharedPtr<ShaderVariation> alphaTexturePS_;
     /// UI root element.
     /// UI root element.
     SharedPtr<UIElement> rootElement_;
     SharedPtr<UIElement> rootElement_;
+    /// UI root modal element.
+    SharedPtr<UIElement> rootModalElement_;
     /// Cursor.
     /// Cursor.
     SharedPtr<Cursor> cursor_;
     SharedPtr<Cursor> cursor_;
     /// UI element being dragged.
     /// UI element being dragged.
     WeakPtr<UIElement> dragElement_;
     WeakPtr<UIElement> dragElement_;
     /// Currently focused element
     /// Currently focused element
     WeakPtr<UIElement> focusElement_;
     WeakPtr<UIElement> focusElement_;
-    /// Modal element.
-    WeakPtr<UIElement> modalElement_;
     /// UI rendering batches.
     /// UI rendering batches.
     PODVector<UIBatch> batches_;
     PODVector<UIBatch> batches_;
     /// UI rendering vertex data.
     /// UI rendering vertex data.
@@ -180,6 +182,8 @@ private:
     bool initialized_;
     bool initialized_;
     /// Flag to switch mouse wheel event to be sent to non-focused element at cursor.
     /// Flag to switch mouse wheel event to be sent to non-focused element at cursor.
     bool nonFocusedMouseWheel_;
     bool nonFocusedMouseWheel_;
+    /// Non-modal batch size (used internally for rendering).
+    unsigned nonModalBatchSize_;
 };
 };
 
 
 /// Register UI library objects.
 /// Register UI library objects.