Browse Source

Refactored setting UI element style.
All UI elements derived from BorderImage can now define different graphics to be used on hover.
UI layout loading support.

Lasse Öörni 15 years ago
parent
commit
bf9f51ba4d

BIN
Bin/Data/Textures/UI.png


+ 7 - 9
Bin/Data/UI/UI.xml → Bin/Data/UI/DefaultStyle.xml

@@ -1,21 +1,20 @@
 <elements>
-    <element name="Cursor">
+    <element type="Cursor">
         <size value="16 24" />
         <texture name="Textures/UI.png" />
         <imagerect value="0 0 16 24" />
         <hotspot value="0 0" />
     </element>
-    <element name="Button">
+    <element type="Button">
         <size value="16 16" />
         <texture name="Textures/UI.png" />
-        <color value="0.85 0.85 0.85" />
-        <hovercolor value="0.15 0.15 0.15" />
         <inactiverect value="16 0 32 16" />
         <pressedrect value="32 0 48 16" />
         <border value="4 4 4 4" />
+        <hoveroffset value="0 16" />
         <labeloffset value="-1 1" />
     </element>
-    <element name="Window">
+    <element type="Window">
         <texture name="Textures/UI.png" />
         <imagerect value="48 0 64 16" />
         <border value="3 3 3 3" />
@@ -23,16 +22,15 @@
         <movable enable="true" />
         <resizable enable="true" />
     </element>
-    <element name="Text">
+    <element type="Text">
         <font name="Cour.ttf" size="10" />
     </element>
-    <element name="CheckBox">
+    <element type="CheckBox">
         <size value="16 16" />
         <texture name="Textures/UI.png" />
-        <color value="0.85 0.85 0.85" />
-        <hovercolor value="0.15 0.15 0.15" />
         <uncheckedrect value="64 0 80 16" />
         <checkedrect value="80 0 96 16" />
         <border value="4 4 4 4" />
+        <hoveroffset value="0 16" />
     </element>
 </elements>

+ 11 - 0
Bin/Data/UI/TestLayout.xml

@@ -0,0 +1,11 @@
+<layout>
+    <element type="Button">
+        <size value="100 40" />
+        <alignment horizontal="center" vertical="center" />
+        <element type="Text">
+            <text value="TEST" />
+            <font name="cour.ttf" size="12" />
+            <alignment horizontal="center" vertical="center" />
+        </element>
+    </element>
+</layout>

+ 35 - 3
Engine/Engine/RegisterTemplates.h

@@ -27,6 +27,7 @@
 #include "BorderImage.h"
 #include "Channel.h"
 #include "Deserializer.h"
+#include "Engine.h"
 #include "GeometryNode.h"
 #include "RegisterArray.h"
 #include "Resource.h"
@@ -331,17 +332,43 @@ template <class T> T* ConstructUIElementWithName(const std::string& name)
     return new T(name);
 }
 
