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>
 <elements>
-    <element name="Cursor">
+    <element type="Cursor">
         <size value="16 24" />
         <size value="16 24" />
         <texture name="Textures/UI.png" />
         <texture name="Textures/UI.png" />
         <imagerect value="0 0 16 24" />
         <imagerect value="0 0 16 24" />
         <hotspot value="0 0" />
         <hotspot value="0 0" />
     </element>
     </element>
-    <element name="Button">
+    <element type="Button">
         <size value="16 16" />
         <size value="16 16" />
         <texture name="Textures/UI.png" />
         <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" />
         <inactiverect value="16 0 32 16" />
         <pressedrect value="32 0 48 16" />
         <pressedrect value="32 0 48 16" />
         <border value="4 4 4 4" />
         <border value="4 4 4 4" />
+        <hoveroffset value="0 16" />
         <labeloffset value="-1 1" />
         <labeloffset value="-1 1" />
     </element>
     </element>
-    <element name="Window">
+    <element type="Window">
         <texture name="Textures/UI.png" />
         <texture name="Textures/UI.png" />
         <imagerect value="48 0 64 16" />
         <imagerect value="48 0 64 16" />
         <border value="3 3 3 3" />
         <border value="3 3 3 3" />
@@ -23,16 +22,15 @@
         <movable enable="true" />
         <movable enable="true" />
         <resizable enable="true" />
         <resizable enable="true" />
     </element>
     </element>
-    <element name="Text">
+    <element type="Text">
         <font name="Cour.ttf" size="10" />
         <font name="Cour.ttf" size="10" />
     </element>
     </element>
-    <element name="CheckBox">
+    <element type="CheckBox">
         <size value="16 16" />
         <size value="16 16" />
         <texture name="Textures/UI.png" />
         <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" />
         <uncheckedrect value="64 0 80 16" />
         <checkedrect value="80 0 96 16" />
         <checkedrect value="80 0 96 16" />
         <border value="4 4 4 4" />
         <border value="4 4 4 4" />
+        <hoveroffset value="0 16" />
     </element>
     </element>
 </elements>
 </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 "BorderImage.h"
 #include "Channel.h"
 #include "Channel.h"
 #include "Deserializer.h"
 #include "Deserializer.h"
+#include "Engine.h"
 #include "GeometryNode.h"
 #include "GeometryNode.h"
 #include "RegisterArray.h"
 #include "RegisterArray.h"
 #include "Resource.h"
 #include "Resource.h"
