Browse Source

Added auto-adjusting layout support to UIElement.
Fixed event sending order.
Optimized UI drawing (less batches if there are child elements with same priority and renderstate.)
Adjusted subsystem creation/destruction order. Delete renderer last to avoid being unable to free GPU resources at exit.
Proper fix for BillboardSet zero size animation LOD bug.
Code cleanup.

Lasse Öörni 15 years ago
parent
commit
fe4ccf7c58

+ 2 - 1
Bin/Data/UI/TestLayout.xml

@@ -42,9 +42,10 @@
             <verticalslider name="ViewSliderV" />
             <element type="Text">
                 <position value="1 1" />
+                <size value="200 150" />
                 <font name="times.ttf" size="15" />
                 <text value="Urho3D - a Win32/Direct3D9 rendering and game engine\nhttp://urho3d.googlecode.com\nLicensed under the MIT license, see License.txt for details." />
-                <maxwidth value="200" />
+                <wordwrap enable="true" />
             </element>
         </element>
         <element type="LineEdit">

+ 27 - 0
Bin/Data/UI/TestLayout2.xml

@@ -0,0 +1,27 @@
+<layout>
+    <element type="Window">
+        <position value="50 50" />
+        <size value="400 300" />
+        <resizable enable="true" />
+        <layout orientation="vertical" horizontal="resizechildren" vertical="resizechildren" border="4 4 4 4" spacing="2" />
+        <clipborder value="4 4 4 4" />
+        <element type="Element">
+            <clipchildren enable="true" />
+            <layout orientation="vertical" horizontal="resizechildren" vertical="resizechildren" />
+            <element type="Text">
+                <font name="courier.ttf" size="15" />
+                <text value="At some point the fire had started.\n  There was total chaos. The locks of the wooden barracks had been blown apart by gunfire from shotguns and assault rifles, and the trainees were pouring out in confusion. But the primary target still lay ahead.\n  The administrative building. Where the identities and evaluations of the trainees were stored.\n  That was first class knowledge. If the Agents could bring that knowledge to the public, and cross-reference the records - mostly boys of young age - with lists of known missing persons, then they could prove the existence of SCEPTRE." />
+                <wordwrap enable="true" />
+            </element>
+        </element>
+        <element type="Button">
+            <fixedsize value="100 30" />
+            <alignment horizontal="center" />
+            <element type="Text">
+                <text value="TEST" />
+                <font name="cour.ttf" size="12" />
+                <alignment horizontal="center" vertical="center" />
+            </element>
+        </element>
+    </element>
+</layout>

+ 82 - 0
Bin/Data/UI/TestLayout3.xml

@@ -0,0 +1,82 @@
+<layout>
+    <element type="Window">
+        <position value="50 50" />
+        <size value="400 300" />
+        <resizable enable="true" />
+        <layout orientation="vertical" horizontal="resizechildren" vertical="resizechildren" border="8 8 8 8" spacing="8" />
+        <clipborder value="8 8 8 8" />
+        <minsize value="60 60" />
+        <element type="Element">
+            <layout orientation="horizontal" horizontal="resizechildren" vertical="resizechildren" spacing="8" />
+            <element type="Button">
+                <element type="Text">
+                    <text value="TEST1" />
+                    <font name="cour.ttf" size="12" />
+                    <alignment horizontal="center" vertical="center" />
+                </element>
+            </element>
+            <element type="Button">
+                <element type="Text">
+                    <text value="TEST2" />
+                    <font name="cour.ttf" size="12" />
+                    <alignment horizontal="center" vertical="center" />
+                </element>
+            </element>
+            <element type="Button">
+                <element type="Text">
+                    <text value="TEST3" />
+                    <font name="cour.ttf" size="12" />
+                    <alignment horizontal="center" vertical="center" />
+                </element>
+            </element>
+        </element>
+        <element type="Element">
+            <layout orientation="horizontal" horizontal="resizechildren" vertical="resizechildren" spacing="8" />
+            <element type="Button">
+                <element type="Text">
+                    <text value="TEST4" />
+                    <font name="cour.ttf" size="12" />
+                    <alignment horizontal="center" vertical="center" />
+                </element>
+            </element>
+            <element type="Button">
+                <element type="Text">
+                    <text value="TEST5" />
+                    <font name="cour.ttf" size="12" />
+                    <alignment horizontal="center" vertical="center" />
+                </element>
+            </element>
+            <element type="Button">
+                <element type="Text">
+                    <text value="TEST6" />
+                    <font name="cour.ttf" size="12" />
+                    <alignment horizontal="center" vertical="center" />
+                </element>
+            </element>
+        </element>
+        <element type="Element">
+            <layout orientation="horizontal" horizontal="resizechildren" vertical="resizechildren" spacing="8" />
+            <element type="Button">
+                <element type="Text">
+                    <text value="TEST6" />
+                    <font name="cour.ttf" size="12" />
+                    <alignment horizontal="center" vertical="center" />
+                </element>
+            </element>
+            <element type="Button">
+                <element type="Text">
+                    <text value="TEST7" />
+                    <font name="cour.ttf" size="12" />
+                    <alignment horizontal="center" vertical="center" />
+                </element>
+            </element>
+            <element type="Button">
+                <element type="Text">
+                    <text value="TEST8" />
+                    <font name="cour.ttf" size="12" />
+                    <alignment horizontal="center" vertical="center" />
+                </element>
+            </element>
+        </element>
+    </element>
+</layout>

+ 50 - 36
Engine/Common/EventListener.cpp

@@ -33,8 +33,8 @@
 
 static VariantMap noEventData;
 
-std::map<StringHash, std::vector<EventListener*> > EventListener::sEventListeners;
-std::map<std::pair<EventListener*, StringHash>, std::vector<EventListener*> > EventListener::sSpecificEventListeners;
+std::map<StringHash, std::list<EventListener*> > EventListener::sEventListeners;
+std::map<std::pair<EventListener*, StringHash>, std::list<EventListener*> > EventListener::sSpecificEventListeners;
 
 EventListener* eventSender = 0;
 
@@ -47,15 +47,14 @@ EventListener::~EventListener()
     unsubscribeFromAllEvents();
     
     // Remove from all specific event listeners