+//! Template function for setting UI element style from an XML element
+template <class T> void UIElementSetStyle(const XMLElement& element, T* ptr)
+{
+    try
+    {
+        ptr->setStyle(element, getEngine()->getResourceCache());
+    }
+    catch (Exception& e)
+    {
+        SAFE_RETHROW(e);
+    }
+}
+
+//! Template function for setting UI element style from an XML file
+template <class T> void UIElementSetStyleAuto(XMLFile* file, T* ptr)
+{
+    try
+    {
+        ptr->setStyleAuto(file, getEngine()->getResourceCache());
+    }
+    catch (Exception& e)
+    {
+        SAFE_RETHROW(e);
+    }
+}
+
 //! Template function for registering a class derived from UIElement
 template <class T> void registerUIElement(asIScriptEngine* engine, const char* className)
 {
     static const std::string declFactory(std::string(className) + "@+ f()");
     static const std::string declFactoryWithName(std::string(className) + "@+ f(const string& in)");
     
-    engine->RegisterObjectType(className, 0, asOBJ_REF);
+    registerHashedType<T>(engine, className);
     engine->RegisterObjectBehaviour(className, asBEHAVE_FACTORY, declFactory.c_str(), asFUNCTION(ConstructUIElement<T>), asCALL_CDECL);
     engine->RegisterObjectBehaviour(className, asBEHAVE_FACTORY, declFactoryWithName.c_str(), asFUNCTION(ConstructUIElementWithName<T>), asCALL_CDECL);
-    engine->RegisterObjectBehaviour(className, asBEHAVE_ADDREF, "void f()", asMETHODPR(T, addRef, (), void), asCALL_THISCALL);
-    engine->RegisterObjectBehaviour(className, asBEHAVE_RELEASE, "void f()", asMETHODPR(T, releaseRef, (), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "void setStyle(const XMLElement& in)", asFUNCTION(UIElementSetStyle<T>), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod(className, "void setStyleAuto(XMLFile@+)", asFUNCTION(UIElementSetStyleAuto<T>), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod(className, "void setName(const string& in)", asMETHOD(T, setName), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setPosition(const IntVector2& in)", asMETHODPR(T, setPosition, (const IntVector2&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setPosition(int, int)", asMETHODPR(T, setPosition, (int, int), void), asCALL_THISCALL);
@@ -371,6 +398,7 @@ template <class T> void registerUIElement(asIScriptEngine* engine, const char* c
     engine->RegisterObjectMethod(className, "const IntVector2& getSize() const", asMETHOD(T, getSize), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "int getWidth() const", asMETHOD(T, getWidth), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "int getHeight() const", asMETHOD(T, getHeight), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "const IntVector2& getChildOffset() const", asMETHOD(T, getChildOffset), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "HorizontalAlignment getHorizontalAlignment() const", asMETHOD(T, getHorizontalAlignment), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "VerticalAlignment getVerticalAlignment() const", asMETHOD(T, getVerticalAlignment), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "const Color& getColor(UIElementCorner) const", asMETHOD(T, getColor), asCALL_THISCALL);
@@ -389,6 +417,7 @@ template <class T> void registerUIElement(asIScriptEngine* engine, const char* c
     engine->RegisterObjectMethod(className, "UIElement@+ getChild(uint) const", asMETHODPR(T, getChild, (unsigned) const, UIElement*), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "UIElement@+ getChild(const string& in, bool) const", asMETHODPR(T, getChild, (const std::string&, bool) const, UIElement*), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "UIElement@+ getParent() const", asMETHOD(T, getParent), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "XMLElement getStyleElement(XMLFile@+) const", asMETHODPR(T, getStyleElement, (XMLFile*) const, XMLElement), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "IntVector2 screenToElement(const IntVector2& in)", asMETHOD(T, screenToElement), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "IntVector2 elementToScreen(const IntVector2& in)", asMETHOD(T, elementToScreen), asCALL_THISCALL);
 }
@@ -403,9 +432,12 @@ template <class T> void registerBorderImage(asIScriptEngine* engine, const char*
     engine->RegisterObjectMethod(className, "void setFullImageRect()", asMETHOD(T, setFullImageRect), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setBorder(const IntRect& in)", asMETHODPR(T, setBorder, (const IntRect&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setBorder(int, int, int, int)", asMETHODPR(T, setBorder, (int, int, int, int), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "void setHoverOffset(const IntVector2& in)", asMETHODPR(T, setHoverOffset, (const IntVector2&), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "void setHoverOffset(int, int)", asMETHODPR(T, setHoverOffset, (int, int), void), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "Texture@+ getTexture() const", asMETHOD(T, setTexture), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "const IntRect& getImageRect() const", asMETHOD(T, getImageRect), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "const IntRect& getBorder() const", asMETHOD(T, getBorder), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "const IntVector2& getHoverOffset() const", asMETHOD(T, getHoverOffset), asCALL_THISCALL);
 }
 
 #endif // ENGINE_REGISTERTEMPLATES_H

+ 53 - 21
Engine/Engine/RegisterUI.cpp

@@ -38,18 +38,6 @@ static void registerFont(asIScriptEngine* engine)
     registerRefCasts<Resource, Font>(engine, "Resource", "Font");
 }
 
-void UIElementLoadParameters(XMLFile* file, const std::string& elementName, UIElement* ptr)
-{
-    try
-    {
-        ptr->loadParameters(file, elementName, getEngine()->getResourceCache());
-    }
-    catch (Exception& e)
-    {
-        SAFE_RETHROW(e);
-    }
-}
-
 static void registerUIElement(asIScriptEngine* engine)
 {
     engine->RegisterEnum("HorizontalAlignment");
@@ -69,7 +57,9 @@ static void registerUIElement(asIScriptEngine* engine)
     engine->RegisterEnumValue("UIElementCorner", "C_BOTTOMRIGHT", C_BOTTOMRIGHT);
     
     registerUIElement<UIElement>(engine, "UIElement");
-    engine->RegisterObjectMethod("UIElement", "void loadParameters(XMLFile@+, const string& in)", asFUNCTION(UIElementLoadParameters), asCALL_CDECL_OBJLAST);
+    
+    // Register static function for getting UI style XML element
+    engine->RegisterGlobalFunction("XMLElement getStyleElement(XMLFile@+, const string& in)", asFUNCTIONPR(UIElement::getStyleElement, (XMLFile*, const std::string&), XMLElement), asCALL_CDECL);
     
     // Register Variant getPtr() for UIElement
     engine->RegisterObjectMethod("Variant", "UIElement@+ getUIElement() const", asFUNCTION(getVariantPtr<UIElement>), asCALL_CDECL_OBJLAST);
@@ -78,7 +68,6 @@ static void registerUIElement(asIScriptEngine* engine)
 static void registerText(asIScriptEngine* engine)
 {
     registerUIElement<Text>(engine, "Text");
-    engine->RegisterObjectMethod("Text", "void loadParameters(XMLFile@+, const string& in)", asFUNCTION(UIElementLoadParameters), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Text", "bool setFont(Font@+, int)", asMETHOD(Text, setFont), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text", "void setMaxWidth(int)", asMETHOD(Text, setMaxWidth), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text", "void setText(const string& in)", asMETHOD(Text, setText), asCALL_THISCALL);
@@ -98,14 +87,12 @@ static void registerText(asIScriptEngine* engine)
 static void registerBorderImage(asIScriptEngine* engine)
 {
     registerBorderImage<BorderImage>(engine, "BorderImage");
-    engine->RegisterObjectMethod("BorderImage", "void loadParameters(XMLFile@+, const string& in)", asFUNCTION(UIElementLoadParameters), asCALL_CDECL_OBJLAST);
     registerRefCasts<UIElement, BorderImage>(engine, "UIElement", "BorderImage");
 }
 
 static void registerCursor(asIScriptEngine* engine)
 {
     registerBorderImage<Cursor>(engine, "Cursor");
-    engine->RegisterObjectMethod("Cursor", "void loadParameters(XMLFile@+, const string& in)", asFUNCTION(UIElementLoadParameters), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Cursor", "void setHotspot(const IntVector2& in)", asMETHODPR(Cursor, setHotspot, (const IntVector2&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("Cursor", "void setHotspot(int, int)", asMETHODPR(Cursor, setHotspot, (int, int), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("Cursor", "const IntVector2& getHotspot() const", asMETHOD(Cursor, getHotspot), asCALL_THISCALL);
@@ -115,17 +102,14 @@ static void registerCursor(asIScriptEngine* engine)
 static void registerButton(asIScriptEngine* engine)
 {
     registerBorderImage<Button>(engine, "Button");
-    engine->RegisterObjectMethod("Button", "void loadParameters(XMLFile@+, const string& in)", asFUNCTION(UIElementLoadParameters), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Button", "void setInactiveRect(const IntRect& in)", asMETHODPR(Button, setInactiveRect, (const IntRect&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("Button", "void setInactiveRect(int, int, int, int)", asMETHODPR(Button, setInactiveRect, (int, int, int, int), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("Button", "void setPressedRect(const IntRect& in)", asMETHODPR(Button, setPressedRect, (const IntRect&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("Button", "void setPressedRect(int, int, int, int)", asMETHODPR(Button, setPressedRect, (int, int, int, int), void), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Button", "void setLabel(UIElement@+)", asMETHOD(Button, setLabel), asCALL_THISCALL);
     engine->RegisterObjectMethod("Button", "void setLabelOffset(const IntVector2& in)", asMETHODPR(Button, setLabelOffset, (const IntVector2&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("Button", "void setLabelOffset(int, int)", asMETHODPR(Button, setLabelOffset, (int, int), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("Button", "const IntRect& getInactiveRect() const", asMETHOD(Button, getInactiveRect), asCALL_THISCALL);
     engine->RegisterObjectMethod("Button", "const IntRect& getPressedRect() const", asMETHOD(Button, getPressedRect), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Button", "UIElement@+ getLabel() const", asMETHOD(Button, getLabel), asCALL_THISCALL);
     engine->RegisterObjectMethod("Button", "const IntVector2& getLabelOffset() const", asMETHOD(Button, getLabelOffset), asCALL_THISCALL);
     registerRefCasts<UIElement, Button>(engine, "UIElement", "Button");
 }
@@ -133,7 +117,6 @@ static void registerButton(asIScriptEngine* engine)
 static void registerCheckBox(asIScriptEngine* engine)
 {
     registerBorderImage<CheckBox>(engine, "CheckBox");
-    engine->RegisterObjectMethod("CheckBox", "void loadParameters(XMLFile@+, const string& in)", asFUNCTION(UIElementLoadParameters), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("CheckBox", "void setChecked(bool)", asMETHOD(CheckBox, setChecked), asCALL_THISCALL);
     engine->RegisterObjectMethod("CheckBox", "void setUncheckedRect(const IntRect& in)", asMETHODPR(CheckBox, setUncheckedRect, (const IntRect&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("CheckBox", "void setUncheckedRect(int, int, int, int)", asMETHODPR(CheckBox, setUncheckedRect, (int, int, int, int), void), asCALL_THISCALL);
@@ -148,7 +131,6 @@ static void registerCheckBox(asIScriptEngine* engine)
 static void registerWindow(asIScriptEngine* engine)
 {
     registerBorderImage<Window>(engine, "Window");
-    engine->RegisterObjectMethod("Window", "void loadParameters(XMLFile@+, const string& in)", asFUNCTION(UIElementLoadParameters), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Window", "void setMovable(bool)", asMETHOD(Window, setMovable), asCALL_THISCALL);
     engine->RegisterObjectMethod("Window", "void setResizable(bool)", asMETHOD(Window, setResizable), asCALL_THISCALL);
     engine->RegisterObjectMethod("Window", "void setMinSize(const IntVector2& in)", asMETHODPR(Window, setMinSize, (const IntVector2&), void), asCALL_THISCALL);
@@ -180,6 +162,53 @@ static Cursor* GetUICursor()
     return getEngine()->getUICursor();
 }
 
+static UIElement* UICreateElement(const std::string& type, const std::string& name, UI* ptr)
+{
+    try
+    {
+        SharedPtr<UIElement> element = ptr->createElement(ShortStringHash(type), name);
+        // The shared pointer will go out of scope, so have to increment the reference count
+        // (here an auto handle can not be used)
+        if (element)
+            element->addRef();
+        return element.getPtr();
+    }
+    catch (Exception& e)
+    {
+        SAFE_RETHROW_RET(e, 0);
+    }
+}
+
+static UIElement* UILoadLayout(XMLFile* file, UI* ptr)
+{
+    try
+    {
+        SharedPtr<UIElement> root = ptr->loadLayout(file);
+        if (root)
+            root->addRef();
+        return root.getPtr();
+    }
+    catch (Exception& e)
+    {
+        SAFE_RETHROW_RET(e, 0);
+    }
+}
+
+static UIElement* UILoadLayoutWithStyle(XMLFile* file, XMLFile* styleFile, UI* ptr)
+{
+    try
+    {
+        SharedPtr<UIElement> root = ptr->loadLayout(file, styleFile);
+        if (root)
+            root->addRef();
+        return root.getPtr();
+    }
+    catch (Exception& e)
+    {
+        SAFE_RETHROW_RET(e, 0);
+    }
+}
+
 static void registerUI(asIScriptEngine* engine)
 {
     engine->RegisterObjectType("UI", 0, asOBJ_REF);
@@ -188,6 +217,9 @@ static void registerUI(asIScriptEngine* engine)
     engine->RegisterObjectMethod("UI", "void setCursor(Cursor@+)", asMETHOD(UI, setCursor), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "void setFocusElement(UIElement@+)", asMETHOD(UI, setFocusElement), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "void bringToFront(UIElement@+)", asMETHOD(UI, bringToFront), asCALL_THISCALL);
+    engine->RegisterObjectMethod("UI", "UIElement@ createElement(const string& in, const string& in)", asFUNCTION(UICreateElement), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("UI", "UIElement@ loadLayout(XMLFile@+)", asFUNCTION(UILoadLayout), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("UI", "UIElement@ loadLayout(XMLFile@+, XMLFile@+)", asFUNCTION(UILoadLayoutWithStyle), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("UI", "UIElement@+ getRootElement() const", asMETHOD(UI, getRootElement), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "Cursor@+ getCursor() const", asMETHOD(UI, getCursor), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "UIElement@+ getElementAt(const IntVector2& in, bool)", asMETHODPR(UI, getElementAt, (const IntVector2&, bool), UIElement*), asCALL_THISCALL);

+ 3 - 3
Engine/Renderer/Renderer.cpp

@@ -1476,7 +1476,7 @@ void Renderer::setScissorTest(bool enable, const Rect& rect, bool borderInclusiv
         }
     }
     else
-        mScissorRect = IntRect(0, 0, 0, 0);
+        mScissorRect = IntRect::sZero;
     
     if (enable != mScissorTest)
     {
@@ -1528,7 +1528,7 @@ void Renderer::setScissorTest(bool enable, const IntRect& rect)
         }
     }
     else
-        mScissorRect = IntRect(0, 0, 0, 0);
+        mScissorRect = IntRect::sZero;
     
     if (enable != mScissorTest)
     {
@@ -2182,7 +2182,7 @@ void Renderer::resetCachedState()
     mDepthWrite = true;
     mFillMode = FILL_SOLID;
     mScissorTest = false;
-    mScissorRect = IntRect(0, 0, 0, 0);
+    mScissorRect = IntRect::sZero;
     mStencilTest = false;
     mStencilTestMode = CMP_ALWAYS;
     mStencilPass = OP_KEEP;

+ 50 - 0
Engine/UI/BaseUIElementFactory.cpp

@@ -0,0 +1,50 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2011 Lasse Öörni
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "Precompiled.h"
+#include "BaseUIElementFactory.h"
+#include "Button.h"
+#include "CheckBox.h"
+#include "Cursor.h"
+#include "Text.h"
+#include "Window.h"
+
+UIElement* BaseUIElementFactory::createElement(ShortStringHash type, const std::string& name)
+{
+    if (type == BorderImage::getTypeStatic())
+        return new BorderImage(name);
+    if (type == Button::getTypeStatic())
+        return new Button(name);
+    if (type == CheckBox::getTypeStatic())
+        return new CheckBox(name);
+    if (type == Cursor::getTypeStatic())
+        return new Cursor(name);
+    if (type == Text::getTypeStatic())
+        return new Text(std::string(), name);
+    if (type == UIElement::getTypeStatic())
+        return new UIElement(name);
+    if (type == Window::getTypeStatic())
+        return new Window(name);
+    
+    return 0;
+}

+ 37 - 0
Engine/UI/BaseUIElementFactory.h

@@ -0,0 +1,37 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2011 Lasse Öörni
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#ifndef UI_BASEUIELEMENTFACTORY_H
+#define UI_BASEUIELEMENTFACTORY_H
+
+#include "UIElementFactory.h"
+
+//! Creates the inbuilt UI elements
+class BaseUIElementFactory : public UIElementFactory
+{
+public:
+    //! Create a UI element of the specified type. Return null if can not create
+    virtual UIElement* createElement(ShortStringHash type, const std::string& name = std::string());
+};
+
+#endif // UI_BASEUIELEMENTFACTORY_H

+ 43 - 22
Engine/UI/BorderImage.cpp

@@ -30,8 +30,9 @@
 
 BorderImage::BorderImage(const std::string& name) :
     UIElement(name),
-    mImageRect(0, 0, 0, 0),
-    mBorder(0, 0, 0, 0)
+    mImageRect(IntRect::sZero),
+    mBorder(IntRect::sZero),
+    mHoverOffset(IntVector2::sZero)
 {
 }
 
@@ -39,18 +40,21 @@ BorderImage::~BorderImage()
 {
 }
 
-XMLElement BorderImage::loadParameters(XMLFile* file, const std::string& elementName, ResourceCache* cache)
+void BorderImage::setStyle(const XMLElement& element, ResourceCache* cache)
 {
-    XMLElement paramElem = UIElement::loadParameters(file, elementName, cache);
+    if (!cache)
+        SAFE_EXCEPTION("Null resource cache for UI element");
     
-    if (paramElem.hasChildElement("texture"))
-        setTexture(cache->getResource<Texture2D>(paramElem.getChildElement("texture").getString("name")));
-    if (paramElem.hasChildElement("imagerect"))
-        setImageRect(paramElem.getChildElement("imagerect").getIntRect("value"));
-    if (paramElem.hasChildElement("border"))
-        setBorder(paramElem.getChildElement("border").getIntRect("value"));
+    UIElement::setStyle(element, cache);
     
-    return paramElem;
+    if (element.hasChildElement("texture"))
+        setTexture(cache->getResource<Texture2D>(element.getChildElement("texture").getString("name")));
+    if (element.hasChildElement("imagerect"))
+        setImageRect(element.getChildElement("imagerect").getIntRect("value"));
+    if (element.hasChildElement("border"))
+        setBorder(element.getChildElement("border").getIntRect("value"));
+    if (element.hasChildElement("hoveroffset"))
+        setHoverOffset(element.getChildElement("hoveroffset").getIntVector2("value"));
 }
 
 void BorderImage::getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads, const IntRect& currentScissor)
@@ -69,31 +73,35 @@ void BorderImage::getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>&
     IntVector2 innerTextureSize(
         max(mImageRect.mRight - mImageRect.mLeft - mBorder.mLeft - mBorder.mRight, 0),
         max(mImageRect.mBottom - mImageRect.mTop - mBorder.mTop - mBorder.mBottom, 0));
-
+    
+    IntVector2 topLeft(mImageRect.mLeft, mImageRect.mTop);
+    if (mHovering)
+        topLeft += mHoverOffset;
+    
     // Top
     if (mBorder.mTop)
     {
         if (mBorder.mLeft)
-            batch.addQuad(*this, 0, 0, mBorder.mLeft, mBorder.mTop, mImageRect.mLeft, mImageRect.mTop);
+            batch.addQuad(*this, 0, 0, mBorder.mLeft, mBorder.mTop, topLeft.mX, topLeft.mY);
         if (innerSize.mX)
             batch.addQuad(*this, mBorder.mLeft, 0, innerSize.mX, mBorder.mTop,
-            mImageRect.mLeft + mBorder.mLeft, mImageRect.mTop, innerTextureSize.mX, mBorder.mTop);
+            topLeft.mX + mBorder.mLeft, topLeft.mY, innerTextureSize.mX, mBorder.mTop);
         if (mBorder.mRight)
             batch.addQuad(*this, mBorder.mLeft + innerSize.mX, 0, mBorder.mRight, mBorder.mTop,
-            mImageRect.mLeft + mBorder.mLeft + innerTextureSize.mX, mImageRect.mTop);
+            topLeft.mX + mBorder.mLeft + innerTextureSize.mX, topLeft.mY);
     }
     // Middle
     if (innerSize.mY)
     {
         if (mBorder.mLeft)
             batch.addQuad(*this, 0, mBorder.mTop, mBorder.mLeft, innerSize.mY,
-            mImageRect.mLeft, mImageRect.mTop + mBorder.mTop, mBorder.mLeft, innerTextureSize.mY);
+            topLeft.mX, topLeft.mY + mBorder.mTop, mBorder.mLeft, innerTextureSize.mY);
         if (innerSize.mX)
             batch.addQuad(*this, mBorder.mLeft, mBorder.mTop, innerSize.mX, innerSize.mY,
-            mImageRect.mLeft + mBorder.mLeft, mImageRect.mTop + mBorder.mTop, innerTextureSize.mX, innerTextureSize.mY);
+            topLeft.mX + mBorder.mLeft, topLeft.mY + mBorder.mTop, innerTextureSize.mX, innerTextureSize.mY);
         if (mBorder.mRight)
             batch.addQuad(*this, mBorder.mLeft + innerSize.mX, mBorder.mTop, mBorder.mRight,
-            innerSize.mY, mImageRect.mLeft + mBorder.mLeft + innerTextureSize.mX, mImageRect.mTop + mBorder.mTop,
+            innerSize.mY, topLeft.mX + mBorder.mLeft + innerTextureSize.mX, topLeft.mY + mBorder.mTop,
             mBorder.mRight, innerTextureSize.mY);
     }
     // Bottom
@@ -101,18 +109,21 @@ void BorderImage::getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>&
     {
         if (mBorder.mLeft)
             batch.addQuad(*this, 0, mBorder.mTop + innerSize.mY, mBorder.mLeft, mBorder.mBottom,
-            mImageRect.mLeft, mImageRect.mTop + mBorder.mTop + innerTextureSize.mY);
+            topLeft.mX, topLeft.mY + mBorder.mTop + innerTextureSize.mY);
         if (innerSize.mX)
             batch.addQuad(*this, mBorder.mLeft, mBorder.mTop + innerSize.mY, innerSize.mX,
-            mBorder.mBottom, mImageRect.mLeft + mBorder.mLeft, mImageRect.mTop + mBorder.mTop + innerTextureSize.mY,
+            mBorder.mBottom, topLeft.mX + mBorder.mLeft, topLeft.mY + mBorder.mTop + innerTextureSize.mY,
             innerTextureSize.mX, mBorder.mBottom);
         if (mBorder.mRight)
             batch.addQuad(*this, mBorder.mLeft + innerSize.mX, mBorder.mTop + innerSize.mY,
-            mBorder.mRight, mBorder.mBottom, mImageRect.mLeft + mBorder.mLeft + innerTextureSize.mX, 
-            mImageRect.mTop + mBorder.mTop + innerTextureSize.mY);
+            mBorder.mRight, mBorder.mBottom, topLeft.mX + mBorder.mLeft + innerTextureSize.mX, 
+            topLeft.mY + mBorder.mTop + innerTextureSize.mY);
     }
     
     UIBatch::addOrMerge(batch, batches);
+    
+    // Reset hovering for next frame
+    mHovering = false;
 }
 
 void BorderImage::setTexture(Texture* texture)
@@ -161,3 +172,13 @@ void BorderImage::setBorder(int left, int top, int right, int bottom)
     mBorder.mRight = max(right, 0);
     mBorder.mBottom = max(bottom, 0);
 }
+
+void BorderImage::setHoverOffset(const IntVector2& offset)
+{
+    mHoverOffset = offset;
+}
+
+void BorderImage::setHoverOffset(int x, int y)
+{
+    mHoverOffset = IntVector2(x, y);
+}

+ 12 - 2
Engine/UI/BorderImage.h

@@ -32,14 +32,16 @@ class Texture2D;
 //! An UI element with an image that optionally has a border
 class BorderImage : public UIElement
 {
+    DEFINE_TYPE(BorderImage);
+    
 public:
     //! Construct with name
     BorderImage(const std::string& name = std::string());
     //! Destruct
     virtual ~BorderImage();
     
-    //! Load parameters from an XML file
-    virtual XMLElement loadParameters(XMLFile* file, const std::string& elementName, ResourceCache* cache);
+    //! Set UI element style from XML data
+    virtual void setStyle(const XMLElement& element, ResourceCache* cache);
     //! Return UI rendering batches
     virtual void getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads, const IntRect& currentScissor);
     
@@ -55,6 +57,10 @@ public:
     void setBorder(const IntRect& rect);
     //! Set image border dimensions
     void setBorder(int left, int top, int right, int bottom);
+    //! Set offset to image rectangle used on hover
+    void setHoverOffset(const IntVector2& offset);
+    //! Set offset to image rectangle used on hover
+    void setHoverOffset(int x, int y);
     
     //! Return texture
     Texture* getTexture() const { return mTexture; }
@@ -62,6 +68,8 @@ public:
     const IntRect& getImageRect() const { return mImageRect; }
     //! Return image border dimensions
     const IntRect& getBorder() const { return mBorder; }
+    //! Return offset to image rectangle used on hover
+    const IntVector2& getHoverOffset() const { return mHoverOffset; }
     
 protected:
     //! Texture
@@ -70,6 +78,8 @@ protected:
     IntRect mImageRect;
     //! Image border dimensions
     IntRect mBorder;
+    //! Offset to image rectangle on hover
+    IntVector2 mHoverOffset;
 };
 
 #endif // UI_BORDERIMAGE_H

+ 15 - 44
Engine/UI/Button.cpp

@@ -32,56 +32,48 @@
 
 Button::Button(const std::string& name) :
     BorderImage(name),
-    mInactiveRect(0, 0, 0, 0),
-    mPressedRect(0, 0, 0, 0),
-    mLabelOffset(0, 0),
+    mInactiveRect(IntRect::sZero),
+    mPressedRect(IntRect::sZero),
+    mLabelOffset(IntVector2::sZero),
     mPressed(false)
 {
     mClipChildren = true;
     mEnabled = true;
-    mLabelContainer = new UIElement();
-    addChild(mLabelContainer);
 }
 
 Button::~Button()
 {
 }
 
-XMLElement Button::loadParameters(XMLFile* file, const std::string& elementName, ResourceCache* cache)
+void Button::setStyle(const XMLElement& element, ResourceCache* cache)
 {
-    XMLElement paramElem = BorderImage::loadParameters(file, elementName, cache);
+    if (!cache)
+        SAFE_EXCEPTION("Null resource cache for UI element");
     
-    if (paramElem.hasChildElement("inactiverect"))
-        setInactiveRect(paramElem.getChildElement("inactiverect").getIntRect("value"));
-    if (paramElem.hasChildElement("pressedrect"))
-        setPressedRect(paramElem.getChildElement("pressedrect").getIntRect("value"));
-    if (paramElem.hasChildElement("labeloffset"))
-        setLabelOffset(paramElem.getChildElement("labeloffset").getIntVector2("value"));
+    BorderImage::setStyle(element, cache);
     
-    return paramElem;
+    if (element.hasChildElement("inactiverect"))
+        setInactiveRect(element.getChildElement("inactiverect").getIntRect("value"));
+    if (element.hasChildElement("pressedrect"))
+        setPressedRect(element.getChildElement("pressedrect").getIntRect("value"));
+    if (element.hasChildElement("labeloffset"))
+        setLabelOffset(element.getChildElement("labeloffset").getIntVector2("value"));
 }
 
 void Button::update(float timeStep)
 {
     if (!mHovering)
         setPressed(false);
-    
-    if (mLabelContainer->getSize() != getSize())
-        mLabelContainer->setSize(getSize());
 }
 
 void Button::getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads, const IntRect& currentScissor)
 {
-    if (mLabelContainer->getSize() != getSize())
-        mLabelContainer->setSize(getSize());
-    
     if (mPressed)
         mImageRect = mPressedRect;
     else
         mImageRect = mInactiveRect;
     
     BorderImage::getBatches(batches, quads, currentScissor);
-    mHovering = false;
 }
 
 void Button::onHover(const IntVector2& position, const IntVector2& screenPosition, unsigned buttons)
@@ -125,19 +117,6 @@ void Button::setPressedRect(int left, int top, int right, int bottom)
     mPressedRect = IntRect(left, top, right, bottom);
 }
 
-void Button::setLabel(UIElement* label)
-{
-    mLabelContainer->removeChild(mLabel);
-    mLabel = label;
-    if (mLabel)
-    {
-        mLabelContainer->addChild(mLabel);
-        // Center the label element on the button forcibly
-        mLabel->setAlignment(HA_CENTER, VA_CENTER);
-        updateLabelOffset();
-    }
-}
-
 void Button::setLabelOffset(const IntVector2& offset)
 {
     mLabelOffset = offset;
@@ -150,14 +129,6 @@ void Button::setLabelOffset(int x, int y)
 
 void Button::setPressed(bool enable)
 {
-    if (enable != mPressed)
-    {
-        mPressed = enable;
-        updateLabelOffset();
-    }
-}
-
-void Button::updateLabelOffset()
-{
-    mLabelContainer->setPosition(mPressed ? mLabelOffset : IntVector2::sZero);
+    mPressed = enable;
+    setChildOffset(mPressed ? mLabelOffset : IntVector2::sZero);
 }

+ 4 - 12
Engine/UI/Button.h

@@ -29,14 +29,16 @@
 //! An image that reacts to mouse presses
 class Button : public BorderImage
 {
+    DEFINE_TYPE(Button);
+    
 public:
     //! Construct with name
     Button(const std::string& name = std::string());
     //! Destruct
     virtual ~Button();
     
-    //! Load parameters from an XML file
-    virtual XMLElement loadParameters(XMLFile* file, const std::string& elementName, ResourceCache* cache);
+    //! Set UI element style from XML data
+    virtual void setStyle(const XMLElement& element, ResourceCache* cache);
     //! Perform UI element update
     virtual void update(float timeStep);
     //! Return UI rendering batches
@@ -54,8 +56,6 @@ public:
     void setPressedRect(const IntRect& rect);
     //! Set pressed image rectangle
     void setPressedRect(int left, int top, int right, int bottom);
-    //! Set optional label UI element
-    void setLabel(UIElement* label);
     //! Set label offset on press
     void setLabelOffset(const IntVector2& offset);
     //! Set label offset on press
@@ -65,21 +65,13 @@ public:
     const IntRect& getInactiveRect() const { return mInactiveRect; }
     //! Return pressed image rectangle
     const IntRect& getPressedRect() const { return mPressedRect; }
-    //! Return label UI element
-    UIElement* getLabel() const { return mLabel; }
     //! Return label offset on press
     const IntVector2& getLabelOffset() const { return mLabelOffset; }
     
 protected:
     //! Set new pressed state
     void setPressed(bool enable);
-    //! Set offset of label depending on button press state
-    void updateLabelOffset();
     
-    //! Label UI element
-    SharedPtr<UIElement> mLabel;
-    //! Label container for offsetting
-    SharedPtr<UIElement> mLabelContainer;
     //! Inactive image rectangle
     IntRect mInactiveRect;
     //! Pressed image rectangle

+ 10 - 10
Engine/UI/CheckBox.cpp

@@ -32,8 +32,8 @@
 
 CheckBox::CheckBox(const std::string& name) :
     BorderImage(name),
-    mUncheckedRect(0, 0, 0, 0),
-    mCheckedRect(0, 0, 0, 0),
+    mUncheckedRect(IntRect::sZero),
+    mCheckedRect(IntRect::sZero),
     mChecked(false)
 {
     mEnabled = true;
@@ -43,16 +43,17 @@ CheckBox::~CheckBox()
 {
 }
 
-XMLElement CheckBox::loadParameters(XMLFile* file, const std::string& elementName, ResourceCache* cache)
+void CheckBox::setStyle(const XMLElement& element, ResourceCache* cache)
 {
-    XMLElement paramElem = BorderImage::loadParameters(file, elementName, cache);
+    if (!cache)
+        SAFE_EXCEPTION("Null resource cache for UI element");
     
-    if (paramElem.hasChildElement("uncheckedrect"))
-        setUncheckedRect(paramElem.getChildElement("uncheckedrect").getIntRect("value"));
-    if (paramElem.hasChildElement("checkedrect"))
-        setCheckedRect(paramElem.getChildElement("checkedrect").getIntRect("value"));
+    BorderImage::setStyle(element, cache);
     
-    return paramElem;
+    if (element.hasChildElement("uncheckedrect"))
+        setUncheckedRect(element.getChildElement("uncheckedrect").getIntRect("value"));
+    if (element.hasChildElement("checkedrect"))
+        setCheckedRect(element.getChildElement("checkedrect").getIntRect("value"));
 }
 
 void CheckBox::getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads, const IntRect& currentScissor)
@@ -63,7 +64,6 @@ void CheckBox::getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& qu
         mImageRect = mUncheckedRect;
     
     BorderImage::getBatches(batches, quads, currentScissor);
-    mHovering = false;
 }
 
 void CheckBox::onClick(const IntVector2& position, const IntVector2& screenPosition, unsigned buttons)

+ 4 - 2
Engine/UI/CheckBox.h

@@ -28,14 +28,16 @@
 
 class CheckBox : public BorderImage
 {
+    DEFINE_TYPE(CheckBox);
+    
 public:
     //! Construct with name
     CheckBox(const std::string& name = std::string());
     //! Destruct
     virtual ~CheckBox();
     
-    //! Load parameters from an XML file
-    virtual XMLElement loadParameters(XMLFile* file, const std::string& elementName, ResourceCache* cache);
+    //! Set UI element style from XML data
+    virtual void setStyle(const XMLElement& element, ResourceCache* cache);
     //! Return UI rendering batches
     virtual void getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads, const IntRect& currentScissor);
     //! React to mouse click

+ 6 - 5
Engine/UI/Cursor.cpp

@@ -40,14 +40,15 @@ Cursor::~Cursor()
 {
 }
 
-XMLElement Cursor::loadParameters(XMLFile* file, const std::string& elementName, ResourceCache* cache)
+void Cursor::setStyle(const XMLElement& element, ResourceCache* cache)
 {
-    XMLElement paramElem = BorderImage::loadParameters(file, elementName, cache);
+    if (!cache)
+        SAFE_EXCEPTION("Null resource cache for UI element");
     
-    if (paramElem.hasChildElement("hotspot"))
-        setHotspot(paramElem.getChildElement("hotspot").getIntVector2("value"));
+    BorderImage::setStyle(element, cache);
     
-    return paramElem;
+    if (element.hasChildElement("hotspot"))
+        setHotspot(element.getChildElement("hotspot").getIntVector2("value"));
 }
 
 IntVector2 Cursor::getScreenPosition()

+ 4 - 2
Engine/UI/Cursor.h

@@ -29,14 +29,16 @@
 //! An image with hotspot coordinates
 class Cursor : public BorderImage
 {
+    DEFINE_TYPE(Cursor);
+    
 public:
     //! Construct with name
     Cursor(const std::string& name = std::string());
     //! Destruct
     virtual ~Cursor();
     
-    //! Load parameters from an XML file
-    virtual XMLElement loadParameters(XMLFile* file, const std::string& elementName, ResourceCache* cache);
+    //! Set UI element style from XML data
+    virtual void setStyle(const XMLElement& element, ResourceCache* cache);
     //! Return UI element screen position
     virtual IntVector2 getScreenPosition();
     

+ 30 - 6
Engine/UI/Text.cpp

@@ -45,17 +45,34 @@ Text::~Text()
 {
 }
 
-XMLElement Text::loadParameters(XMLFile* file, const std::string& elementName, ResourceCache* cache)
+void Text::setStyle(const XMLElement& element, ResourceCache* cache)
 {
-    XMLElement paramElem = UIElement::loadParameters(file, elementName, cache);
+    if (!cache)
+        SAFE_EXCEPTION("Null resource cache for UI element");
     
-    if (paramElem.hasChildElement("font"))
+    UIElement::setStyle(element, cache);
+    
+    if (element.hasChildElement("font"))
     {
-        XMLElement fontElem = paramElem.getChildElement("font");
+        XMLElement fontElem = element.getChildElement("font");
         setFont(cache->getResource<Font>(fontElem.getString("name")), fontElem.getInt("size"));
     }
-    
-    return paramElem;
+    if (element.hasChildElement("maxwidth"))
+        setMaxWidth(element.getChildElement("maxwidth").getInt("value"));
+    if (element.hasChildElement("text"))
+        setText(element.getChildElement("text").getString("value"));
+    if (element.hasChildElement("textspacing"))
+        setTextSpacing(element.getChildElement("textspacing").getFloat("value"));
+    if (element.hasChildElement("textalignment"))
+    {
+        std::string horiz = element.getChildElement("textalignment").getStringLower("value");
+        if (horiz == "left")
+            setTextAlignment(HA_LEFT);
+        if (horiz == "center")
+            setTextAlignment(HA_CENTER);
+        if (horiz == "right")
+            setTextAlignment(HA_RIGHT);
+    }
 }
 
 bool Text::setFont(Font* font, int size)
@@ -108,7 +125,11 @@ void Text::setTextSpacing(float spacing)
 void Text::getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads, const IntRect& currentScissor)
 {
     if (!mFont)
+    {
+        mHovering = false;
         return;
+    }
+    
     const FontFace* face = mFont->getFace(mFontSize);
     
     UIBatch batch;
@@ -142,6 +163,9 @@ void Text::getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads,
     }
     
     UIBatch::addOrMerge(batch, batches);
+    
+    // Reset hovering for next frame
+    mHovering = false;
 }
 
 int Text::getRowHeight() const

+ 4 - 2
Engine/UI/Text.h

@@ -33,14 +33,16 @@ class Font;
 //! An UI element that displays text
 class Text : public UIElement
 {
+    DEFINE_TYPE(Text);
+    
 public:
     //! Construct with initial text and name
     Text(const std::string& text = std::string(), const std::string& name = std::string());
     //! Destruct
     virtual ~Text();
     
-    //! Load parameters from an XML file
-    virtual XMLElement loadParameters(XMLFile* file, const std::string& elementName, ResourceCache* cache);
+    //! Set UI element style from XML data
+    virtual void setStyle(const XMLElement& element, ResourceCache* cache);
     //! Return UI rendering batches
     virtual void getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads, const IntRect& currentScissor);
     

+ 84 - 0
Engine/UI/UI.cpp

@@ -22,6 +22,7 @@
 //
 
 #include "Precompiled.h"
+#include "BaseUIElementFactory.h"
 #include "Cursor.h"
 #include "Font.h"
 #include "InputEvents.h"
@@ -33,6 +34,7 @@
 #include "RendererEvents.h"
 #include "RendererImpl.h"
 #include "ResourceCache.h"
+#include "StringUtils.h"
 #include "Texture2D.h"
 #include "UI.h"
 #include "VertexShader.h"
@@ -74,6 +76,9 @@ UI::UI(Renderer* renderer, ResourceCache* cache) :
     mNoTexturePS = mCache->getResource<PixelShader>("Shaders/SM2/Basic_VCol.ps2");
     mDiffTexturePS = mCache->getResource<PixelShader>("Shaders/SM2/Basic_DiffVCol.ps2");
     mAlphaTexturePS = mCache->getResource<PixelShader>("Shaders/SM2/Basic_AlphaVCol.ps2");
+    
+    // Add the base element factory
+    addElementFactory(new BaseUIElementFactory());
 }
 
 UI::~UI()
@@ -239,6 +244,66 @@ void UI::render()
     }
 }
 
+void UI::addElementFactory(UIElementFactory* factory)
+{
+    if (!factory)
+        return;
+    
+    mFactories.push_back(SharedPtr<UIElementFactory>(factory));
+}
+
+SharedPtr<UIElement> UI::createElement(ShortStringHash type, const std::string& name)
+{
+    SharedPtr<UIElement> element;
+    
+    for (unsigned i = 0; i < mFactories.size(); ++i)
+    {
+        element = mFactories[i]->createElement(type, name);
+        if (element)
+            return element;
+    }
+    
+    EXCEPTION("Could not create unknown UI element type " + toString(type));
+}
+
+SharedPtr<UIElement> UI::loadLayout(XMLFile* file, XMLFile* styleFile)
+{
+    PROFILE(UI_LoadLayout);
+    
+    SharedPtr<UIElement> root;
+    
+    if (!file)
+    {
+        LOGERROR("Null UI layout XML file");
+        return root;
+    }
+    
+    LOGDEBUG("Loading UI layout " + file->getName());
+    
+    XMLElement rootElem = file->getRootElement();
+    XMLElement childElem = rootElem.getChildElement("element", false);
+    if (!childElem)
+    {
+        LOGERROR("No root UI element in " + file->getName());
+        return root;
+    }
+    
+    root = createElement(ShortStringHash(childElem.getString("type")), childElem.getString("name", false));
+    
+    // First set the base style from the style file if exists, then apply UI layout overrides
+    if (styleFile)
+        root->setStyleAuto(styleFile, mCache);
+    root->setStyle(childElem, mCache);
+    
+    // Load rest of the elements recursively
+    loadLayout(root, childElem, styleFile);
+    
+    if (childElem.getNextElement("element"))
+        LOGWARNING("Ignored additional root UI elements in " + file->getName());
+    
+    return root;
+}
+
 UIElement* UI::getElementAt(const IntVector2& position, bool enabledOnly)
 {
     UIElement* result = 0;
@@ -441,3 +506,22 @@ void UI::handleChar(StringHash eventType, VariantMap& eventData)
     if (element)
         element->onChar(eventData[P_CHAR].getInt());
 }
+
+void UI::loadLayout(UIElement* current, const XMLElement& elem, XMLFile* styleFile)
+{
+    XMLElement childElem = elem.getChildElement("element", false);
+    while (childElem)
+    {
+        SharedPtr<UIElement> child = createElement(ShortStringHash(childElem.getString("type")), childElem.getString("name", false));
+        // First set the base style from the style file if exists, then apply UI layout overrides
+        if (styleFile)
+            child->setStyleAuto(styleFile, mCache);
+        child->setStyle(childElem, mCache);
+        
+        // Add to the hierarchy and recurse
+        current->addChild(child);
+        loadLayout(child, childElem, styleFile);
+        
+        childElem = childElem.getNextElement("element");
+    }
+}

+ 14 - 1
Engine/UI/UI.h

@@ -31,8 +31,9 @@
 class Cursor;
 class Renderer;
 class ResourceCache;
-class UIElement;
 class UIBatch;
+class UIElement;
+class UIElementFactory;
 
 //! Manages the graphical user interface
 class UI : public RefCounted, public EventListener
@@ -53,6 +54,12 @@ public:
     void update(float timeStep);
     //! Render the UI
     void render();
+    //! Add a UI element factory
+    void addElementFactory(UIElementFactory* factory);
+    //! Create a UI element by type. Throw exception if can not create
+    SharedPtr<UIElement> createElement(ShortStringHash type, const std::string& name = std::string());
+    //! Load a UI layout from an XML file. Optionally specify another XML file for element style. Return the root element
+    SharedPtr<UIElement> loadLayout(XMLFile* file, XMLFile* styleFile = 0);
     
     //! Return root UI elemenet
     UIElement* getRootElement() const { return mRootElement; }
@@ -66,6 +73,8 @@ public:
     UIElement* getFocusElement();
     //! Return cursor position
     IntVector2 getCursorPosition();
+    //! Return UI element factories
+    const std::vector<SharedPtr<UIElementFactory> >& getElementFactories() const { return mFactories; }
     
 private:
     //! Update UI elements and generate batches for UI rendering
@@ -88,6 +97,8 @@ private:
     void handleMouseButtonUp(StringHash eventType, VariantMap& eventData);
     //! Handle character event
     void handleChar(StringHash eventType, VariantMap& eventData);
+    //! Load a UI layout from an XML file recursively
+    void loadLayout(UIElement* current, const XMLElement& elem, XMLFile* styleFile);
     
     //! Renderer
     SharedPtr<Renderer> mRenderer;
@@ -107,6 +118,8 @@ private:
     SharedPtr<UIElement> mRootElement;
     //! Cursor
     SharedPtr<Cursor> mCursor;
+    //! UI element factories
+    std::vector<SharedPtr<UIElementFactory> > mFactories;
     //! UI rendering batches
     std::vector<UIBatch> mBatches;
     //! UI rendering quads

+ 80 - 43
Engine/UI/UIElement.cpp

@@ -37,6 +37,7 @@ UIElement::UIElement(const std::string& name) :
     mSize(IntVector2::sZero),
     mHorizontalAlignment(HA_LEFT),
     mVerticalAlignment(VA_TOP),
+    mChildOffset(IntVector2::sZero),
     mHoverColor(Color(0.0f, 0.0f, 0.0f)),
     mPriority(0),
     mOpacity(1.0f),
@@ -68,33 +69,18 @@ UIElement::~UIElement()
     }
 }
 
-XMLElement UIElement::loadParameters(XMLFile* file, const std::string& elementName, ResourceCache* cache)
+void UIElement::setStyle(const XMLElement& element, ResourceCache* cache)
 {
-    if (!file)
-        SAFE_EXCEPTION_RET("Null UI definition file", XMLElement());
     if (!cache)
-        SAFE_EXCEPTION_RET("Null resource cache", XMLElement());
+        SAFE_EXCEPTION("Null resource cache for UI element");
     
-    XMLElement rootElem = file->getRootElement();
-    XMLElement paramElem = rootElem.getChildElement("element", false);
-    
-    while (paramElem.notNull())
-    {
-        if (paramElem.getString("name") == elementName)
-            break;
-        paramElem = paramElem.getNextElement("element");
-    }
-    
-    if (paramElem.isNull())
-        SAFE_EXCEPTION_RET("No UI element definition " + elementName + " in " + file->getName(), XMLElement());
-    
-    if (paramElem.hasChildElement("position"))
-        setPosition(paramElem.getChildElement("position").getIntVector2("value"));
-    if (paramElem.hasChildElement("size"))
-        setSize(paramElem.getChildElement("size").getIntVector2("value"));
-    if (paramElem.hasChildElement("alignment"))
+    if (element.hasChildElement("position"))
+        setPosition(element.getChildElement("position").getIntVector2("value"));
+    if (element.hasChildElement("size"))
+        setSize(element.getChildElement("size").getIntVector2("value"));
+    if (element.hasChildElement("alignment"))
     {
-        XMLElement alignElem = paramElem.getChildElement("alignment");
+        XMLElement alignElem = element.getChildElement("alignment");
         
         std::string horiz;
         std::string vert;
@@ -119,13 +105,13 @@ XMLElement UIElement::loadParameters(XMLFile* file, const std::string& elementNa
         if (vert == "bottom")
             setVerticalAlignment(VA_BOTTOM);
     }
-    if (paramElem.hasChildElement("priority"))
-        setPriority(paramElem.getChildElement("priority").getInt("value"));
-    if (paramElem.hasChildElement("opacity"))
-        setOpacity(paramElem.getChildElement("opacity").getFloat("value"));
-    if (paramElem.hasChildElement("color"))
+    if (element.hasChildElement("priority"))
+        setPriority(element.getChildElement("priority").getInt("value"));
+    if (element.hasChildElement("opacity"))
+        setOpacity(element.getChildElement("opacity").getFloat("value"));
+    if (element.hasChildElement("color"))
     {
-        XMLElement colorElem = paramElem.getChildElement("color");
+        XMLElement colorElem = element.getChildElement("color");
         if (colorElem.hasAttribute("value"))
             setColor(colorElem.getColor("value"));
         if (colorElem.hasAttribute("topleft"))
@@ -137,20 +123,18 @@ XMLElement UIElement::loadParameters(XMLFile* file, const std::string& elementNa
         if (colorElem.hasAttribute("bottomright"))
             setColor(C_BOTTOMRIGHT, colorElem.getColor("bottomright"));
     }
-    if (paramElem.hasChildElement("hovercolor"))
-        setHoverColor(paramElem.getChildElement("hovercolor").getColor("value"));
-    if (paramElem.hasChildElement("bringtofront"))
-        setBringToFront(paramElem.getChildElement("bringtofront").getBool("enable"));
-    if (paramElem.hasChildElement("clipchildren"))
-        setClipChildren(paramElem.getChildElement("clipchildren").getBool("enable"));
-    if (paramElem.hasChildElement("enabled"))
-        setEnabled(paramElem.getChildElement("enabled").getBool("enable"));
-    if (paramElem.hasChildElement("focusable"))
-        setFocusable(paramElem.getChildElement("focusable").getBool("enable"));
-    if (paramElem.hasChildElement("visible"))
-        setVisible(paramElem.getChildElement("visible").getBool("enable"));
-    
-    return paramElem;
+    if (element.hasChildElement("hovercolor"))
+        setHoverColor(element.getChildElement("hovercolor").getColor("value"));
+    if (element.hasChildElement("bringtofront"))
+        setBringToFront(element.getChildElement("bringtofront").getBool("enable"));
+    if (element.hasChildElement("clipchildren"))
+        setClipChildren(element.getChildElement("clipchildren").getBool("enable"));
+    if (element.hasChildElement("enabled"))
+        setEnabled(element.getChildElement("enabled").getBool("enable"));
+    if (element.hasChildElement("focusable"))
+        setFocusable(element.getChildElement("focusable").getBool("enable"));
+    if (element.hasChildElement("visible"))
+        setVisible(element.getChildElement("visible").getBool("enable"));
 }
 
 void UIElement::update(float timeStep)
@@ -159,6 +143,7 @@ void UIElement::update(float timeStep)
 
 void UIElement::getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads, const IntRect& currentScissor)
 {
+    // Reset hovering for next frame
     mHovering = false;
 }
 
@@ -201,6 +186,8 @@ IntVector2 UIElement::getScreenPosition()
                 break;
             }
             
+            pos += parent->mChildOffset;
+            
             current = parent;
             parent = parent->mParent;
         }
@@ -397,6 +384,12 @@ void UIElement::setVisible(bool enable)
     mVisible = enable;
 }
 
+void UIElement::setStyleAuto(XMLFile* file, ResourceCache* cache)
+{
+    XMLElement element = getStyleElement(file);
+    setStyle(element, cache);
+}
+
 void UIElement::addChild(UIElement* element)
 {
     if ((!element) || (element->mParent == this) || (mParent == element))
@@ -495,6 +488,23 @@ UIElement* UIElement::getChild(const std::string& name, bool recursive) const
     return 0;
 }
 
+XMLElement UIElement::getStyleElement(XMLFile* file) const
+{
+    if (file)
+    {
+        XMLElement rootElem = file->getRootElement();
+        XMLElement childElem = rootElem.getChildElement("element", false);
+        while (childElem)
+        {
+            if (childElem.getString("type", false) == getTypeName())
+                return childElem;
+            childElem = childElem.getNextElement("element");
+        }
+    }
+    
+    return XMLElement();
+}
+
 IntVector2 UIElement::screenToElement(const IntVector2& screenPosition)
 {
     return screenPosition - getScreenPosition();
@@ -517,6 +527,23 @@ void UIElement::adjustScissor(IntRect& currentScissor)
     }
 }
 
+XMLElement UIElement::getStyleElement(XMLFile* file, const std::string& typeName)
+{
+    if (file)
+    {
+        XMLElement rootElem = file->getRootElement();
+        XMLElement childElem = rootElem.getChildElement("element", false);
+        while (childElem)
+        {
+            if (childElem.getString("type", false) == typeName)
+                return childElem;
+            childElem = childElem.getNextElement("element");
+        }
+    }
+    
+    return XMLElement();
+}
+
 void UIElement::markDirty()
 {
     if ((mScreenPositionDirty) && (mDerivedOpacityDirty))
@@ -529,6 +556,16 @@ void UIElement::markDirty()
         (*i)->markDirty();
 }
 
+void UIElement::setChildOffset(const IntVector2& offset)
+{
+    if (offset != mChildOffset)
+    {
+        mChildOffset = offset;
+        for (std::vector<SharedPtr<UIElement> >::const_iterator i = mChildren.begin(); i != mChildren.end(); ++i)
+            (*i)->markDirty();
+    }
+}
+
 void UIElement::getChildrenRecursive(std::vector<UIElement*>& dest) const
 {
     for (std::vector<SharedPtr<UIElement> >::const_iterator i = mChildren.begin(); i != mChildren.end(); ++i)

+ 22 - 6
Engine/UI/UIElement.h

@@ -25,6 +25,7 @@
 #define UI_UIELEMENT_H
 
 #include "EventListener.h"
+#include "HashedType.h"
 #include "SharedPtr.h"
 #include "UIBatch.h"
 #include "Vector2.h"
@@ -60,16 +61,18 @@ static const unsigned NUM_UIELEMENT_CORNERS = 4;
 class ResourceCache;
 
 //! Base class for UI elements
-class UIElement : public RefCounted, public EventListener
+class UIElement : public HashedType, public EventListener
 {
+    DEFINE_TYPE(UIElement);
+    
 public:
     //! Construct with name
     UIElement(const std::string& name = std::string());
     //! Destruct with name
     virtual ~UIElement();
     
-    //! Load parameters from an XML file
-    virtual XMLElement loadParameters(XMLFile* file, const std::string& elementName, ResourceCache* cache);
+    //! Set UI element style from XML data
+    virtual void setStyle(const XMLElement& element, ResourceCache* cache);
     //! Perform UI element update
     virtual void update(float timeStep);
     //! Return UI rendering batches
@@ -116,7 +119,7 @@ public:
     void setColor(const Color& color);
     //! Set color on one corner
     void setColor(UIElementCorner corner, const Color& color);
-    //! Set hover color modification
+    //! Set color modification used on hover
     void setHoverColor(const Color& color);
     //! Set priority
     void setPriority(int priority);
@@ -134,6 +137,8 @@ public:
     void setFocus(bool enable);
     //! Set whether is visible
     void setVisible(bool enable);
+    //! Set style from an XML file. Find the style element automatically
+    void setStyleAuto(XMLFile* file, ResourceCache* cache);
     //! Add a child element
     void addChild(UIElement* element);
     //! Remove a child element
@@ -151,13 +156,15 @@ public:
     int getWidth() const { return mSize.mX; }
     //! Return height
     int getHeight() const { return mSize.mY; }
+    //! Return child element offset
+    const IntVector2& getChildOffset() const { return mChildOffset; }
     //! Return horizontal alignment
     HorizontalAlignment getHorizontalAlignment() const { return mHorizontalAlignment; }
     //! Return vertical alignment
     VerticalAlignment getVerticalAlignment() const { return mVerticalAlignment; }
     //! Return corner color
     const Color& getColor(UIElementCorner corner) const { return mColor[corner]; }
-    //! Return hover color modification
+    //! Return color modification used on hover
     const Color& getHoverColor() { return mHoverColor; }
     //! Return priority
     int getPriority() const { return mPriority; }
@@ -189,6 +196,8 @@ public:
     std::vector<UIElement*> getChildren(bool recursive = false) const;
     //! Return parent element
     UIElement* getParent() const { return mParent; }
+    //! Return first matching UI style element from an XML file. If not found, return empty
+    XMLElement getStyleElement(XMLFile* file) const;
     
     //! Convert screen coordinates to element coordinates
     IntVector2 screenToElement(const IntVector2& screenPosition);
@@ -198,9 +207,14 @@ public:
     //! Adjust scissor for rendering
     void adjustScissor(IntRect& currentScissor);
     
+    //! Return first matching UI style element from an XML file, with freely specified type. If not found, return empty
+    static XMLElement getStyleElement(XMLFile* file, const std::string& typeName);
+    
 protected:
     //! Mark screen position as needing an update
     void markDirty();
+    //! Set child offset
+    void setChildOffset(const IntVector2& offset);
     
     //! Name
     std::string mName;
@@ -210,7 +224,7 @@ protected:
     UIElement* mParent;
     //! Colors
     Color mColor[NUM_UIELEMENT_CORNERS];
-    //! Hover color modification
+    //! Color modification on hover
     Color mHoverColor;
     //! Priority
     int mPriority;
@@ -239,6 +253,8 @@ private:
     IntVector2 mScreenPosition;
     //! Size
     IntVector2 mSize;
+    //! Child elements' offset. Used internally
+    IntVector2 mChildOffset;
     //! Horizontal alignment
     HorizontalAlignment mHorizontalAlignment;
     //! Vertical alignment

+ 40 - 0
Engine/UI/UIElementFactory.h

@@ -0,0 +1,40 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2011 Lasse Öörni
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#ifndef UI_UIELEMENTFACTORY_H
+#define UI_UIELEMENTFACTORY_H
+
+#include "RefCount.h"
+#include "StringHash.h"
+
+class UIElement;
+
+//! Base class for UI element factories
+class UIElementFactory : public RefCounted
+{
+public:
+    //! Create a UI element of the specified type. Return null if can not create
+    virtual UIElement* createElement(ShortStringHash type, const std::string& name = std::string()) = 0;
+};
+
+#endif // UI_UIELEMENTFACTORY

+ 14 - 13
Engine/UI/Window.cpp

@@ -49,22 +49,23 @@ Window::~Window()
 {
 }
 
-XMLElement Window::loadParameters(XMLFile* file, const std::string& elementName, ResourceCache* cache)
+void Window::setStyle(const XMLElement& element, ResourceCache* cache)
 {
-    XMLElement paramElem = BorderImage::loadParameters(file, elementName, cache);
+    if (!cache)
+        SAFE_EXCEPTION("Null resource cache for UI element");
     
-    if (paramElem.hasChildElement("minsize"))
-        setMinSize(paramElem.getChildElement("minsize").getIntVector2("value"));
-    if (paramElem.hasChildElement("maxsize"))
-        setMaxSize(paramElem.getChildElement("maxsize").getIntVector2("value"));
-    if (paramElem.hasChildElement("resizeborder"))
-        setResizeBorder(paramElem.getChildElement("resizeborder").getIntRect("value"));
-    if (paramElem.hasChildElement("movable"))
-        setMovable(paramElem.getChildElement("movable").getBool("enable"));
-    if (paramElem.hasChildElement("resizable"))
-        setResizable(paramElem.getChildElement("resizable").getBool("enable"));
+    BorderImage::setStyle(element, cache);
     
-    return paramElem;
+    if (element.hasChildElement("minsize"))
+        setMinSize(element.getChildElement("minsize").getIntVector2("value"));
+    if (element.hasChildElement("maxsize"))
+        setMaxSize(element.getChildElement("maxsize").getIntVector2("value"));
+    if (element.hasChildElement("resizeborder"))
+        setResizeBorder(element.getChildElement("resizeborder").getIntRect("value"));
+    if (element.hasChildElement("movable"))
+        setMovable(element.getChildElement("movable").getBool("enable"));
+    if (element.hasChildElement("resizable"))
+        setResizable(element.getChildElement("resizable").getBool("enable"));
 }
 
 void Window::onDragStart(const IntVector2& position, const IntVector2& screenPosition, unsigned buttons)

+ 4 - 2
Engine/UI/Window.h

@@ -46,14 +46,16 @@ enum WindowDragMode
 //! Window that can optionally by moved or resized
 class Window : public BorderImage
 {
+    DEFINE_TYPE(Window);
+    
 public:
     //! Construct with name
     Window(const std::string& name = std::string());
     //! Destruct
     virtual ~Window();
     
-    //! Load parameters from an XML file
-    virtual XMLElement loadParameters(XMLFile* file, const std::string& elementName, ResourceCache* cache);
+    //! Set UI element style from XML data
+    virtual void setStyle(const XMLElement& element, ResourceCache* cache);
     //! React to mouse drag start
     virtual void onDragStart(const IntVector2& position, const IntVector2& screenPosition, unsigned buttons);
     //! React to mouse drag motion

+ 4 - 20
Examples/Test/Application.cpp

@@ -28,8 +28,6 @@
 #include "Audio.h"
 #include "Application.h"
 #include "BillboardSet.h"
-#include "Button.h"
-#include "CheckBox.h"
 #include "CollisionShape.h"
 #include "Cursor.h"
 #include "CustomObject.h"
@@ -199,29 +197,15 @@ void Application::init()
     UI* ui = mEngine->getUI();
     UIElement* uiRoot = ui->getRootElement();
     
-    XMLFile* uiSetup = mCache->getResource<XMLFile>("UI/UI.xml");
+    XMLFile* uiStyle = mCache->getResource<XMLFile>("UI/DefaultStyle.xml");
     
     Cursor* cursor = new Cursor("Cursor");
-    cursor->loadParameters(uiSetup, "Cursor", mCache);
+    cursor->setStyleAuto(uiStyle, mCache);
     cursor->setPosition(renderer->getWidth() / 2, renderer->getHeight() / 2);
     ui->setCursor(cursor);
     
-    //Button* button = new Button("TestButton");
-    //button->loadParameters(uiSetup, "Button", mCache);
-    //Text* text = new Text("TEST");
-    //text->setFont(mCache->getResource<Font>("cour.ttf"), 12);
-    //button->setLabel(text);
-    //button->setAlignment(HA_CENTER, VA_CENTER);
-    //button->setSize(100, 40);
-    //uiRoot->addChild(button);
-    //
-    //for (unsigned i = 0; i < 4; ++i)
-    //{
-    //    CheckBox* checkBox = new CheckBox("TestCheckBox" + toString(i));
-    //    checkBox->loadParameters(uiSetup, "CheckBox", mCache);
-    //    checkBox->setPosition(renderer->getWidth() / 3, renderer->getHeight() / 3 + 32 * i);
-    //    uiRoot->addChild(checkBox);
-    //}
+    //XMLFile* uiLayout = mCache->getResource<XMLFile>("UI/TestLayout.xml");
+    //uiRoot->addChild(ui->loadLayout(uiLayout, uiStyle));
     
     mScene = mEngine->createScene();
     PhysicsWorld* world = mScene->getExtension<PhysicsWorld>();