@@ -331,17 +332,43 @@ template <class T> T* ConstructUIElementWithName(const std::string& name)
     return new T(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 function for registering a class derived from UIElement
 template <class T> void registerUIElement(asIScriptEngine* engine, const char* className)
 template <class T> void registerUIElement(asIScriptEngine* engine, const char* className)
 {
 {
     static const std::string declFactory(std::string(className) + "@+ f()");
     static const std::string declFactory(std::string(className) + "@+ f()");
     static const std::string declFactoryWithName(std::string(className) + "@+ f(const string& in)");
     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, 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_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 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(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);
     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, "const IntVector2& getSize() const", asMETHOD(T, getSize), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "int getWidth() const", asMETHOD(T, getWidth), 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, "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, "HorizontalAlignment getHorizontalAlignment() const", asMETHOD(T, getHorizontalAlignment), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "VerticalAlignment getVerticalAlignment() const", asMETHOD(T, getVerticalAlignment), 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);
     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(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@+ 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, "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 screenToElement(const IntVector2& in)", asMETHOD(T, screenToElement), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "IntVector2 elementToScreen(const IntVector2& in)", asMETHOD(T, elementToScreen), 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 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(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 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, "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& getImageRect() const", asMETHOD(T, getImageRect), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "const IntRect& getBorder() const", asMETHOD(T, getBorder), 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
 #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");
     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)
 static void registerUIElement(asIScriptEngine* engine)
 {
 {
     engine->RegisterEnum("HorizontalAlignment");
     engine->RegisterEnum("HorizontalAlignment");
@@ -69,7 +57,9 @@ static void registerUIElement(asIScriptEngine* engine)
     engine->RegisterEnumValue("UIElementCorner", "C_BOTTOMRIGHT", C_BOTTOMRIGHT);
     engine->RegisterEnumValue("UIElementCorner", "C_BOTTOMRIGHT", C_BOTTOMRIGHT);
     
     
     registerUIElement<UIElement>(engine, "UIElement");
     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
     // Register Variant getPtr() for UIElement
     engine->RegisterObjectMethod("Variant", "UIElement@+ getUIElement() const", asFUNCTION(getVariantPtr<UIElement>), asCALL_CDECL_OBJLAST);
     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)
 static void registerText(asIScriptEngine* engine)
 {
 {
     registerUIElement<Text>(engine, "Text");
     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", "bool setFont(Font@+, int)", asMETHOD(Text, setFont), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text", "void setMaxWidth(int)", asMETHOD(Text, setMaxWidth), 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);
     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)
 static void registerBorderImage(asIScriptEngine* engine)
 {
 {
     registerBorderImage<BorderImage>(engine, "BorderImage");
     registerBorderImage<BorderImage>(engine, "BorderImage");
-    engine->RegisterObjectMethod("BorderImage", "void loadParameters(XMLFile@+, const string& in)", asFUNCTION(UIElementLoadParameters), asCALL_CDECL_OBJLAST);
     registerRefCasts<UIElement, BorderImage>(engine, "UIElement", "BorderImage");
     registerRefCasts<UIElement, BorderImage>(engine, "UIElement", "BorderImage");
 }
 }
 
 
 static void registerCursor(asIScriptEngine* engine)
 static void registerCursor(asIScriptEngine* engine)
 {
 {
     registerBorderImage<Cursor>(engine, "Cursor");
     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(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", "void setHotspot(int, int)", asMETHODPR(Cursor, setHotspot, (int, int), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("Cursor", "const IntVector2& getHotspot() const", asMETHOD(Cursor, getHotspot), 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)
 static void registerButton(asIScriptEngine* engine)
 {
 {
     registerBorderImage<Button>(engine, "Button");
     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(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 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(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 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(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", "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& getInactiveRect() const", asMETHOD(Button, getInactiveRect), asCALL_THISCALL);
     engine->RegisterObjectMethod("Button", "const IntRect& getPressedRect() const", asMETHOD(Button, getPressedRect), 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);
     engine->RegisterObjectMethod("Button", "const IntVector2& getLabelOffset() const", asMETHOD(Button, getLabelOffset), asCALL_THISCALL);
     registerRefCasts<UIElement, Button>(engine, "UIElement", "Button");
     registerRefCasts<UIElement, Button>(engine, "UIElement", "Button");
 }
 }
@@ -133,7 +117,6 @@ static void registerButton(asIScriptEngine* engine)
 static void registerCheckBox(asIScriptEngine* engine)
 static void registerCheckBox(asIScriptEngine* engine)
 {
 {
     registerBorderImage<CheckBox>(engine, "CheckBox");
     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 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(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);
     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)
 static void registerWindow(asIScriptEngine* engine)
 {
 {
     registerBorderImage<Window>(engine, "Window");
     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 setMovable(bool)", asMETHOD(Window, setMovable), asCALL_THISCALL);
     engine->RegisterObjectMethod("Window", "void setResizable(bool)", asMETHOD(Window, setResizable), 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);
     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();
     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)
 static void registerUI(asIScriptEngine* engine)
 {
 {
     engine->RegisterObjectType("UI", 0, asOBJ_REF);
     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 setCursor(Cursor@+)", asMETHOD(UI, setCursor), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "void setFocusElement(UIElement@+)", asMETHOD(UI, setFocusElement), 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", "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", "UIElement@+ getRootElement() const", asMETHOD(UI, getRootElement), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "Cursor@+ getCursor() const", asMETHOD(UI, getCursor), 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);
     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
     else
-        mScissorRect = IntRect(0, 0, 0, 0);
+        mScissorRect = IntRect::sZero;
     
     
     if (enable != mScissorTest)
     if (enable != mScissorTest)
     {
     {
@@ -1528,7 +1528,7 @@ void Renderer::setScissorTest(bool enable, const IntRect& rect)
         }
         }
     }
     }
     else
     else
-        mScissorRect = IntRect(0, 0, 0, 0);
+        mScissorRect = IntRect::sZero;
     
     
     if (enable != mScissorTest)
     if (enable != mScissorTest)
     {
     {
@@ -2182,7 +2182,7 @@ void Renderer::resetCachedState()
     mDepthWrite = true;
     mDepthWrite = true;
     mFillMode = FILL_SOLID;
     mFillMode = FILL_SOLID;
     mScissorTest = false;
     mScissorTest = false;
-    mScissorRect = IntRect(0, 0, 0, 0);
+    mScissorRect = IntRect::sZero;
     mStencilTest = false;
     mStencilTest = false;
     mStencilTestMode = CMP_ALWAYS;
     mStencilTestMode = CMP_ALWAYS;
     mStencilPass = OP_KEEP;
     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) :
 BorderImage::BorderImage(const std::string& name) :
     UIElement(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)
 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(
     IntVector2 innerTextureSize(
         max(mImageRect.mRight - mImageRect.mLeft - mBorder.mLeft - mBorder.mRight, 0),
         max(mImageRect.mRight - mImageRect.mLeft - mBorder.mLeft - mBorder.mRight, 0),
         max(mImageRect.mBottom - mImageRect.mTop - mBorder.mTop - mBorder.mBottom, 0));
         max(mImageRect.mBottom - mImageRect.mTop - mBorder.mTop - mBorder.mBottom, 0));
-
+    
+    IntVector2 topLeft(mImageRect.mLeft, mImageRect.mTop);
+    if (mHovering)
+        topLeft += mHoverOffset;
+    
     // Top
     // Top
     if (mBorder.mTop)
     if (mBorder.mTop)
     {
     {
         if (mBorder.mLeft)
         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)
         if (innerSize.mX)
             batch.addQuad(*this, mBorder.mLeft, 0, innerSize.mX, mBorder.mTop,
             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)
         if (mBorder.mRight)
             batch.addQuad(*this, mBorder.mLeft + innerSize.mX, 0, mBorder.mRight, mBorder.mTop,
             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
     // Middle
     if (innerSize.mY)
     if (innerSize.mY)
     {
     {
         if (mBorder.mLeft)
         if (mBorder.mLeft)
             batch.addQuad(*this, 0, mBorder.mTop, mBorder.mLeft, innerSize.mY,
             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)
         if (innerSize.mX)
             batch.addQuad(*this, mBorder.mLeft, mBorder.mTop, innerSize.mX, innerSize.mY,
             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)
         if (mBorder.mRight)
             batch.addQuad(*this, mBorder.mLeft + innerSize.mX, mBorder.mTop, 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);
             mBorder.mRight, innerTextureSize.mY);
     }
     }
     // Bottom
     // Bottom
@@ -101,18 +109,21 @@ void BorderImage::getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>&
     {
     {
         if (mBorder.mLeft)
         if (mBorder.mLeft)
             batch.addQuad(*this, 0, mBorder.mTop + innerSize.mY, mBorder.mLeft, mBorder.mBottom,
             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)
         if (innerSize.mX)
             batch.addQuad(*this, mBorder.mLeft, mBorder.mTop + innerSize.mY, 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);
             innerTextureSize.mX, mBorder.mBottom);
         if (mBorder.mRight)
         if (mBorder.mRight)
             batch.addQuad(*this, mBorder.mLeft + innerSize.mX, mBorder.mTop + innerSize.mY,
             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);
     UIBatch::addOrMerge(batch, batches);
+    
+    // Reset hovering for next frame
+    mHovering = false;
 }
 }
 
 
 void BorderImage::setTexture(Texture* texture)
 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.mRight = max(right, 0);
     mBorder.mBottom = max(bottom, 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
 //! An UI element with an image that optionally has a border
 class BorderImage : public UIElement
 class BorderImage : public UIElement
 {
 {
+    DEFINE_TYPE(BorderImage);
+    
 public:
 public:
     //! Construct with name
     //! Construct with name
     BorderImage(const std::string& name = std::string());
     BorderImage(const std::string& name = std::string());
     //! Destruct
     //! Destruct
     virtual ~BorderImage();
     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
     //! Return UI rendering batches
     virtual void getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads, const IntRect& currentScissor);
     virtual void getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads, const IntRect& currentScissor);
     
     
@@ -55,6 +57,10 @@ public:
     void setBorder(const IntRect& rect);
     void setBorder(const IntRect& rect);
     //! Set image border dimensions
     //! Set image border dimensions
     void setBorder(int left, int top, int right, int bottom);
     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
     //! Return texture
     Texture* getTexture() const { return mTexture; }
     Texture* getTexture() const { return mTexture; }
@@ -62,6 +68,8 @@ public:
     const IntRect& getImageRect() const { return mImageRect; }
     const IntRect& getImageRect() const { return mImageRect; }
     //! Return image border dimensions
     //! Return image border dimensions
     const IntRect& getBorder() const { return mBorder; }
     const IntRect& getBorder() const { return mBorder; }
+    //! Return offset to image rectangle used on hover
+    const IntVector2& getHoverOffset() const { return mHoverOffset; }
     
     
 protected:
 protected:
     //! Texture
     //! Texture
@@ -70,6 +78,8 @@ protected:
     IntRect mImageRect;
     IntRect mImageRect;
     //! Image border dimensions
     //! Image border dimensions
     IntRect mBorder;
     IntRect mBorder;
+    //! Offset to image rectangle on hover
+    IntVector2 mHoverOffset;
 };
 };
 
 
 #endif // UI_BORDERIMAGE_H
 #endif // UI_BORDERIMAGE_H

+ 15 - 44
Engine/UI/Button.cpp

@@ -32,56 +32,48 @@
 
 
 Button::Button(const std::string& name) :
 Button::Button(const std::string& name) :
     BorderImage(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)
     mPressed(false)
 {
 {
     mClipChildren = true;
     mClipChildren = true;
     mEnabled = true;
     mEnabled = true;
-    mLabelContainer = new UIElement();
-    addChild(mLabelContainer);
 }
 }
 
 
 Button::~Button()
 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)
 void Button::update(float timeStep)
 {
 {
     if (!mHovering)
     if (!mHovering)
         setPressed(false);
         setPressed(false);
-    
-    if (mLabelContainer->getSize() != getSize())
-        mLabelContainer->setSize(getSize());
 }
 }
 
 
 void Button::getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads, const IntRect& currentScissor)
 void Button::getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads, const IntRect& currentScissor)
 {
 {
-    if (mLabelContainer->getSize() != getSize())
-        mLabelContainer->setSize(getSize());
-    
     if (mPressed)
     if (mPressed)
         mImageRect = mPressedRect;
         mImageRect = mPressedRect;
     else
     else
         mImageRect = mInactiveRect;
         mImageRect = mInactiveRect;
     
     
     BorderImage::getBatches(batches, quads, currentScissor);
     BorderImage::getBatches(batches, quads, currentScissor);
-    mHovering = false;
 }
 }
 
 
 void Button::onHover(const IntVector2& position, const IntVector2& screenPosition, unsigned buttons)
 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);
     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)
 void Button::setLabelOffset(const IntVector2& offset)
 {
 {
     mLabelOffset = offset;
     mLabelOffset = offset;
@@ -150,14 +129,6 @@ void Button::setLabelOffset(int x, int y)
 
 
 void Button::setPressed(bool enable)
 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
 //! An image that reacts to mouse presses
 class Button : public BorderImage
 class Button : public BorderImage
 {
 {
+    DEFINE_TYPE(Button);
+    
 public:
 public:
     //! Construct with name
     //! Construct with name
     Button(const std::string& name = std::string());
     Button(const std::string& name = std::string());
     //! Destruct
     //! Destruct
     virtual ~Button();
     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
     //! Perform UI element update
     virtual void update(float timeStep);
     virtual void update(float timeStep);
     //! Return UI rendering batches
     //! Return UI rendering batches
@@ -54,8 +56,6 @@ public:
     void setPressedRect(const IntRect& rect);
     void setPressedRect(const IntRect& rect);
     //! Set pressed image rectangle
     //! Set pressed image rectangle
     void setPressedRect(int left, int top, int right, int bottom);
     void setPressedRect(int left, int top, int right, int bottom);
-    //! Set optional label UI element
-    void setLabel(UIElement* label);
     //! Set label offset on press
     //! Set label offset on press
     void setLabelOffset(const IntVector2& offset);
     void setLabelOffset(const IntVector2& offset);
     //! Set label offset on press
     //! Set label offset on press
@@ -65,21 +65,13 @@ public:
     const IntRect& getInactiveRect() const { return mInactiveRect; }
     const IntRect& getInactiveRect() const { return mInactiveRect; }
     //! Return pressed image rectangle
     //! Return pressed image rectangle
     const IntRect& getPressedRect() const { return mPressedRect; }
     const IntRect& getPressedRect() const { return mPressedRect; }
-    //! Return label UI element
-    UIElement* getLabel() const { return mLabel; }
     //! Return label offset on press
     //! Return label offset on press
     const IntVector2& getLabelOffset() const { return mLabelOffset; }
     const IntVector2& getLabelOffset() const { return mLabelOffset; }
     
     
 protected:
 protected:
     //! Set new pressed state
     //! Set new pressed state
     void setPressed(bool enable);
     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
     //! Inactive image rectangle
     IntRect mInactiveRect;
     IntRect mInactiveRect;
     //! Pressed image rectangle
     //! Pressed image rectangle

+ 10 - 10
Engine/UI/CheckBox.cpp

@@ -32,8 +32,8 @@
 
 
 CheckBox::CheckBox(const std::string& name) :
 CheckBox::CheckBox(const std::string& name) :
     BorderImage(name),
     BorderImage(name),
-    mUncheckedRect(0, 0, 0, 0),
-    mCheckedRect(0, 0, 0, 0),
+    mUncheckedRect(IntRect::sZero),
+    mCheckedRect(IntRect::sZero),
     mChecked(false)
     mChecked(false)
 {
 {
     mEnabled = true;
     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)
 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;
         mImageRect = mUncheckedRect;
     
     
     BorderImage::getBatches(batches, quads, currentScissor);
     BorderImage::getBatches(batches, quads, currentScissor);
-    mHovering = false;
 }
 }
 
 
 void CheckBox::onClick(const IntVector2& position, const IntVector2& screenPosition, unsigned buttons)
 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
 class CheckBox : public BorderImage
 {
 {
+    DEFINE_TYPE(CheckBox);
+    
 public:
 public:
     //! Construct with name
     //! Construct with name
     CheckBox(const std::string& name = std::string());
     CheckBox(const std::string& name = std::string());
     //! Destruct
     //! Destruct
     virtual ~CheckBox();
     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
     //! Return UI rendering batches
     virtual void getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads, const IntRect& currentScissor);
     virtual void getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads, const IntRect& currentScissor);
     //! React to mouse click
     //! 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()
 IntVector2 Cursor::getScreenPosition()

+ 4 - 2
Engine/UI/Cursor.h

@@ -29,14 +29,16 @@
 //! An image with hotspot coordinates
 //! An image with hotspot coordinates
 class Cursor : public BorderImage
 class Cursor : public BorderImage
 {
 {
+    DEFINE_TYPE(Cursor);
+    
 public:
 public:
     //! Construct with name
     //! Construct with name
     Cursor(const std::string& name = std::string());
     Cursor(const std::string& name = std::string());
     //! Destruct
     //! Destruct
     virtual ~Cursor();
     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
     //! Return UI element screen position
     virtual IntVector2 getScreenPosition();
     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"));
         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)
 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)
 void Text::getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads, const IntRect& currentScissor)
 {
 {
     if (!mFont)
     if (!mFont)
+    {
+        mHovering = false;
         return;
         return;
+    }
+    
     const FontFace* face = mFont->getFace(mFontSize);
     const FontFace* face = mFont->getFace(mFontSize);
     
     
     UIBatch batch;
     UIBatch batch;
@@ -142,6 +163,9 @@ void Text::getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads,
     }
     }
     
     
     UIBatch::addOrMerge(batch, batches);
     UIBatch::addOrMerge(batch, batches);
+    
+    // Reset hovering for next frame
+    mHovering = false;
 }
 }
 
 
 int Text::getRowHeight() const
 int Text::getRowHeight() const

+ 4 - 2
Engine/UI/Text.h

@@ -33,14 +33,16 @@ class Font;
 //! An UI element that displays text
 //! An UI element that displays text
 class Text : public UIElement
 class Text : public UIElement
 {
 {
+    DEFINE_TYPE(Text);
+    
 public:
 public:
     //! Construct with initial text and name
     //! Construct with initial text and name
     Text(const std::string& text = std::string(), const std::string& name = std::string());
     Text(const std::string& text = std::string(), const std::string& name = std::string());
     //! Destruct
     //! Destruct
     virtual ~Text();
     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
     //! Return UI rendering batches
     virtual void getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads, const IntRect& currentScissor);
     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 "Precompiled.h"
+#include "BaseUIElementFactory.h"
 #include "Cursor.h"
 #include "Cursor.h"
 #include "Font.h"
 #include "Font.h"
 #include "InputEvents.h"
 #include "InputEvents.h"
@@ -33,6 +34,7 @@
 #include "RendererEvents.h"
 #include "RendererEvents.h"
 #include "RendererImpl.h"
 #include "RendererImpl.h"
 #include "ResourceCache.h"
 #include "ResourceCache.h"
+#include "StringUtils.h"
 #include "Texture2D.h"
 #include "Texture2D.h"
 #include "UI.h"
 #include "UI.h"
 #include "VertexShader.h"
 #include "VertexShader.h"
@@ -74,6 +76,9 @@ UI::UI(Renderer* renderer, ResourceCache* cache) :
     mNoTexturePS = mCache->getResource<PixelShader>("Shaders/SM2/Basic_VCol.ps2");
     mNoTexturePS = mCache->getResource<PixelShader>("Shaders/SM2/Basic_VCol.ps2");
     mDiffTexturePS = mCache->getResource<PixelShader>("Shaders/SM2/Basic_DiffVCol.ps2");
     mDiffTexturePS = mCache->getResource<PixelShader>("Shaders/SM2/Basic_DiffVCol.ps2");
     mAlphaTexturePS = mCache->getResource<PixelShader>("Shaders/SM2/Basic_AlphaVCol.ps2");
     mAlphaTexturePS = mCache->getResource<PixelShader>("Shaders/SM2/Basic_AlphaVCol.ps2");
+    
+    // Add the base element factory
+    addElementFactory(new BaseUIElementFactory());
 }
 }
 
 
 UI::~UI()
 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* UI::getElementAt(const IntVector2& position, bool enabledOnly)
 {
 {
     UIElement* result = 0;
     UIElement* result = 0;
@@ -441,3 +506,22 @@ void UI::handleChar(StringHash eventType, VariantMap& eventData)
     if (element)
     if (element)
         element->onChar(eventData[P_CHAR].getInt());
         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 Cursor;
 class Renderer;
 class Renderer;
 class ResourceCache;
 class ResourceCache;
-class UIElement;
 class UIBatch;
 class UIBatch;
+class UIElement;
+class UIElementFactory;
 
 
 //! Manages the graphical user interface
 //! Manages the graphical user interface
 class UI : public RefCounted, public EventListener
 class UI : public RefCounted, public EventListener
@@ -53,6 +54,12 @@ public:
     void update(float timeStep);
     void update(float timeStep);
     //! Render the UI
     //! Render the UI
     void render();
     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
     //! Return root UI elemenet
     UIElement* getRootElement() const { return mRootElement; }
     UIElement* getRootElement() const { return mRootElement; }
@@ -66,6 +73,8 @@ public:
     UIElement* getFocusElement();
     UIElement* getFocusElement();
     //! Return cursor position
     //! Return cursor position
     IntVector2 getCursorPosition();
     IntVector2 getCursorPosition();
+    //! Return UI element factories
+    const std::vector<SharedPtr<UIElementFactory> >& getElementFactories() const { return mFactories; }
     
     
 private:
 private:
     //! Update UI elements and generate batches for UI rendering
     //! Update UI elements and generate batches for UI rendering
@@ -88,6 +97,8 @@ private:
     void handleMouseButtonUp(StringHash eventType, VariantMap& eventData);
     void handleMouseButtonUp(StringHash eventType, VariantMap& eventData);
     //! Handle character event
     //! Handle character event
     void handleChar(StringHash eventType, VariantMap& eventData);
     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
     //! Renderer
     SharedPtr<Renderer> mRenderer;
     SharedPtr<Renderer> mRenderer;
@@ -107,6 +118,8 @@ private:
     SharedPtr<UIElement> mRootElement;
     SharedPtr<UIElement> mRootElement;
     //! Cursor
     //! Cursor
     SharedPtr<Cursor> mCursor;
     SharedPtr<Cursor> mCursor;
+    //! UI element factories
+    std::vector<SharedPtr<UIElementFactory> > mFactories;
     //! UI rendering batches
     //! UI rendering batches
     std::vector<UIBatch> mBatches;
     std::vector<UIBatch> mBatches;
     //! UI rendering quads
     //! UI rendering quads

+ 80 - 43
Engine/UI/UIElement.cpp

@@ -37,6 +37,7 @@ UIElement::UIElement(const std::string& name) :
     mSize(IntVector2::sZero),
     mSize(IntVector2::sZero),
     mHorizontalAlignment(HA_LEFT),
     mHorizontalAlignment(HA_LEFT),
     mVerticalAlignment(VA_TOP),
     mVerticalAlignment(VA_TOP),
+    mChildOffset(IntVector2::sZero),
     mHoverColor(Color(0.0f, 0.0f, 0.0f)),
     mHoverColor(Color(0.0f, 0.0f, 0.0f)),
     mPriority(0),
     mPriority(0),
     mOpacity(1.0f),
     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)
     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 horiz;
         std::string vert;
         std::string vert;
@@ -119,13 +105,13 @@ XMLElement UIElement::loadParameters(XMLFile* file, const std::string& elementNa
         if (vert == "bottom")
         if (vert == "bottom")
             setVerticalAlignment(VA_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"))
         if (colorElem.hasAttribute("value"))
             setColor(colorElem.getColor("value"));
             setColor(colorElem.getColor("value"));
         if (colorElem.hasAttribute("topleft"))
         if (colorElem.hasAttribute("topleft"))
@@ -137,20 +123,18 @@ XMLElement UIElement::loadParameters(XMLFile* file, const std::string& elementNa
         if (colorElem.hasAttribute("bottomright"))
         if (colorElem.hasAttribute("bottomright"))
             setColor(C_BOTTOMRIGHT, colorElem.getColor("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)
 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)
 void UIElement::getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads, const IntRect& currentScissor)
 {
 {
+    // Reset hovering for next frame
     mHovering = false;
     mHovering = false;
 }
 }
 
 
@@ -201,6 +186,8 @@ IntVector2 UIElement::getScreenPosition()
                 break;
                 break;
             }
             }
             
             
+            pos += parent->mChildOffset;
+            
             current = parent;
             current = parent;
             parent = parent->mParent;
             parent = parent->mParent;
         }
         }
@@ -397,6 +384,12 @@ void UIElement::setVisible(bool enable)
     mVisible = enable;
     mVisible = enable;
 }
 }
 
 
+void UIElement::setStyleAuto(XMLFile* file, ResourceCache* cache)
+{
+    XMLElement element = getStyleElement(file);
+    setStyle(element, cache);
+}
+
 void UIElement::addChild(UIElement* element)
 void UIElement::addChild(UIElement* element)
 {
 {
     if ((!element) || (element->mParent == this) || (mParent == element))
     if ((!element) || (element->mParent == this) || (mParent == element))
@@ -495,6 +488,23 @@ UIElement* UIElement::getChild(const std::string& name, bool recursive) const
     return 0;
     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)
 IntVector2 UIElement::screenToElement(const IntVector2& screenPosition)
 {
 {
     return screenPosition - getScreenPosition();
     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()
 void UIElement::markDirty()
 {
 {
     if ((mScreenPositionDirty) && (mDerivedOpacityDirty))
     if ((mScreenPositionDirty) && (mDerivedOpacityDirty))
@@ -529,6 +556,16 @@ void UIElement::markDirty()
         (*i)->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
 void UIElement::getChildrenRecursive(std::vector<UIElement*>& dest) const
 {
 {
     for (std::vector<SharedPtr<UIElement> >::const_iterator i = mChildren.begin(); i != mChildren.end(); ++i)
     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
 #define UI_UIELEMENT_H
 
 
 #include "EventListener.h"
 #include "EventListener.h"
+#include "HashedType.h"
 #include "SharedPtr.h"
 #include "SharedPtr.h"
 #include "UIBatch.h"
 #include "UIBatch.h"
 #include "Vector2.h"
 #include "Vector2.h"
@@ -60,16 +61,18 @@ static const unsigned NUM_UIELEMENT_CORNERS = 4;
 class ResourceCache;
 class ResourceCache;
 
 
 //! Base class for UI elements
 //! Base class for UI elements
-class UIElement : public RefCounted, public EventListener
+class UIElement : public HashedType, public EventListener
 {
 {
+    DEFINE_TYPE(UIElement);
+    
 public:
 public:
     //! Construct with name
     //! Construct with name
     UIElement(const std::string& name = std::string());
     UIElement(const std::string& name = std::string());
     //! Destruct with name
     //! Destruct with name
     virtual ~UIElement();
     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
     //! Perform UI element update
     virtual void update(float timeStep);
     virtual void update(float timeStep);
     //! Return UI rendering batches
     //! Return UI rendering batches
@@ -116,7 +119,7 @@ public:
     void setColor(const Color& color);
     void setColor(const Color& color);
     //! Set color on one corner
     //! Set color on one corner
     void setColor(UIElementCorner corner, const Color& color);
     void setColor(UIElementCorner corner, const Color& color);
-    //! Set hover color modification
+    //! Set color modification used on hover
     void setHoverColor(const Color& color);
     void setHoverColor(const Color& color);
     //! Set priority
     //! Set priority
     void setPriority(int priority);
     void setPriority(int priority);
@@ -134,6 +137,8 @@ public:
     void setFocus(bool enable);
     void setFocus(bool enable);
     //! Set whether is visible
     //! Set whether is visible
     void setVisible(bool enable);
     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
     //! Add a child element
     void addChild(UIElement* element);
     void addChild(UIElement* element);
     //! Remove a child element
     //! Remove a child element
@@ -151,13 +156,15 @@ public:
     int getWidth() const { return mSize.mX; }
     int getWidth() const { return mSize.mX; }
     //! Return height
     //! Return height
     int getHeight() const { return mSize.mY; }
     int getHeight() const { return mSize.mY; }
+    //! Return child element offset
+    const IntVector2& getChildOffset() const { return mChildOffset; }
     //! Return horizontal alignment
     //! Return horizontal alignment
     HorizontalAlignment getHorizontalAlignment() const { return mHorizontalAlignment; }
     HorizontalAlignment getHorizontalAlignment() const { return mHorizontalAlignment; }
     //! Return vertical alignment
     //! Return vertical alignment
     VerticalAlignment getVerticalAlignment() const { return mVerticalAlignment; }
     VerticalAlignment getVerticalAlignment() const { return mVerticalAlignment; }
     //! Return corner color
     //! Return corner color
     const Color& getColor(UIElementCorner corner) const { return mColor[corner]; }
     const Color& getColor(UIElementCorner corner) const { return mColor[corner]; }
-    //! Return hover color modification
+    //! Return color modification used on hover
     const Color& getHoverColor() { return mHoverColor; }
     const Color& getHoverColor() { return mHoverColor; }
     //! Return priority
     //! Return priority
     int getPriority() const { return mPriority; }
     int getPriority() const { return mPriority; }
@@ -189,6 +196,8 @@ public:
     std::vector<UIElement*> getChildren(bool recursive = false) const;
     std::vector<UIElement*> getChildren(bool recursive = false) const;
     //! Return parent element
     //! Return parent element
     UIElement* getParent() const { return mParent; }
     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
     //! Convert screen coordinates to element coordinates
     IntVector2 screenToElement(const IntVector2& screenPosition);
     IntVector2 screenToElement(const IntVector2& screenPosition);
@@ -198,9 +207,14 @@ public:
     //! Adjust scissor for rendering
     //! Adjust scissor for rendering
     void adjustScissor(IntRect& currentScissor);
     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:
 protected:
     //! Mark screen position as needing an update
     //! Mark screen position as needing an update
     void markDirty();
     void markDirty();
+    //! Set child offset
+    void setChildOffset(const IntVector2& offset);
     
     
     //! Name
     //! Name
     std::string mName;
     std::string mName;
@@ -210,7 +224,7 @@ protected:
     UIElement* mParent;
     UIElement* mParent;
     //! Colors
     //! Colors
     Color mColor[NUM_UIELEMENT_CORNERS];
     Color mColor[NUM_UIELEMENT_CORNERS];
-    //! Hover color modification
+    //! Color modification on hover
     Color mHoverColor;
     Color mHoverColor;
     //! Priority
     //! Priority
     int mPriority;
     int mPriority;
@@ -239,6 +253,8 @@ private:
     IntVector2 mScreenPosition;
     IntVector2 mScreenPosition;
     //! Size
     //! Size
     IntVector2 mSize;
     IntVector2 mSize;
+    //! Child elements' offset. Used internally
+    IntVector2 mChildOffset;
     //! Horizontal alignment
     //! Horizontal alignment
     HorizontalAlignment mHorizontalAlignment;
     HorizontalAlignment mHorizontalAlignment;
     //! Vertical alignment
     //! 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)
 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
 //! Window that can optionally by moved or resized
 class Window : public BorderImage
 class Window : public BorderImage
 {
 {
+    DEFINE_TYPE(Window);
+    
 public:
 public:
     //! Construct with name
     //! Construct with name
     Window(const std::string& name = std::string());
     Window(const std::string& name = std::string());
     //! Destruct
     //! Destruct
     virtual ~Window();
     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
     //! React to mouse drag start
     virtual void onDragStart(const IntVector2& position, const IntVector2& screenPosition, unsigned buttons);
     virtual void onDragStart(const IntVector2& position, const IntVector2& screenPosition, unsigned buttons);
     //! React to mouse drag motion
     //! React to mouse drag motion

+ 4 - 20
Examples/Test/Application.cpp

@@ -28,8 +28,6 @@
 #include "Audio.h"
 #include "Audio.h"
 #include "Application.h"
 #include "Application.h"
 #include "BillboardSet.h"
 #include "BillboardSet.h"
-#include "Button.h"
-#include "CheckBox.h"
 #include "CollisionShape.h"
 #include "CollisionShape.h"
 #include "Cursor.h"
 #include "Cursor.h"
 #include "CustomObject.h"
 #include "CustomObject.h"
@@ -199,29 +197,15 @@ void Application::init()
     UI* ui = mEngine->getUI();
     UI* ui = mEngine->getUI();
     UIElement* uiRoot = ui->getRootElement();
     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* cursor = new Cursor("Cursor");
-    cursor->loadParameters(uiSetup, "Cursor", mCache);
+    cursor->setStyleAuto(uiStyle, mCache);
     cursor->setPosition(renderer->getWidth() / 2, renderer->getHeight() / 2);
     cursor->setPosition(renderer->getWidth() / 2, renderer->getHeight() / 2);
     ui->setCursor(cursor);
     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();
     mScene = mEngine->createScene();
     PhysicsWorld* world = mScene->getExtension<PhysicsWorld>();
     PhysicsWorld* world = mScene->getExtension<PhysicsWorld>();