-    for (std::map<std::pair<EventListener*, StringHash>, std::vector<EventListener*> >::iterator i = sSpecificEventListeners.begin();
+    for (std::map<std::pair<EventListener*, StringHash>, std::list<EventListener*> >::iterator i = sSpecificEventListeners.begin();
         i != sSpecificEventListeners.end();)
     {
-        std::map<std::pair<EventListener*, StringHash>, std::vector<EventListener*> >::iterator current = i;
-        ++i;
+        std::map<std::pair<EventListener*, StringHash>, std::list<EventListener*> >::iterator current = i++;
         if (current->first.first == this)
         {
-            std::vector<EventListener*>& listeners = current->second;
-            for (std::vector<EventListener*>::iterator j = listeners.begin(); j != listeners.end(); ++j)
+            std::list<EventListener*>& listeners = current->second;
+            for (std::list<EventListener*>::iterator j = listeners.begin(); j != listeners.end(); ++j)
                 (*j)->removeSpecificEventHandlers(this);
             sSpecificEventListeners.erase(current);
         }
@@ -93,8 +92,8 @@ void EventListener::subscribeToEvent(StringHash eventType, EventHandlerInvoker*
     
     mEventHandlers[eventType] = handler;
     
-    std::vector<EventListener*>& listeners = sEventListeners[eventType];
-    for (std::vector<EventListener*>::const_iterator j = listeners.begin(); j != listeners.end(); ++j)
+    std::list<EventListener*>& listeners = sEventListeners[eventType];
+    for (std::list<EventListener*>::const_iterator j = listeners.begin(); j != listeners.end(); ++j)
     {
         // Check if already registered
         if (*j == this)
@@ -127,8 +126,8 @@ void EventListener::subscribeToEvent(EventListener* sender, StringHash eventType
     
     mSpecificEventHandlers[combination] = handler;
     
-    std::vector<EventListener*>& listeners = sSpecificEventListeners[combination];
-    for (std::vector<EventListener*>::const_iterator j = listeners.begin(); j != listeners.end(); ++j)
+    std::list<EventListener*>& listeners = sSpecificEventListeners[combination];
+    for (std::list<EventListener*>::const_iterator j = listeners.begin(); j != listeners.end(); ++j)
     {
         // Check if already registered
         if (*j == this)
@@ -144,8 +143,7 @@ void EventListener::unsubscribeFromEvent(StringHash eventType)
     for (std::map<std::pair<EventListener*, StringHash>, EventHandlerInvoker*>::iterator i = mSpecificEventHandlers.begin();
         i != mSpecificEventHandlers.end();)
     {
-        std::map<std::pair<EventListener*, StringHash>, EventHandlerInvoker*>::iterator current = i;
-        ++i;
+        std::map<std::pair<EventListener*, StringHash>, EventHandlerInvoker*>::iterator current = i++;
         if (current->first.second == eventType)
         {
             removeEventListener(current->first.first, current->first.second);
@@ -182,8 +180,7 @@ void EventListener::unsubscribeFromEvents(EventListener* sender)
     for (std::map<std::pair<EventListener*, StringHash>, EventHandlerInvoker*>::iterator i = mSpecificEventHandlers.begin();
         i != mSpecificEventHandlers.end();)
     {
-        std::map<std::pair<EventListener*, StringHash>, EventHandlerInvoker*>::iterator current = i;
-        ++i;
+        std::map<std::pair<EventListener*, StringHash>, EventHandlerInvoker*>::iterator current = i++;
         if (current->first.first == sender)
         {
             removeEventListener(current->first.first, current->first.second);
@@ -220,44 +217,54 @@ void EventListener::sendEvent(StringHash eventType)
 
 void EventListener::sendEvent(StringHash eventType, VariantMap& eventData)
 {
-    eventSender = this;
-    
     std::set<EventListener*> processed;
     
     // Check first the specific event listeners
-    std::map<std::pair<EventListener*, StringHash>, std::vector<EventListener*> >::const_iterator i = sSpecificEventListeners.find(
+    std::map<std::pair<EventListener*, StringHash>, std::list<EventListener*> >::const_iterator i = sSpecificEventListeners.find(
         std::make_pair(this, eventType));
     if (i != sSpecificEventListeners.end())
     {
-        const std::vector<EventListener*>& listeners = i->second;
-        // Iterate in reverse direction in case listeners remove themselves as a response
-        for (unsigned j = listeners.size() - 1; j < listeners.size(); --j)
+        const std::list<EventListener*>& listeners = i->second;
+        for (std::list<EventListener*>::const_iterator j = listeners.begin(); j != listeners.end();)
         {
-            listeners[j]->onEvent(this, eventType, eventData);
-            processed.insert(listeners[j]);
+            // Make a copy of the iterator, because the listener might remove itself as a response
+            std::list<EventListener*>::const_iterator current = j++;
+            processed.insert(*current);
+            eventSender = this;
+            (*current)->onEvent(this, eventType, eventData);
         }
     }
     
     // Then the non-specific listeners
-    std::map<StringHash, std::vector<EventListener*> >::const_iterator j = sEventListeners.find(eventType);
+    std::map<StringHash, std::list<EventListener*> >::const_iterator j = sEventListeners.find(eventType);
     if (j == sEventListeners.end())
     {
         eventSender = 0;
         return;
     }
-    const std::vector<EventListener*>& listeners = j->second;
+    const std::list<EventListener*>& listeners = j->second;
     if (processed.empty())
     {
-        for (unsigned k = listeners.size() - 1; k < listeners.size(); --k)
-            listeners[k]->onEvent(this, eventType, eventData);
+        for (std::list<EventListener*>::const_iterator k = listeners.begin(); k != listeners.end();)
+        {
+            std::list<EventListener*>::const_iterator current = k++;
+            eventSender = this;
+            (*current)->onEvent(this, eventType, eventData);
+        }
     }
     else
     {
         // If there were specific listeners, check that the event is not sent doubly to them
-        for (unsigned k = listeners.size() - 1; k < listeners.size(); --k)
+        for (std::list<EventListener*>::const_iterator k = listeners.begin(); k != listeners.end();)
         {
-            if (processed.find(listeners[k]) == processed.end())
-                listeners[k]->onEvent(this, eventType, eventData);
+            if (processed.find(*k) == processed.end())
+            {
+                std::list<EventListener*>::const_iterator current = k++;
+                eventSender = this;
+                (*current)->onEvent(this, eventType, eventData);
+            }
+            else
+                ++k;
         }
     }
     
@@ -267,13 +274,21 @@ void EventListener::sendEvent(StringHash eventType, VariantMap& eventData)
 void EventListener::sendEvent(EventListener* receiver, StringHash eventType)
 {
     if (receiver)
+    {
+        eventSender = this;
         receiver->onEvent(this, eventType, noEventData);
+        eventSender = 0;
+    }
 }
 
 void EventListener::sendEvent(EventListener* receiver, StringHash eventType, VariantMap& eventData)
 {
     if (receiver)
+    {
+        eventSender = this;
         receiver->onEvent(this, eventType, eventData);
+        eventSender = 0;
+    }
 }
 
 bool EventListener::hasSubscribedToEvent(StringHash eventType) const
@@ -291,8 +306,7 @@ void EventListener::removeSpecificEventHandlers(EventListener* sender)
     for (std::map<std::pair<EventListener*, StringHash>, EventHandlerInvoker*>::iterator i = mSpecificEventHandlers.begin();
         i != mSpecificEventHandlers.end();)
     {
-        std::map<std::pair<EventListener*, StringHash>, EventHandlerInvoker*>::iterator current = i;
-        ++i;
+        std::map<std::pair<EventListener*, StringHash>, EventHandlerInvoker*>::iterator current = i++;
         if (current->first.first == sender)
         {
             delete current->second;
@@ -303,8 +317,8 @@ void EventListener::removeSpecificEventHandlers(EventListener* sender)
 
 void EventListener::removeEventListener(StringHash eventType)
 {
-    std::vector<EventListener*>& listeners = sEventListeners[eventType];
-    for (std::vector<EventListener*>::iterator i = listeners.begin(); i != listeners.end(); ++i)
+    std::list<EventListener*>& listeners = sEventListeners[eventType];
+    for (std::list<EventListener*>::iterator i = listeners.begin(); i != listeners.end(); ++i)
     {
         if (*i == this)
         {
@@ -316,8 +330,8 @@ void EventListener::removeEventListener(StringHash eventType)
 
 void EventListener::removeEventListener(EventListener* sender, StringHash eventType)
 {
-    std::vector<EventListener*>& listeners = sSpecificEventListeners[std::make_pair(sender, eventType)];
-    for (std::vector<EventListener*>::iterator i = listeners.begin(); i != listeners.end(); ++i)
+    std::list<EventListener*>& listeners = sSpecificEventListeners[std::make_pair(sender, eventType)];
+    for (std::list<EventListener*>::iterator i = listeners.begin(); i != listeners.end(); ++i)
     {
         if (*i == this)
         {

+ 4 - 5
Engine/Common/EventListener.h

@@ -26,6 +26,8 @@
 
 #include "Event.h"
 
+#include <list>
+
 class EventListener;
 
 //! Internal helper class for invoking event handler functions
@@ -141,9 +143,6 @@ protected:
     //! Event handlers for specific senders' events
     std::map<std::pair<EventListener*, StringHash>, EventHandlerInvoker*> mSpecificEventHandlers;
     
-    //! Event sender. Only non-null during event handling
-    static EventListener* sSender;
-    
 private:
     //! Prevent copy construction
     EventListener(const EventListener& rhs);
@@ -151,9 +150,9 @@ private:
     EventListener& operator = (const EventListener& rhs);
     
     //! Event listeners for non-specific events
-    static std::map<StringHash, std::vector<EventListener*> > sEventListeners;
+    static std::map<StringHash, std::list<EventListener*> > sEventListeners;
     //! Event listeners for specific senders' events
-    static std::map<std::pair<EventListener*, StringHash>, std::vector<EventListener*> > sSpecificEventListeners;
+    static std::map<std::pair<EventListener*, StringHash>, std::list<EventListener*> > sSpecificEventListeners;
 };
 
 //! Return event sender. Only non-null during the event handling

+ 3 - 8
Engine/Engine/Console.cpp

@@ -63,6 +63,7 @@ Console::Console(Engine* engine) :
         mBackground->setEnabled(true);
         mBackground->setVisible(false);
         mBackground->setPriority(200); // Show on top of the debug HUD
+        mBackground->setLayout(O_VERTICAL, LM_RESIZECHILDREN, LM_RESIZEELEMENT, 0, IntRect(4, 4, 4, 4));
         
         mLineEdit = new LineEdit();
         mLineEdit->setColor(Color(0.0f, 0.0f, 0.0f, 0.5f));
@@ -130,10 +131,8 @@ void Console::setNumRows(unsigned rows)
     if (!mBackground)
         return;
     
-    for (unsigned i = 0; i < mRows.size(); ++i)
-        mBackground->removeChild(mRows[i]);
+    mBackground->removeAllChildren();
     mRows.clear();
-    mBackground->removeChild(mLineEdit);
     
     mRows.resize(rows);
     for (unsigned i = 0; i < mRows.size(); ++i)
@@ -175,12 +174,8 @@ void Console::updateElements()
         mLineEdit->getTextElement()->setFont(mFont, mFontSize);
     }
     
-    mLineEdit->setWidth(width - 8);
     mLineEdit->setHeight(mLineEdit->getTextElement()->getRowHeight());
-    mBackground->layoutVertical(0, IntRect(4, 4, 4, 4), true, true);
-    
-    if (mBackground->getWidth() > width)
-        mBackground->setWidth(width);
+    mBackground->setWidth(width);
 }
 
 void Console::handleTextFinished(StringHash eventType, VariantMap& eventData)

+ 3 - 3
Engine/Engine/Engine.cpp

@@ -223,8 +223,6 @@ void Engine::init(const std::vector<std::string>& arguments)
     
     mInput = new Input(mRenderer);
     
-    mNetwork = new Network();
-    
     // If no resource paths or packages already defined, add the default resource path
     if ((mCache->getPackageFiles().empty()) && (mCache->getResourcePaths().empty()))
         mCache->addResourcePath("Data");
@@ -237,13 +235,15 @@ void Engine::init(const std::vector<std::string>& arguments)
     
     if (!mHeadless)
     {
-        mDebugRenderer = new DebugRenderer(mRenderer, mCache);
         mPipeline = new Pipeline(mRenderer, mCache);
+        mDebugRenderer = new DebugRenderer(mRenderer, mCache);
         mUI = new UI(mRenderer, mCache);
         if (!shadows)
             mPipeline->setDrawShadows(false);
     }
     
+    mNetwork = new Network();
+    
     mInitialized = true;
 }
 

+ 12 - 12
Engine/Engine/Engine.h

@@ -177,32 +177,32 @@ private:
     SharedPtr<Log> mLog;
     //! Profiler
     SharedPtr<Profiler> mProfiler;
+    //! Renderer subsystem
+    SharedPtr<Renderer> mRenderer;
     //! Audio subsystem
     SharedPtr<Audio> mAudio;
+    //! Input subsystem
+    SharedPtr<Input> mInput;
     //! Resource cache
     SharedPtr<ResourceCache> mCache;
-    //! Console
-    SharedPtr<Console> mConsole;
-    //! Debug hud
-    SharedPtr<DebugHud> mDebugHud;
+    //! High-level rendering pipeline
+    SharedPtr<Pipeline> mPipeline;
     //! Debug renderer
     SharedPtr<DebugRenderer> mDebugRenderer;
-    //! Input subsystem
-    SharedPtr<Input> mInput;
+    //! UI subsystem
+    SharedPtr<UI> mUI;
     //! Network subsystem
     SharedPtr<Network> mNetwork;
     //! Client subsystem
     SharedPtr<Client> mClient;
     //! Server subsystem
     SharedPtr<Server> mServer;
-    //! High-level rendering pipeline
-    SharedPtr<Pipeline> mPipeline;
-    //! Renderer subsystem
-    SharedPtr<Renderer> mRenderer;
+    //! Console
+    SharedPtr<Console> mConsole;
+    //! Debug hud
+    SharedPtr<DebugHud> mDebugHud;
     //! Script engine
     SharedPtr<ScriptEngine> mScriptEngine;
-    //! UI subsystem
-    SharedPtr<UI> mUI;
     //! Default scene
     WeakPtr<Scene> mDefaultScene;
     //! Window title

+ 13 - 4
Engine/Engine/RegisterTemplates.h

@@ -423,13 +423,17 @@ template <class T> void registerUIElement(asIScriptEngine* engine, const char* c
     engine->RegisterObjectMethod(className, "void setMaxSize(int, int)", asMETHODPR(T, setMaxSize, (int, int), void), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setMaxWidth(int)", asMETHOD(T, setMaxWidth), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setMaxHeight(int)", asMETHOD(T, setMaxHeight), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "void setFixedSize(const IntVector2& in)", asMETHODPR(T, setFixedSize, (const IntVector2&), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "void setFixedSize(int, int)", asMETHODPR(T, setFixedSize, (int, int), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "void setFixedWidth(int)", asMETHOD(T, setFixedWidth), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "void setFixedHeight(int)", asMETHOD(T, setFixedHeight), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setAlignment(HorizontalAlignment, VerticalAlignment)", asMETHOD(T, setAlignment), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setHorizontalAlignment(HorizontalAlignment)", asMETHOD(T, setHorizontalAlignment), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setVerticalAlignment(VerticalAlignment)", asMETHOD(T, setVerticalAlignment), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setClipBorder(const IntRect& in)", asMETHODPR(T, setClipBorder, (const IntRect&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setClipBorder(int, int, int, int)", asMETHODPR(T, setClipBorder, (int, int, int, int), void), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setColor(const Color& in)", asMETHODPR(T, setColor, (const Color&), void), asCALL_THISCALL);
-    engine->RegisterObjectMethod(className, "void setColor(UIElementCorner, const Color& in)", asMETHODPR(T, setColor, (UIElementCorner, const Color&), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "void setColor(Corner, const Color& in)", asMETHODPR(T, setColor, (Corner, const Color&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setPriority(int)", asMETHOD(T, setPriority), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setOpacity(float)", asMETHOD(T, setOpacity), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setBringToFront(bool)", asMETHOD(T, setBringToFront), asCALL_THISCALL);
@@ -443,12 +447,12 @@ template <class T> void registerUIElement(asIScriptEngine* engine, const char* c
     engine->RegisterObjectMethod(className, "void setVisible(bool)", asMETHOD(T, setVisible), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setUserData(const Variant& in)", asMETHOD(T, setUserData), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setStyleAuto(XMLFile@+)", asFUNCTION(UIElementSetStyleAuto<T>), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod(className, "void setLayout(Orientation, LayoutMode, LayoutMode, int, const IntRect&)", asMETHOD(T, setLayout), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "void updateLayout()", asMETHOD(T, updateLayout), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void bringToFront()", asMETHOD(T, bringToFront), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void addChild(UIElement@+)", asMETHOD(T, addChild), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void removeChild(UIElement@+)", asMETHOD(T, removeChild), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void removeAllChildren()", asMETHOD(T, removeAllChildren), asCALL_THISCALL);
-    engine->RegisterObjectMethod(className, "void layoutHorizontal(int, const IntRect& in, bool, bool)", asMETHOD(T, layoutHorizontal), asCALL_THISCALL);
-    engine->RegisterObjectMethod(className, "void layoutVertical(int, const IntRect& in, bool, bool)", asMETHOD(T, layoutVertical), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "const string& getName() const", asMETHOD(T, getName), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "const IntVector2& getPosition() const", asMETHOD(T, getPosition), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "const IntVector2& getSize() const", asMETHOD(T, getSize), asCALL_THISCALL);
@@ -464,7 +468,7 @@ template <class T> void registerUIElement(asIScriptEngine* engine, const char* c
     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 IntRect& getClipBorder() const", asMETHOD(T, getClipBorder), asCALL_THISCALL);
-    engine->RegisterObjectMethod(className, "const Color& getColor(UIElementCorner) const", asMETHOD(T, getColor), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "const Color& getColor(Corner) const", asMETHOD(T, getColor), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "int getPriority() const", asMETHOD(T, getPriority), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "float getOpacity() const", asMETHOD(T, getOpacity), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "bool getBringToFront() const", asMETHOD(T, getBringToFront), asCALL_THISCALL);
@@ -479,6 +483,11 @@ template <class T> void registerUIElement(asIScriptEngine* engine, const char* c
     engine->RegisterObjectMethod(className, "bool isHovering() const", asMETHOD(T, isHovering), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "bool hasColorGradient() const", asMETHOD(T, hasColorGradient), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "const Variant& getUserData() const", asMETHOD(T, getUserData), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "Orientation getLayoutOrientation() const", asMETHOD(T, getLayoutOrientation), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "LayoutMode getHorizontalLayoutMode() const", asMETHOD(T, getHorizontalLayoutMode), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "LayoutMode getVerticalLayoutMode() const", asMETHOD(T, getVerticalLayoutMode), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "int getLayoutSpacing() const", asMETHOD(T, getLayoutSpacing), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "const IntRect& getLayoutBorder() const", asMETHOD(T, getLayoutBorder), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "uint getNumChildren(bool) const", asMETHOD(T, getNumChildren), 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);

+ 17 - 11
Engine/Engine/RegisterUI.cpp

@@ -54,15 +54,20 @@ static void registerUIElement(asIScriptEngine* engine)
     engine->RegisterEnumValue("VerticalAlignment", "VA_CENTER", VA_CENTER);
     engine->RegisterEnumValue("VerticalAlignment", "VA_BOTTOM", VA_BOTTOM);
     
-    engine->RegisterEnum("UIElementCorner");
-    engine->RegisterEnumValue("UIElementCorner", "C_TOPLEFT", C_TOPLEFT);
-    engine->RegisterEnumValue("UIElementCorner", "C_TOPRIGHT", C_TOPRIGHT);
-    engine->RegisterEnumValue("UIElementCorner", "C_BOTTOMLEFT", C_BOTTOMLEFT);
-    engine->RegisterEnumValue("UIElementCorner", "C_BOTTOMRIGHT", C_BOTTOMRIGHT);
+    engine->RegisterEnum("Corner");
+    engine->RegisterEnumValue("Corner", "C_TOPLEFT", C_TOPLEFT);
+    engine->RegisterEnumValue("Corner", "C_TOPRIGHT", C_TOPRIGHT);
+    engine->RegisterEnumValue("Corner", "C_BOTTOMLEFT", C_BOTTOMLEFT);
+    engine->RegisterEnumValue("Corner", "C_BOTTOMRIGHT", C_BOTTOMRIGHT);
     
-    engine->RegisterEnum("UIElementOrientation");
-    engine->RegisterEnumValue("UIElementOrientation", "O_HORIZONTAL", O_HORIZONTAL);
-    engine->RegisterEnumValue("UIElementOrientation", "O_VERTICAL", O_VERTICAL);
+    engine->RegisterEnum("Orientation");
+    engine->RegisterEnumValue("Orientation", "O_HORIZONTAL", O_HORIZONTAL);
+    engine->RegisterEnumValue("Orientation", "O_VERTICAL", O_VERTICAL);
+    
+    engine->RegisterEnum("LayoutMode");
+    engine->RegisterEnumValue("LayoutMode", "LM_FREE", LM_FREE);
+    engine->RegisterEnumValue("LayoutMode", "LM_RESIZECHILDREN", LM_RESIZECHILDREN);
+    engine->RegisterEnumValue("LayoutMode", "LM_RESIZEELEMENT", LM_RESIZEELEMENT);
     
     registerUIElement<UIElement>(engine, "UIElement");
     
@@ -111,10 +116,10 @@ static void registerCheckBox(asIScriptEngine* engine)
 static void registerSlider(asIScriptEngine* engine)
 {
     registerBorderImage<Slider>(engine, "Slider");
-    engine->RegisterObjectMethod("Slider", "void setOrientation(UIElementOrientation)", asMETHOD(Slider, setOrientation), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Slider", "void setOrientation(Orientation)", asMETHOD(Slider, setOrientation), asCALL_THISCALL);
     engine->RegisterObjectMethod("Slider", "void setRange(float)", asMETHOD(Slider, setRange), asCALL_THISCALL);
     engine->RegisterObjectMethod("Slider", "void setValue(float)", asMETHOD(Slider, setValue), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Slider", "UIElementOrientation getOrientation() const", asMETHOD(Slider, getOrientation), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Slider", "Orientation getOrientation() const", asMETHOD(Slider, getOrientation), asCALL_THISCALL);
     engine->RegisterObjectMethod("Slider", "float getRange() const", asMETHOD(Slider, getRange), asCALL_THISCALL);
     engine->RegisterObjectMethod("Slider", "float getValue() const", asMETHOD(Slider, getValue), asCALL_THISCALL);
     engine->RegisterObjectMethod("Slider", "BorderImage@+ getSliderElement() const", asMETHOD(Slider, getSliderElement), asCALL_THISCALL);
@@ -145,10 +150,10 @@ static void registerText(asIScriptEngine* engine)
 {
     registerUIElement<Text>(engine, "Text");
     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);
     engine->RegisterObjectMethod("Text", "void setTextAlignment(HorizontalAlignment)", asMETHOD(Text, setTextAlignment), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text", "void setRowSpacing(float)", asMETHOD(Text, setRowSpacing), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Text", "void setWordwrap(bool)", asMETHOD(Text, setWordwrap), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text", "void setSelection(uint, uint)", asMETHOD(Text, setSelection), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text", "void clearSelection()", asMETHOD(Text, clearSelection), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text", "void setSelectionColor(const Color& in)", asMETHOD(Text, setSelectionColor), asCALL_THISCALL);
@@ -159,6 +164,7 @@ static void registerText(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Text", "const string& getText() const", asMETHOD(Text, getText), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text", "HorizontalAlignment getTextAlignment() const", asMETHOD(Text, getTextAlignment), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text", "float getRowSpacing() const", asMETHOD(Text, getRowSpacing), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Text", "bool getWordwrap() const", asMETHOD(Text, getWordwrap), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text", "uint getSelectionStart() const", asMETHOD(Text, getSelectionStart), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text", "uint getSelectionLength() const", asMETHOD(Text, getSelectionLength), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text", "const Color& getSelectionColor() const", asMETHOD(Text, getSelectionColor), asCALL_THISCALL);

+ 1 - 2
Engine/Engine/Server.cpp

@@ -199,8 +199,7 @@ void Server::update(float timeStep)
     // Close file transfers that have been unused for some time
     for (std::map<StringHash, ServerFileTransfer>::iterator i = mFileTransfers.begin(); i != mFileTransfers.end();)
     {
-        std::map<StringHash, ServerFileTransfer>::iterator current = i;
-        ++i;
+        std::map<StringHash, ServerFileTransfer>::iterator current = i++;
         if (current->second.mCloseTimer.getMSec(false) > FILE_TIMEOUT)
             mFileTransfers.erase(current);
     }

+ 2 - 4
Engine/Physics/CollisionShape.cpp

@@ -750,8 +750,7 @@ void CollisionShape::cleanupCaches()
     for (std::map<std::string, SharedPtr<TriangleMeshData> >::iterator i = sTriangleMeshCache.begin();
         i != sTriangleMeshCache.end();)
     {
-        std::map<std::string, SharedPtr<TriangleMeshData> >::iterator current = i;
-        ++i;
+        std::map<std::string, SharedPtr<TriangleMeshData> >::iterator current = i++;
         if (current->second.getRefCount() == 1)
             sTriangleMeshCache.erase(current);
     }
@@ -759,8 +758,7 @@ void CollisionShape::cleanupCaches()
     for (std::map<std::string, SharedPtr<HeightfieldData> >::iterator i = sHeightfieldCache.begin();
         i != sHeightfieldCache.end();)
     {
-        std::map<std::string, SharedPtr<HeightfieldData> >::iterator current = i;
-        ++i;
+        std::map<std::string, SharedPtr<HeightfieldData> >::iterator current = i++;
         if (current->second.getRefCount() == 1)
             sHeightfieldCache.erase(current);
     }

+ 5 - 4
Engine/Renderer/BillboardSet.cpp

@@ -273,10 +273,11 @@ void BillboardSet::updateDistance(const FrameInfo& frame)
     // Calculate scaled distance for animation LOD
     static const Vector3 dotScale(1 / 3.0f, 1 / 3.0f, 1 / 3.0f);
     float scale = getWorldBoundingBox().getSize().dotProduct(dotScale);
-    // If there are no billboards, the size becomes zero, and updates no longer happen. Prevent this
-    if (scale < M_EPSILON)
-        scale = 1.0f;
-    mLodDistance = frame.mCamera->getLodDistance(mDistance, scale, mLodBias);
+    // If there are no billboards, the size becomes zero, and LOD'ed updates no longer happen. Disable LOD in that case
+    if (scale > M_EPSILON)
+        mLodDistance = frame.mCamera->getLodDistance(mDistance, scale, mLodBias);
+    else
+        mLodDistance = 0.0f;
 }
 
 void BillboardSet::updateGeometry(const FrameInfo& frame, Renderer* renderer)

+ 2 - 4
Engine/Renderer/InstancedModel.cpp

@@ -994,8 +994,7 @@ void InstancedModel::cleanupInstanceBuffers()
     for (std::map<const VertexBuffer*, SharedPtr<VertexBuffer> >::iterator i = sInstanceVertexBuffers.begin();
         i != sInstanceVertexBuffers.end();)
     {
-        std::map<const VertexBuffer*, SharedPtr<VertexBuffer> >::iterator current = i;
-        ++i;
+        std::map<const VertexBuffer*, SharedPtr<VertexBuffer> >::iterator current = i++;
         if (current->second.getRefCount() == 1)
             sInstanceVertexBuffers.erase(current);
     }
@@ -1003,8 +1002,7 @@ void InstancedModel::cleanupInstanceBuffers()
     for (std::map<std::pair<const IndexBuffer*, unsigned>, SharedPtr<IndexBuffer> >::iterator i = sInstanceIndexBuffers.begin();
         i != sInstanceIndexBuffers.end();)
     {
-        std::map<std::pair<const IndexBuffer*, unsigned>, SharedPtr<IndexBuffer> >::iterator current = i;
-        ++i;
+        std::map<std::pair<const IndexBuffer*, unsigned>, SharedPtr<IndexBuffer> >::iterator current = i++;
         if (current->second.getRefCount() == 1)
             sInstanceIndexBuffers.erase(current);
     }

+ 2 - 4
Engine/Resource/ResourceCache.cpp

@@ -176,8 +176,7 @@ void ResourceCache::releaseResources(ShortStringHash type, bool force)
             for (std::map<StringHash, SharedPtr<Resource> >::iterator j = i->second.mResources.begin();
                 j != i->second.mResources.end();)
             {
-                std::map<StringHash, SharedPtr<Resource> >::iterator current = j;
-                ++j;
+                std::map<StringHash, SharedPtr<Resource> >::iterator current = j++;
                 // If other references exist, do not release, unless forced
                 if ((current->second.getRefCount() == 1) || (force))
                 {
@@ -205,8 +204,7 @@ void ResourceCache::releaseResources(ShortStringHash type, const std::string& pa
             for (std::map<StringHash, SharedPtr<Resource> >::iterator j = i->second.mResources.begin();
                 j != i->second.mResources.end();)
             {
-                std::map<StringHash, SharedPtr<Resource> >::iterator current = j;
-                ++j;
+                std::map<StringHash, SharedPtr<Resource> >::iterator current = j++;
                 if (current->second->getName().find(partialNameLower) != std::string::npos)
                 {
                     // If other references exist, do not release, unless forced

+ 2 - 4
Engine/Script/ScriptEventListener.cpp

@@ -29,8 +29,7 @@ void ScriptEventListener::removeEventHandler(StringHash eventType)
     for (std::map<std::pair<EventListener*, StringHash>, asIScriptFunction*>::iterator i = mSpecificEventHandlers.begin(); i !=
         mSpecificEventHandlers.end();)
     {
-        std::map<std::pair<EventListener*, StringHash>, asIScriptFunction*>::iterator current = i;
-        ++i;
+        std::map<std::pair<EventListener*, StringHash>, asIScriptFunction*>::iterator current = i++;
         if (current->first.second == eventType)
             mSpecificEventHandlers.erase(current);
     }
@@ -59,8 +58,7 @@ void ScriptEventListener::removeEventHandlers(EventListener* sender)
     for (std::map<std::pair<EventListener*, StringHash>, asIScriptFunction*>::iterator i = mSpecificEventHandlers.begin(); i !=
         mSpecificEventHandlers.end();)
     {
-        std::map<std::pair<EventListener*, StringHash>, asIScriptFunction*>::iterator current = i;
-        ++i;
+        std::map<std::pair<EventListener*, StringHash>, asIScriptFunction*>::iterator current = i++;
         if (current->first.first == sender)
             mSpecificEventHandlers.erase(current);
     }

+ 22 - 5
Engine/UI/LineEdit.cpp

@@ -285,6 +285,7 @@ void LineEdit::onKey(int key, int buttons, int qualifiers)
             else
                 mLine = mLine.substr(0, start);
             mText->clearSelection();
+            mCursorPosition = start;
             changed = true;
         }
         break;
@@ -315,13 +316,29 @@ void LineEdit::onChar(unsigned char c, int buttons, int qualifiers)
     
     if (c == '\b')
     {
-        if ((mLine.length()) && (mCursorPosition))
+        if (!mText->getSelectionLength())
         {
-            if (mCursorPosition < mLine.length())
-                mLine = mLine.substr(0, mCursorPosition - 1) + mLine.substr(mCursorPosition);
+            if ((mLine.length()) && (mCursorPosition))
+            {
+                if (mCursorPosition < mLine.length())
+                    mLine = mLine.substr(0, mCursorPosition - 1) + mLine.substr(mCursorPosition);
+                else
+                    mLine = mLine.substr(0, mCursorPosition - 1);
+                --mCursorPosition;
+                changed = true;
+            }
+        }
+        else
+        {
+            // If a selection exists, erase it
+            unsigned start = mText->getSelectionStart();
+            unsigned length = mText->getSelectionLength();
+            if (start + length < mLine.length())
+                mLine = mLine.substr(0, start) + mLine.substr(start + length);
             else
-                mLine = mLine.substr(0, mCursorPosition - 1);
-            --mCursorPosition;
+                mLine = mLine.substr(0, start);
+            mText->clearSelection();
+            mCursorPosition = start;
             changed = true;
         }
     }

+ 1 - 1
Engine/UI/Slider.cpp

@@ -136,7 +136,7 @@ void Slider::onResize()
     updateSlider();
 }
 
-void Slider::setOrientation(UIElementOrientation orientation)
+void Slider::setOrientation(Orientation orientation)
 {
     mOrientation = orientation;
     updateSlider();

+ 3 - 3
Engine/UI/Slider.h

@@ -53,14 +53,14 @@ public:
     virtual void onResize();
     
     //! Set orientation
-    void setOrientation(UIElementOrientation orientation);
+    void setOrientation(Orientation orientation);
     //! Set slider range maximum value (minimum value is always 0)
     void setRange(float range);
     //! Set slider current value
     void setValue(float value);
     
     //! Return orientation
-    UIElementOrientation getOrientation() const { return mOrientation; }
+    Orientation getOrientation() const { return mOrientation; }
     //! Return slider range
     float getRange() const { return mRange; }
     //! Return slider current value
@@ -75,7 +75,7 @@ protected:
     //! Slider image
     SharedPtr<BorderImage> mSlider;
     //! Orientation
-    UIElementOrientation mOrientation;
+    Orientation mOrientation;
     //! Slider range
     float mRange;
     //! Slider current value

+ 102 - 94
Engine/UI/Text.cpp

@@ -35,10 +35,10 @@
 Text::Text(const std::string& name, const std::string& text) :
     UIElement(name),
     mFontSize(DEFAULT_FONT_SIZE),
-    mMaxWidth(0),
     mText(text),
     mTextAlignment(HA_LEFT),
     mRowSpacing(1.0f),
+    mWordwrap(false),
     mSelectionStart(0),
     mSelectionLength(0),
     mSelectionColor(Color(0.0f, 0.0f, 0.0f, 0.0f)),
@@ -55,19 +55,14 @@ void Text::setStyle(const XMLElement& element, ResourceCache* cache)
 {
     UIElement::setStyle(element, cache);
     
+    // Set word wrap first to preserve any pre-defined width
+    if (element.hasChildElement("wordwrap"))
+        setWordwrap(element.getChildElement("wordwrap").getBool("enable"));
     if (element.hasChildElement("font"))
     {
         XMLElement fontElem = element.getChildElement("font");
         setFont(cache->getResource<Font>(fontElem.getString("name")), fontElem.getInt("size"));
     }
-    if (element.hasChildElement("maxwidth"))
-        setMaxWidth(element.getChildElement("maxwidth").getInt("value"));
-    if (element.hasChildElement("text"))
-    {
-        std::string text = element.getChildElement("text").getString("value");
-        replaceInPlace(text, "\\n", "\n");
-        setText(text);
-    }
     if (element.hasChildElement("textalignment"))
     {
         std::string horiz = element.getChildElement("textalignment").getStringLower("value");
@@ -89,84 +84,14 @@ void Text::setStyle(const XMLElement& element, ResourceCache* cache)
         setSelectionColor(element.getChildElement("selectioncolor").getColor("value"));
     if (element.hasChildElement("hovercolor"))
         setHoverColor(element.getChildElement("hovercolor").getColor("value"));
-}
-
-bool Text::setFont(Font* font, int size)
-{
-    if (!font)
-    {
-        LOGERROR("Null font for Text");
-        return false;
-    }
-    
-    if ((font != mFont) || (size != mFontSize))
-    {
-        mFont = font;
-        mFontSize = max(size, 1);
-        updateText();
-    }
-    
-    return true;
-}
-
-void Text::setMaxWidth(int maxWidth)
-{
-    if (maxWidth != mMaxWidth)
-    {
-        mMaxWidth = max(maxWidth, 0);
-        updateText();
-    }
-}
-
-void Text::setText(const std::string& text)
-{
-    mText = text;
-    
-    validateSelection();
-    updateText();
-}
-
-void Text::setTextAlignment(HorizontalAlignment align)
-{
-    if (align != mTextAlignment)
-    {
-        mTextAlignment = align;
-        updateText();
-    }
-}
-
-void Text::setRowSpacing(float spacing)
-{
-    if (spacing != mRowSpacing)
+    if (element.hasChildElement("text"))
     {
-        mRowSpacing = max(spacing, 0.5f);
-        updateText();
+        std::string text = element.getChildElement("text").getString("value");
+        replaceInPlace(text, "\\n", "\n");
+        setText(text);
     }
 }
 
-void Text::setSelection(unsigned start, unsigned length)
-{
-    mSelectionStart = start;
-    mSelectionLength = length;
-    validateSelection();
-}
-
-void Text::clearSelection()
-{
-    mSelectionStart = 0;
-    mSelectionLength = 0;
-}
-
-void Text::setSelectionColor(const Color& color)
-{
-    mSelectionColor = color;
-}
-
-void Text::setHoverColor(const Color& color)
-{
-    mHoverColor = color;
-}
-
 void Text::getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads, const IntRect& currentScissor)
 {
     // Hovering batch
@@ -261,7 +186,89 @@ void Text::getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads,
     mHovering = false;
 }
 
-void Text::updateText()
+void Text::onResize()
+{
+    if (mWordwrap)
+        updateText();
+}
+
+bool Text::setFont(Font* font, int size)
+{
+    if (!font)
+    {
+        LOGERROR("Null font for Text");
+        return false;
+    }
+    
+    if ((font != mFont) || (size != mFontSize))
+    {
+        mFont = font;
+        mFontSize = max(size, 1);
+        updateText();
+    }
+    
+    return true;
+}
+
+void Text::setText(const std::string& text)
+{
+    mText = text;
+    
+    validateSelection();
+    updateText();
+}
+
+void Text::setTextAlignment(HorizontalAlignment align)
+{
+    if (align != mTextAlignment)
+    {
+        mTextAlignment = align;
+        updateText();
+    }
+}
+
+void Text::setRowSpacing(float spacing)
+{
+    if (spacing != mRowSpacing)
+    {
+        mRowSpacing = max(spacing, 0.5f);
+        updateText();
+    }
+}
+
+void Text::setWordwrap(bool enable)
+{
+    if (enable != mWordwrap)
+    {
+        mWordwrap = enable;
+        updateText();
+    }
+}
+
+void Text::setSelection(unsigned start, unsigned length)
+{
+    mSelectionStart = start;
+    mSelectionLength = length;
+    validateSelection();
+}
+
+void Text::clearSelection()
+{
+    mSelectionStart = 0;
+    mSelectionLength = 0;
+}
+
+void Text::setSelectionColor(const Color& color)
+{
+    mSelectionColor = color;
+}
+
+void Text::setHoverColor(const Color& color)
+{
+    mHoverColor = color;
+}
+
+void Text::updateText(bool inResize)
 {
     int width = 0;
     int height = 0;
@@ -280,7 +287,7 @@ void Text::updateText()
         int rowHeight = (int)(mRowSpacing * mRowHeight);
         
         // First see if the text must be split up
-        if (!mMaxWidth)
+        if (!mWordwrap)
         {
             mPrintText = mText;
             printToText.resize(mText.length());
@@ -289,6 +296,7 @@ void Text::updateText()
         }
         else
         {
+            int maxWidth = getWidth();
             unsigned nextBreak = 0;
             unsigned lineStart = 0;
             for (unsigned i = 0; i < mText.length(); ++i)
@@ -309,12 +317,12 @@ void Text::updateText()
                                 break;
                             }
                             futureRowWidth += face->mGlyphs[face->mGlyphIndex[mText[j]]].mAdvanceX;
-                            if ((mText[j] == '-') && (futureRowWidth <= mMaxWidth))
+                            if ((mText[j] == '-') && (futureRowWidth <= maxWidth))
                             {
                                 nextBreak = j + 1;
                                 break;
                             }
-                            if (futureRowWidth > mMaxWidth)
+                            if (futureRowWidth > maxWidth)
                             {
                                 ok = false;
                                 break;
@@ -336,7 +344,7 @@ void Text::updateText()
                             }
                         }
                         mPrintText += '\n';
-                        printToText.push_back(i);
+                        printToText.push_back(min((int)i, (int)mText.length() - 1));
                         rowWidth = 0;
                         nextBreak = lineStart = i;
                     }
@@ -345,7 +353,7 @@ void Text::updateText()
                     {
                         // When copying a space, we may be over row width
                         rowWidth += face->mGlyphs[face->mGlyphIndex[mText[i]]].mAdvanceX;
-                        if (rowWidth <= mMaxWidth)
+                        if (rowWidth <= maxWidth)
                         {
                             mPrintText += mText[i];
                             printToText.push_back(i);
@@ -355,7 +363,7 @@ void Text::updateText()
                 else
                 {
                     mPrintText += '\n';
-                    printToText.push_back(i);
+                    printToText.push_back(min((int)i, (int)mText.length() - 1));
                     rowWidth = 0;
                     nextBreak = lineStart = i;
                 }
@@ -422,10 +430,10 @@ void Text::updateText()
         mCharPositions[mText.length()] = IntVector2(x, y);
     }
     
-    // If maxwidth is nonzero, fix the element width
-    if (mMaxWidth)
-        width = mMaxWidth;
-    setSize(width, height);
+    if (mWordwrap)
+        setHeight(height);
+    else
+        setSize(width, height);
 }
 
 void Text::validateSelection()

+ 9 - 7
Engine/UI/Text.h

@@ -45,17 +45,19 @@ public:
     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 resize
+    virtual void onResize();
     
     //! Set font and font size
     bool setFont(Font* font, int size = DEFAULT_FONT_SIZE);
-    //! Set maximum row width. If not 0, also fixes the element width to this value
-    void setMaxWidth(int maxWidth);
     //! Set text
     void setText(const std::string& text);
     //! Set row alignment
     void setTextAlignment(HorizontalAlignment align);
     //! Set row spacing, 1.0 for original font spacing
     void setRowSpacing(float spacing);
+    //! Set wordwrap. In wordwrap mode the text element will respect its current width. Otherwise it resizes itself freely
+    void setWordwrap(bool enable);
     //! Set selection
     void setSelection(unsigned start, unsigned length);
     //! Clear selection
@@ -69,14 +71,14 @@ public:
     Font* getFont() const { return mFont; }
     //! Return font size
     int getFontSize() const { return mFontSize; }
-    //! Return maximum row width
-    int getMaxWidth() const { return mMaxWidth; }
     //! Return text
     const std::string& getText() const { return mText; }
     //! Return row alignment
     HorizontalAlignment getTextAlignment() const { return mTextAlignment; }
     //! Return row spacing
     float getRowSpacing() const { return mRowSpacing; }
+    //! Return wordwrap mode
+    bool getWordwrap() const { return mWordwrap; }
     //! Return selection start
     unsigned getSelectionStart() const { return mSelectionStart; }
     //! Return selection length
@@ -98,7 +100,7 @@ public:
     
 protected:
     //! Update text when text, font or spacing changed
-    void updateText();
+    void updateText(bool inResize = false);
     //! Validate text selection to be within the text
     void validateSelection();
     //! Return row start X position
@@ -108,8 +110,6 @@ protected:
     SharedPtr<Font> mFont;
     //! Font size
     int mFontSize;
-    //! Maximum row width
-    int mMaxWidth;
     //! Text
     std::string mText;
     //! Text modified into printed form
@@ -118,6 +118,8 @@ protected:
     HorizontalAlignment mTextAlignment;
     //! Row spacing
     float mRowSpacing;
+    //! Wordwrap mode
+    bool mWordwrap;
     //! Selection start
     unsigned mSelectionStart;
     //! Selection length

+ 45 - 8
Engine/UI/UI.cpp

@@ -345,19 +345,56 @@ void UI::update(float timeStep, UIElement* element)
 
 void UI::getBatches(UIElement* element, IntRect currentScissor)
 {
-    // If hidden, do not draw this element or its children
-    if (!element->isVisible())
+    // Set clipping scissor for child elements. No need to draw if zero size
+    element->adjustScissor(currentScissor);
+    if ((currentScissor.mLeft == currentScissor.mRight) || (currentScissor.mTop == currentScissor.mBottom))
         return;
     
-    element->getBatches(mBatches, mQuads, currentScissor);
-    
-    // Set clipping scissor for child elements, then get child element batches in low-to-high priority order
-    element->adjustScissor(currentScissor);
     std::vector<UIElement*> children = element->getChildren();
+    if (children.empty())
+        return;
+    
     std::sort(children.begin(), children.end(), compareUIElements);
     
-    for (std::vector<UIElement*>::const_iterator i = children.begin(); i != children.end(); ++i)
-        getBatches(*i, currentScissor);
+    // For non-root elements draw all children of same priority before recursing into their children: assumption is that they have
+    // same renderstate
+    std::vector<UIElement*>::const_iterator i = children.begin();
+    if (element != mRootElement)
+    {
+        std::vector<UIElement*>::const_iterator j = i;
+        int currentPriority = children.front()->getPriority();
+        while (i != children.end())
+        {
+            while ((j != children.end()) && ((*j)->getPriority() == currentPriority))
+            {
+                if ((*j)->isVisible())
+                    (*j)->getBatches(mBatches, mQuads, currentScissor);
+                ++j;
+            }
+            // Now recurse into the children
+            while (i != j)
+            {
+                if ((*i)->isVisible())
+                    getBatches(*i, currentScissor);
+                ++i;
+            }
+            if (i != children.end())
+                currentPriority = (*i)->getPriority();
+        }
+    }
+    // On the root level draw each element and its children immediately after to avoid artifacts
+    else
+    {
+        while (i != children.end())
+        {
+            if ((*i)->isVisible())
+            {
+                (*i)->getBatches(mBatches, mQuads, currentScissor);
+                getBatches(*i, currentScissor);
+            }
+            ++i;
+        }
+    }
 }
 
 void UI::getElementAt(UIElement*& result, UIElement* current, const IntVector2& position, bool enabledOnly)

+ 346 - 64
Engine/UI/UIElement.cpp

@@ -48,6 +48,11 @@ UIElement::UIElement(const std::string& name) :
     mSelected(false),
     mVisible(true),
     mHovering(false),
+    mLayoutOrientation(O_VERTICAL),
+    mHorizontalLayoutMode(LM_FREE),
+    mVerticalLayoutMode(LM_FREE),
+    mLayoutSpacing(0),
+    mLayoutBorder(IntRect::sZero),
     mPosition(IntVector2::sZero),
     mSize(IntVector2::sZero),
     mMinSize(IntVector2::sZero),
@@ -57,7 +62,9 @@ UIElement::UIElement(const std::string& name) :
     mVerticalAlignment(VA_TOP),
     mScreenPositionDirty(true),
     mDerivedOpacityDirty(true),
-    mHasColorGradient(false)
+    mHasColorGradient(false),
+    mResizeNestingLevel(0),
+    mUpdateLayoutNestingLevel(0)
 {
 }
 
@@ -91,6 +98,8 @@ void UIElement::setStyle(const XMLElement& element, ResourceCache* cache)
         setMinSize(element.getChildElement("minsize").getIntVector2("value"));
     if (element.hasChildElement("maxsize"))
         setMaxSize(element.getChildElement("maxsize").getIntVector2("value"));
+    if (element.hasChildElement("fixedsize"))
+        setFixedSize(element.getChildElement("fixedsize").getIntVector2("value"));
     if (element.hasChildElement("alignment"))
     {
         XMLElement alignElem = element.getChildElement("alignment");
@@ -154,6 +163,45 @@ void UIElement::setStyle(const XMLElement& element, ResourceCache* cache)
         setSelected(element.getChildElement("selected").getBool("enable"));
     if (element.hasChildElement("visible"))
         setVisible(element.getChildElement("visible").getBool("enable"));
+    if (element.hasChildElement("layout"))
+    {
+        XMLElement layoutElem = element.getChildElement("layout");
+        std::string orientation = layoutElem.getStringLower("orientation");
+        if ((orientation == "horizontal") || (orientation == "h"))
+            mLayoutOrientation = O_HORIZONTAL;
+        if ((orientation == "vertical") || (orientation == "v"))
+            mLayoutOrientation = O_VERTICAL;
+        
+        std::string horiz;
+        std::string vert;
+        if (layoutElem.hasAttribute("h"))
+            horiz = layoutElem.getStringLower("h");
+        if (layoutElem.hasAttribute("v"))
+            vert = layoutElem.getStringLower("v");
+        if (layoutElem.hasAttribute("horizontal"))
+            horiz = layoutElem.getStringLower("horizontal");
+        if (layoutElem.hasAttribute("vertical"))
+            vert = layoutElem.getStringLower("vertical");
+        if (horiz == "free")
+            mHorizontalLayoutMode = LM_FREE;
+        if ((horiz == "children") || (horiz == "resizechildren"))
+            mHorizontalLayoutMode = LM_RESIZECHILDREN;
+        if ((horiz == "element") || (horiz == "resizeelement"))
+            mHorizontalLayoutMode = LM_RESIZEELEMENT;
+        if (vert == "free")
+            mVerticalLayoutMode = LM_FREE;
+        if ((vert == "children") || (vert == "resizechildren"))
+            mVerticalLayoutMode = LM_RESIZECHILDREN;
+        if ((vert == "element") || (vert == "resizeelement"))
+            mVerticalLayoutMode = LM_RESIZEELEMENT;
+        
+        if (layoutElem.hasAttribute("spacing"))
+            mLayoutSpacing = max(layoutElem.getInt("spacing"), 0);
+        if (layoutElem.hasAttribute("border"))
+            mLayoutBorder = layoutElem.getIntRect("border");
+        
+        updateLayout();
+    }
 }
 
 void UIElement::update(float timeStep)
@@ -300,6 +348,8 @@ void UIElement::setPosition(int x, int y)
 
 void UIElement::setSize(const IntVector2& size)
 {
+    ++mResizeNestingLevel;
+    
     IntVector2 validatedSize;
     validatedSize.mX = clamp(size.mX, mMinSize.mX, mMaxSize.mX);
     validatedSize.mY = clamp(size.mY, mMinSize.mY, mMaxSize.mY);
@@ -307,15 +357,27 @@ void UIElement::setSize(const IntVector2& size)
     if (validatedSize != mSize)
     {
         mSize = validatedSize;
-        markDirty();
-        onResize();
-        
-        using namespace Resized;
         
-        VariantMap eventData;
-        eventData[P_ELEMENT] = (void*)this;
-        sendEvent(mFocus ? EVENT_FOCUSED : EVENT_DEFOCUSED, eventData);	
+        if (mResizeNestingLevel == 1)
+        {
+            // Check if parent element's layout needs to be updated
+            if (mParent)
+                mParent->updateLayout();
+            
+            markDirty();
+            onResize();
+            
+            using namespace Resized;
+            
+            VariantMap eventData;
+            eventData[P_ELEMENT] = (void*)this;
+            sendEvent(mFocus ? EVENT_FOCUSED : EVENT_DEFOCUSED, eventData);
+            
+            updateLayout();
+        }
     }
+    
+    --mResizeNestingLevel;
 }
 
 void UIElement::setSize(int width, int height)
@@ -380,6 +442,29 @@ void UIElement::setMaxHeight(int height)
     setMaxSize(IntVector2(mMaxSize.mX, height));
 }
 
+void UIElement::setFixedSize(const IntVector2& size)
+{
+    mMinSize = mMaxSize = IntVector2(max(size.mX, 0), max(size.mY, 0));
+    setSize(size);
+}
+
+void UIElement::setFixedSize(int width, int height)
+{
+    setFixedSize(IntVector2(width, height));
+}
+
+void UIElement::setFixedWidth(int width)
+{
+    mMinSize.mX = mMaxSize.mX = max(width, 0);
+    setWidth(width);
+}
+
+void UIElement::setFixedHeight(int height)
+{
+    mMinSize.mY = mMaxSize.mY = max(height, 0);
+    setHeight(height);
+}
+
 void UIElement::setAlignment(HorizontalAlignment hAlign, VerticalAlignment vAlign)
 {
     mHorizontalAlignment = hAlign;
@@ -419,7 +504,7 @@ void UIElement::setColor(const Color& color)
     mHasColorGradient = false;
 }
 
-void UIElement::setColor(UIElementCorner corner, const Color& color)
+void UIElement::setColor(Corner corner, const Color& color)
 {
     mColor[corner] = color;
     mHasColorGradient = false;
@@ -515,6 +600,157 @@ void UIElement::setStyleAuto(XMLFile* file, ResourceCache* cache)
     setStyle(element, cache);
 }
 
+void UIElement::setLayout(Orientation orientation, LayoutMode horizontal, LayoutMode vertical, int spacing, const IntRect& border)
+{
+    mLayoutOrientation = orientation;
+    mHorizontalLayoutMode = horizontal;
+    mVerticalLayoutMode = vertical;
+    mLayoutSpacing = max(spacing, 0);
+    mLayoutBorder = border;
+    
+    updateLayout();
+}
+
+void UIElement::updateLayout()
+{
+    if ((mUpdateLayoutNestingLevel) || ((mHorizontalLayoutMode == LM_FREE) && (mVerticalLayoutMode == LM_FREE)))
+        return;
+    
+    ++mUpdateLayoutNestingLevel;
+    
+    std::vector<int> positions(mChildren.size());
+    std::vector<int> sizes(mChildren.size());
+    std::vector<int> minSizes(mChildren.size());
+    std::vector<int> maxSizes(mChildren.size());
+    
+    if (mLayoutOrientation == O_HORIZONTAL)
+    {
+        int maxChildHeight = 0;
+        
+        if (mHorizontalLayoutMode == LM_RESIZEELEMENT)
+        {
+            for (unsigned i = 0; i < mChildren.size(); ++i)
+            {
+                sizes[i] = mChildren[i]->getWidth();
+                maxChildHeight = max(maxChildHeight, mChildren[i]->getHeight());
+            }
+            
+            setSize(calculateLayoutParentSize(sizes, mLayoutBorder.mLeft, mLayoutBorder.mRight, mLayoutSpacing), mVerticalLayoutMode ==
+                LM_RESIZEELEMENT ? maxChildHeight + mLayoutBorder.mTop + mLayoutBorder.mBottom : getHeight());
+           
+            int position = mLayoutBorder.mLeft;
+            for (unsigned i = 0; i < mChildren.size(); ++i)
+            {
+                mChildren[i]->setHorizontalAlignment(HA_LEFT);
+                int y = mChildren[i]->getPosition().mY;
+                if ((mChildren[i]->getVerticalAlignment() == VA_TOP) && (y < mLayoutBorder.mTop))
+                    y = mLayoutBorder.mTop;
+                if ((mChildren[i]->getVerticalAlignment() == VA_BOTTOM) && (y > -mLayoutBorder.mBottom))
+                    y = -mLayoutBorder.mBottom;
+                mChildren[i]->setPosition(position, y);
+                if (mVerticalLayoutMode == LM_RESIZECHILDREN)
+                    mChildren[i]->setHeight(getHeight() - mLayoutBorder.mTop - mLayoutBorder.mBottom);
+                position += mChildren[i]->getWidth();
+                position += mLayoutSpacing;
+            }
+        }
+        
+        if (mHorizontalLayoutMode == LM_RESIZECHILDREN)
+        {
+            for (unsigned i = 0; i < mChildren.size(); ++i)
+            {
+                sizes[i] = mChildren[i]->getWidth();
+                minSizes[i] = mChildren[i]->getMinWidth();
+                maxSizes[i] = mChildren[i]->getMaxWidth();
+                maxChildHeight = max(maxChildHeight, mChildren[i]->getHeight());
+            }
+            
+            if (mVerticalLayoutMode == LM_RESIZEELEMENT)
+                setHeight(maxChildHeight + mLayoutBorder.mTop + mLayoutBorder.mBottom);
+            
+            calculateLayout(positions, sizes, minSizes, maxSizes, getWidth(), mLayoutBorder.mLeft, mLayoutBorder.mRight,
+                mLayoutSpacing);
+            
+            for (unsigned i = 0; i < mChildren.size(); ++i)
+            {
+                mChildren[i]->setHorizontalAlignment(HA_LEFT);
+                int y = mChildren[i]->getPosition().mY;
+                if ((mChildren[i]->getVerticalAlignment() == VA_TOP) && (y < mLayoutBorder.mTop))
+                    y = mLayoutBorder.mTop;
+                if ((mChildren[i]->getVerticalAlignment() == VA_BOTTOM) && (y > -mLayoutBorder.mBottom))
+                    y = -mLayoutBorder.mBottom;
+                mChildren[i]->setPosition(positions[i], y);
+                mChildren[i]->setSize(sizes[i], mVerticalLayoutMode == LM_RESIZECHILDREN ? getHeight() - mLayoutBorder.mTop -
+                    mLayoutBorder.mBottom : mChildren[i]->getHeight());
+            }
+        }
+    }
+    
+    if (mLayoutOrientation == O_VERTICAL)
+    {
+        int maxChildWidth = 0;
+        
+        if (mVerticalLayoutMode == LM_RESIZEELEMENT)
+        {
+            for (unsigned i = 0; i < mChildren.size(); ++i)
+            {
+                sizes[i] = mChildren[i]->getHeight();
+                maxChildWidth = max(maxChildWidth, mChildren[i]->getWidth());
+            }
+            
+            setSize(mHorizontalLayoutMode == LM_RESIZEELEMENT ? maxChildWidth + mLayoutBorder.mLeft + mLayoutBorder.mRight : getWidth(),
+                calculateLayoutParentSize(sizes, mLayoutBorder.mTop, mLayoutBorder.mBottom, mLayoutSpacing));
+            
+            int position = mLayoutBorder.mTop;
+            for (unsigned i = 0; i < mChildren.size(); ++i)
+            {
+                mChildren[i]->setVerticalAlignment(VA_TOP);
+                int x = mChildren[i]->getPosition().mX;
+                if ((mChildren[i]->getHorizontalAlignment() == HA_LEFT) && (x < mLayoutBorder.mLeft))
+                    x = mLayoutBorder.mLeft;
+                if ((mChildren[i]->getHorizontalAlignment() == HA_RIGHT) && (x > -mLayoutBorder.mRight))
+                    x = -mLayoutBorder.mRight;
+                mChildren[i]->setPosition(x, position);
+                if (mHorizontalLayoutMode == LM_RESIZECHILDREN)
+                    mChildren[i]->setWidth(getWidth() - mLayoutBorder.mLeft - mLayoutBorder.mRight);
+                position += mChildren[i]->getHeight();
+                position += mLayoutSpacing;
+            }
+        }
+        else
+        {
+            for (unsigned i = 0; i < mChildren.size(); ++i)
+            {
+                sizes[i] = mChildren[i]->getHeight();
+                minSizes[i] = mChildren[i]->getMinHeight();
+                maxSizes[i] = mChildren[i]->getMaxHeight();
+                maxChildWidth = max(maxChildWidth, mChildren[i]->getWidth());
+            }
+            
+            if (mHorizontalLayoutMode == LM_RESIZEELEMENT)
+                setWidth(maxChildWidth + mLayoutBorder.mLeft + mLayoutBorder.mRight);
+            
+            calculateLayout(positions, sizes, minSizes, maxSizes, getHeight(), mLayoutBorder.mTop, mLayoutBorder.mBottom,
+                mLayoutSpacing);
+            
+            for (unsigned i = 0; i < mChildren.size(); ++i)
+            {
+                mChildren[i]->setVerticalAlignment(VA_TOP);
+                int x = mChildren[i]->getPosition().mX;
+                if ((mChildren[i]->getHorizontalAlignment() == HA_LEFT) && (x < mLayoutBorder.mLeft))
+                    x = mLayoutBorder.mLeft;
+                if ((mChildren[i]->getHorizontalAlignment() == HA_RIGHT) && (x > -mLayoutBorder.mRight))
+                    x = -mLayoutBorder.mRight;
+                mChildren[i]->setPosition(x, positions[i]);
+                mChildren[i]->setSize(mHorizontalLayoutMode == LM_RESIZECHILDREN ? getWidth() - mLayoutBorder.mLeft -
+                    mLayoutBorder.mRight : mChildren[i]->getWidth(), sizes[i]);
+            }
+        }
+    }
+    
+    --mUpdateLayoutNestingLevel;
+}
+
 void UIElement::bringToFront()
 {
     // Follow the parent chain to the top level window. If it has BringToFront mode, bring it to front now
@@ -555,6 +791,7 @@ void UIElement::addChild(UIElement* element)
     
     element->mParent = this;
     element->markDirty();
+    updateLayout();
 }
 
 void UIElement::removeChild(UIElement* element)
@@ -566,6 +803,7 @@ void UIElement::removeChild(UIElement* element)
             element->mParent = 0;
             element->markDirty();
             mChildren.erase(i);
+            updateLayout();
             return;
         }
     }
@@ -582,50 +820,6 @@ void UIElement::removeAllChildren()
     }
 }
 
-void UIElement::layoutHorizontal(int spacing, const IntRect& border, bool expand, bool contract)
-{
-    IntVector2 currentPos(border.mLeft, border.mTop);
-    IntVector2 neededSize(IntVector2::sZero);
-    for (std::vector<SharedPtr<UIElement> >::iterator i = mChildren.begin(); i != mChildren.end(); ++i)
-    {
-        (*i)->setHorizontalAlignment(HA_LEFT);
-        if ((*i)->getVerticalAlignment() == VA_CENTER)
-            (*i)->setPosition(currentPos.mX, 0);
-        else
-            (*i)->setPosition(currentPos);
-        currentPos.mX += (*i)->getWidth() + spacing;
-        if ((*i)->getHeight() + border.mTop + border.mBottom > neededSize.mY)
-            neededSize.mY = (*i)->getHeight() + border.mTop + border.mBottom;
-    }
-    currentPos.mX -= spacing;
-    if (currentPos.mX + border.mRight > neededSize.mX)
-        neededSize.mX = currentPos.mX + border.mRight;
-    
-    adjustSize(neededSize, expand, contract);
-}
-
-void UIElement::layoutVertical(int spacing, const IntRect& border, bool expand, bool contract)
-{
-    IntVector2 currentPos(border.mLeft, border.mTop);
-    IntVector2 neededSize(IntVector2::sZero);
-    for (std::vector<SharedPtr<UIElement> >::iterator i = mChildren.begin(); i != mChildren.end(); ++i)
-    {
-        (*i)->setVerticalAlignment(VA_TOP);
-        if ((*i)->getHorizontalAlignment() == HA_CENTER)
-            (*i)->setPosition(0, currentPos.mY);
-        else
-            (*i)->setPosition(currentPos);
-        currentPos.mY += (*i)->getHeight() + spacing;
-        if ((*i)->getWidth() + border.mLeft + border.mRight > neededSize.mX)
-            neededSize.mX = (*i)->getWidth() + border.mLeft + border.mRight;
-    }
-    currentPos.mY -= spacing;
-    if (currentPos.mY + border.mBottom > neededSize.mY)
-        neededSize.mY = currentPos.mY + border.mBottom;
-    
-    adjustSize(neededSize, expand, contract);
-}
-
 std::vector<UIElement*> UIElement::getChildren(bool recursive) const
 {
     if (!recursive)
@@ -784,6 +978,11 @@ void UIElement::adjustScissor(IntRect& currentScissor)
         currentScissor.mTop = max(currentScissor.mTop, screenPos.mY + mClipBorder.mTop);
         currentScissor.mRight = min(currentScissor.mRight, screenPos.mX + mSize.mX - mClipBorder.mRight);
         currentScissor.mBottom = min(currentScissor.mBottom, screenPos.mY + mSize.mY - mClipBorder.mBottom);
+        
+        if (currentScissor.mRight < currentScissor.mLeft)
+            currentScissor.mRight = currentScissor.mLeft;
+        if (currentScissor.mBottom < currentScissor.mTop)
+            currentScissor.mBottom = currentScissor.mTop;
     }
 }
 
@@ -832,20 +1031,103 @@ void UIElement::getChildrenRecursive(std::vector<UIElement*>& dest) const
     }
 }
 
-void UIElement::adjustSize(const IntVector2& neededSize, bool expand, bool contract)
+int UIElement::calculateLayoutParentSize(const std::vector<int>& sizes, int begin, int end, int spacing)
 {
-    if (expand)
+    int width = begin + end;
+    for (unsigned i = 0; i < sizes.size(); ++i)
     {
-        if (getWidth() < neededSize.mX)
-            setWidth(neededSize.mX);
-        if (getHeight() < neededSize.mY)
-            setHeight(neededSize.mY);
+        width += sizes[i];
+        if (i < sizes.size() - 1)
+            width += spacing;
     }
-    if (contract)
+    return width;
+}
+
+void UIElement::calculateLayout(std::vector<int>& positions, std::vector<int>& sizes, const std::vector<int>& minSizes,
+        const std::vector<int>& maxSizes, int targetWidth, int begin, int end, int spacing)
+{
+    unsigned numChildren = sizes.size();
+    if (!numChildren)
+        return;
+    int targetTotalSize = targetWidth - begin - end - (numChildren - 1) * spacing;
+    if (targetTotalSize < 0)
+        targetTotalSize = 0;
+    int targetChildSize = targetTotalSize / numChildren;
+    int remainder = targetTotalSize % numChildren;
+    float add = (float)remainder / numChildren;
+    float acc = 0.0f;
+    
+    // Initial pass
+    for (unsigned i = 0; i < numChildren; ++i)
+    {
+        int targetSize = targetChildSize;
+        if (remainder)
+        {
+            acc += add;
+            if (acc >= 0.5f)
+            {
+                acc -= 1.0f;
+                ++targetSize;
+                --remainder;
+            }
+        }
+        sizes[i] = clamp(targetSize, minSizes[i], maxSizes[i]);
+    }
+    
+    // Error correction passes
+    for (;;)
+    {
+        int actualTotalSize = 0;
+        for (unsigned i = 0; i < numChildren; ++i)
+            actualTotalSize += sizes[i];
+        int error = targetTotalSize - actualTotalSize;
+        // Break if no error
+        if (!error)
+            break;
+        
+        // Check which of the children can be resized to correct the error. If none, must break
+        static std::vector<unsigned> resizable;
+        resizable.clear();
+        for (unsigned i = 0; i < numChildren; ++i)
+        {
+            if ((error < 0) && (sizes[i] > minSizes[i]))
+                resizable.push_back(i);
+            else if ((error > 0) && (sizes[i] < maxSizes[i]))
+                resizable.push_back(i);
+        }
+        if (resizable.empty())
+            break;
+        
+        int errorPerChild = error / resizable.size();
+        remainder = (abs(error)) % resizable.size();
+        add = (float)remainder / resizable.size();
+        acc = 0.0f;
+        
+        for (unsigned i = 0; i < resizable.size(); ++i)
+        {
+            unsigned idx = resizable[i];
+            int targetSize = sizes[idx] + errorPerChild;
+            if (remainder)
+            {
+                acc += add;
+                if (acc >= 0.5f)
+                {
+                    acc -= 1.0f;
+                    targetSize = error < 0 ? targetSize - 1 : targetSize + 1;
+                    --remainder;
+                }
+            }
+            
+            sizes[idx] = clamp(targetSize, minSizes[idx], maxSizes[idx]);
+        }
+    }
+    
+    // Calculate final positions
+    int position = begin;
+    for (unsigned i = 0; i < numChildren; ++i)
     {
-        if (getWidth() > neededSize.mX)
-            setWidth(neededSize.mX);
-        if (getHeight() > neededSize.mY)
-            setHeight(neededSize.mY);
+        positions[i] = position;
+        position += sizes[i];
+        position += spacing;
     }
 }

+ 56 - 14
Engine/UI/UIElement.h

@@ -48,7 +48,7 @@ enum VerticalAlignment
 };
 
 //! UI element corners
-enum UIElementCorner
+enum Corner
 {
     C_TOPLEFT = 0,
     C_TOPRIGHT,
@@ -58,18 +58,26 @@ enum UIElementCorner
 };
 
 //! UI element orientation
-enum UIElementOrientation
+enum Orientation
 {
     O_HORIZONTAL = 0,
     O_VERTICAL
 };
 
+//! Layout operation mode
+enum LayoutMode
+{
+    LM_FREE = 0,
+    LM_RESIZECHILDREN,
+    LM_RESIZEELEMENT
+};
+
 class ResourceCache;
 
 //! Base class for UI elements
 class UIElement : public HashedType, public EventListener
 {
-    DEFINE_TYPE(UIElement);
+    DEFINE_TYPE(Element);
     
 public:
     //! Construct with name
@@ -102,7 +110,7 @@ public:
     virtual void onKey(int key, int buttons, int qualifiers);
     //! React to a key press translated to a character
     virtual void onChar(unsigned char c, int buttons, int qualifiers);
-    //! React to resize. The widget should not be further resized inside this function
+    //! React to resize
     virtual void onResize();
     //! React to gaining focus
     virtual void onFocus();
@@ -139,6 +147,14 @@ public:
     void setMaxWidth(int width);
     //! Set maximum height
     void setMaxHeight(int height);
+    //! Set fixed size
+    void setFixedSize(const IntVector2& size);
+    //! Set fixed size
+    void setFixedSize(int width, int height);
+    //! Set fixed width
+    void setFixedWidth(int width);
+    //! Set fixed height
+    void setFixedHeight(int height);
     //! Set horizontal and vertical alignment
     void setAlignment(HorizontalAlignment hAlign, VerticalAlignment vAlign);
     //! Set horizontal alignment
@@ -152,7 +168,7 @@ public:
     //! Set color on all corners
     void setColor(const Color& color);
     //! Set color on one corner
-    void setColor(UIElementCorner corner, const Color& color);
+    void setColor(Corner corner, const Color& color);
     //! Set priority
     void setPriority(int priority);
     //! Set opacity
@@ -179,6 +195,11 @@ public:
     void setUserData(const Variant& userData);
     //! Set style from an XML file. Find the style element automatically
     void setStyleAuto(XMLFile* file, ResourceCache* cache);
+    //! Set layout
+    void setLayout(Orientation layoutOrientation, LayoutMode horizontal, LayoutMode vertical, int spacing = 0,
+        const IntRect& border = IntRect::sZero);
+    //! Manually update layout. Should not be necessary in most cases, but is provided for completeness
+    void updateLayout();
     //! Bring UI element to front
     void bringToFront();
     //! Add a child element
@@ -187,10 +208,6 @@ public:
     void removeChild(UIElement* element);
     //! Remove all child elements
     void removeAllChildren();
-    //! Layout child elements horizontally. Expand/contract the element optionally
-    void layoutHorizontal(int spacing = 0, const IntRect& border = IntRect::sZero, bool expand = true, bool contract = true);
-    //! Layout child elements vertically. Expand/contract the element optionally
-    void layoutVertical(int spacing = 0, const IntRect& border = IntRect::sZero, bool expand = true, bool contract = true);
     
     //! Return name
     const std::string& getName() const { return mName; }
@@ -223,7 +240,7 @@ public:
     //! Return child element clipping border
     const IntRect& getClipBorder() const { return mClipBorder; }
     //! Return corner color
-    const Color& getColor(UIElementCorner corner) const { return mColor[corner]; }
+    const Color& getColor(Corner corner) const { return mColor[corner]; }
     //! Return priority
     int getPriority() const { return mPriority; }
     //! Return opacity
@@ -252,6 +269,16 @@ public:
     bool hasColorGradient() const { return mHasColorGradient; }
     //! Return userdata
     Variant getUserData() const { return mUserData; }
+    //! Return layout orientation
+    Orientation getLayoutOrientation() const { return mLayoutOrientation; }
+    //! Return horizontal layout mode
+    LayoutMode getHorizontalLayoutMode() const { return mHorizontalLayoutMode; }
+    //! Return vertical layout mode
+    LayoutMode getVerticalLayoutMode() const { return mVerticalLayoutMode; }
+    //! Return layout spacing
+    int getLayoutSpacing() const { return mLayoutSpacing; }
+    //! Return layout border
+    const IntRect& getLayoutBorder() const { return mLayoutBorder; }
     //! Return number of child elements
     unsigned getNumChildren(bool recursive = false) const;
     //! Return child element by index
@@ -332,6 +359,16 @@ protected:
     bool mHovering;
     //! Userdata
     Variant mUserData;
+    //! Layout orientation
+    Orientation mLayoutOrientation;
+    //! Horizontal layout mode
+    LayoutMode mHorizontalLayoutMode;
+    //! Vertical layout mode
+    LayoutMode mVerticalLayoutMode;
+    //! Layout spacing
+    int mLayoutSpacing;
+    //! Layout borders
+    IntRect mLayoutBorder;
     
     //! Clipboard data
     static std::string sClipBoard;
@@ -339,10 +376,11 @@ protected:
 private:
     //! Return child elements recursively
     void getChildrenRecursive(std::vector<UIElement*>& dest) const;
-    //! Adjust size after laying out the child elements
-    void adjustSize(const IntVector2& neededSize, bool expand, bool contract);
-    //! Validate size against min & max size
-    void validateSize();
+    //! Calculate layout width for resizing the parent element
+    int calculateLayoutParentSize(const std::vector<int>& sizes, int begin, int end, int spacing);
+    //! Calculate child widths/positions in the layout
+    void calculateLayout(std::vector<int>& positions, std::vector<int>& sizes, const std::vector<int>& minSizes,
+        const std::vector<int>& maxSizes, int targetWidth, int begin, int end, int spacing);
     
     //! Position
     IntVector2 mPosition;
@@ -370,6 +408,10 @@ private:
     bool mDerivedOpacityDirty;
     //! Has color gradient flag
     bool mHasColorGradient;
+    //! Resize nesting level to prevent multiple events and endless loop
+    unsigned mResizeNestingLevel;
+    //! Layout update nesting level to prevent endless loop
+    unsigned mUpdateLayoutNestingLevel;
 };
 
 #endif // UI_UIELEMENT_H

+ 1 - 0
Engine/UI/Window.cpp

@@ -37,6 +37,7 @@ Window::Window(const std::string& name) :
     mDragMode(DRAG_NONE)
 {
     mBringToFront = true;
+    mClipChildren = true;
     mEnabled = true;
 }
 

+ 7 - 5
Examples/NinjaSnowWar/Game.cpp

@@ -1109,13 +1109,15 @@ void Game::updateCamera()
     dir = dir * Quaternion(mControls.mYaw, Vector3::sUp);
     dir = dir * Quaternion(mControls.mPitch, Vector3::sRight);
     // Force the player character rotation, so that there is no stuttering
-    bool alive = player->getHealth() > 0;
-    if (alive)
+    if (mClient)
     {
-        Quaternion rot(mControls.mYaw, Vector3::sUp);
-        body->setRotation(rot);
-        if (mClient)
+        bool alive = player->getHealth() > 0;
+        if (alive)
+        {
+            Quaternion rot(mControls.mYaw, Vector3::sUp);
+            body->setRotation(rot);
             body->Node::setRotation(rot); // This disables client-side rotation smoothing
+        }
     }
     
     Vector3 aimPoint = pos + Vector3(0,100,0);