Forráskód Böngészése

UI layout system refactoring.
DropDownList improvements.
UI bugfixes.

Lasse Öörni 15 éve
szülő
commit
9350f4056e

+ 6 - 7
Bin/Data/UI/TestLayout.xml

@@ -42,12 +42,11 @@
     <element type="DropDownList">
         <position value="10 160" />
         <size value="100 30" />
+        <resizepopup enable="true" />
+        <layout mode="horizontal" border="8 8 8 8" />
         <popupitem name="PopupItem1" />
         <popupitem name="PopupItem2" />
         <popupitem name="PopupItem3" />
-        <placeholder>
-            <alignment horizontal="center" vertical="center" />
-        </placeholder>
         <popup>
             <layout border="8 8 8 8" />
         </popup>
@@ -132,9 +131,9 @@
         <size value="200 16" />
     </element>
     <element type="Window" name="TestPopup">
-        <layout orientation="vertical" horizontal="resizeelement" vertical="resizeelement" spacing="4" border="8 8 8 8" />
+        <layout mode="vertical" spacing="4" border="8 8 8 8" />
         <element type="Menu">
-            <size value="80 16" />
+            <layout mode="vertical" />
             <element type="Text">
                 <text value="MenuItem3" />
                 <font name="cour.ttf" size="12" />
@@ -142,7 +141,7 @@
             </element>
         </element>
         <element type="Menu">
-            <size value="80 16" />
+            <layout mode="vertical" />
             <element type="Text">
                 <text value="MenuItem4" />
                 <font name="cour.ttf" size="12" />
@@ -150,7 +149,7 @@
             </element>
         </element>
         <element type="Menu">
-            <size value="80 16" />
+            <layout mode="vertical" />
             <element type="Text">
                 <text value="MenuItem5" />
                 <font name="cour.ttf" size="12" />

+ 2 - 2
Bin/Data/UI/TestLayout2.xml

@@ -2,11 +2,11 @@
     <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" />
+    <layout mode="vertical" 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" />
+        <layout mode="vertical" />
         <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." />

+ 5 - 7
Bin/Data/UI/TestLayout3.xml

@@ -2,11 +2,11 @@
     <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" />
+    <layout mode="vertical" 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" />
+        <layout mode="horizontal" spacing="8" />
         <element type="Button">
             <element type="Text">
                 <text value="TEST1" />
@@ -31,9 +31,10 @@
         </element>
     </element>
     <element type="Element">
-        <layout orientation="horizontal" horizontal="resizechildren" vertical="resizechildren" spacing="8" />
+        <layout mode="horizontal" spacing="8" />
     	<element type="Text" name="ScrollViewText">
 	        <position value="1 0" />
+	        <fixedwidth value="400" />
 	        <font name="times.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" />
@@ -47,9 +48,6 @@
         </element>
     	<element type="ScrollView">
 	        <contentelement name="ScrollViewText" />
-	        <scrollpanel>
-	        	<layout orientation="vertical" horizontal="resizechildren" vertical="resizechildren" border="1 1 1 1" spacing="0" />
-	        </scrollpanel>
 	    </element>
         <element type="Button">
             <element type="Text">
@@ -60,7 +58,7 @@
         </element>
     </element>
     <element type="Element">
-        <layout orientation="horizontal" horizontal="resizechildren" vertical="resizechildren" spacing="8" />
+        <layout mode="horizontal" spacing="8" />
         <element type="Button">
             <element type="Text">
                 <text value="TEST6" />

+ 5 - 4
Engine/Engine/Console.cpp

@@ -54,16 +54,17 @@ Console::Console(Engine* engine) :
             log->addListener(this);
         
         mBackground = new BorderImage();
-        mBackground->setWidth(uiRoot->getWidth());
+        mBackground->setFixedWidth(uiRoot->getWidth());
         mBackground->setColor(C_TOPLEFT, Color(0.0f, 0.25f, 0.0f, 0.75f));
         mBackground->setColor(C_TOPRIGHT, Color(0.0f, 0.25f, 0.0f, 0.75f));
         mBackground->setColor(C_BOTTOMLEFT, Color(0.25f, 0.75f, 0.25f, 0.75f));
         mBackground->setColor(C_BOTTOMRIGHT, Color(0.25f, 0.75f, 0.25f, 0.75f));
         mBackground->setBringToBack(false);
+        mBackground->setClipChildren(true);
         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));
+        mBackground->setLayout(LM_VERTICAL, 0, IntRect(4, 4, 4, 4));
         
         mLineEdit = new LineEdit();
         mLineEdit->setColor(Color(0.0f, 0.0f, 0.0f, 0.5f));
@@ -175,8 +176,8 @@ void Console::updateElements()
         mLineEdit->getTextElement()->setFont(mFont, mFontSize);
     }
     
-    mLineEdit->setHeight(mLineEdit->getTextElement()->getRowHeight());
-    mBackground->setWidth(width);
+    mLineEdit->setFixedHeight(mLineEdit->getTextElement()->getRowHeight());
+    mBackground->setFixedWidth(width);
 }
 
 void Console::handleTextFinished(StringHash eventType, VariantMap& eventData)

+ 4 - 4
Engine/Engine/RegisterTemplates.h

@@ -446,7 +446,9 @@ 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 setLayout(LayoutMode, int, const IntRect&)", asMETHOD(T, setLayout), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "void setLayoutSpacing(int)", asMETHOD(T, setLayoutSpacing), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "void setLayoutBorder(const IntRect&)", asMETHOD(T, setLayoutBorder), 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);
@@ -481,9 +483,7 @@ 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, "LayoutMode getLayoutMode() const", asMETHOD(T, getLayoutMode), 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);

+ 33 - 2
Engine/Engine/RegisterUI.cpp

@@ -25,6 +25,7 @@
 #include "Button.h"
 #include "CheckBox.h"
 #include "Cursor.h"
+#include "DropDownList.h"
 #include "Engine.h"
 #include "Font.h"
 #include "LineEdit.h"
@@ -74,8 +75,8 @@ static void registerUIElement(asIScriptEngine* engine)
     
     engine->RegisterEnum("LayoutMode");
     engine->RegisterEnumValue("LayoutMode", "LM_FREE", LM_FREE);
-    engine->RegisterEnumValue("LayoutMode", "LM_RESIZECHILDREN", LM_RESIZECHILDREN);
-    engine->RegisterEnumValue("LayoutMode", "LM_RESIZEELEMENT", LM_RESIZEELEMENT);
+    engine->RegisterEnumValue("LayoutMode", "LM_HORIZONTAL", LM_HORIZONTAL);
+    engine->RegisterEnumValue("LayoutMode", "LM_VERTICAL", LM_VERTICAL);
     
     registerUIElement<UIElement>(engine, "UIElement");
     
@@ -284,6 +285,35 @@ static void registerMenu(asIScriptEngine* engine)
     registerRefCasts<UIElement, Menu>(engine, "UIElement", "Menu");
 }
 
+static CScriptArray* DropDownListGetItems(DropDownList* ptr)
+{
+    std::vector<UIElement*> result = ptr->getItems();
+    return vectorToHandleArray<UIElement>(result, "array<UIElement@>");
+}
+
+static void registerDropDownList(asIScriptEngine* engine)
+{
+    registerButton<DropDownList>(engine, "DropDownList");
+    engine->RegisterObjectMethod("DropDownList", "void showPopup(bool)", asMETHOD(DropDownList, showPopup), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DropDownList", "void addItem(UIElement@+)", asMETHOD(DropDownList, addItem), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DropDownList", "void removeItem(UIElement@+)", asMETHODPR(DropDownList, removeItem, (UIElement*), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DropDownList", "void removeItem(uint)", asMETHODPR(DropDownList, removeItem, (unsigned), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DropDownList", "void removeAllItems()", asMETHOD(DropDownList, removeAllItems), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DropDownList", "void setSelection(uint)", asMETHOD(DropDownList, setSelection), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DropDownList", "void setResizePopup(bool)", asMETHOD(DropDownList, setResizePopup), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DropDownList", "UIElement@+ getPopup() const", asMETHOD(DropDownList, getPopup), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DropDownList", "bool getShowPopup() const", asMETHOD(DropDownList, getShowPopup), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DropDownList", "uint getNumItems() const", asMETHOD(DropDownList, getSelection), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DropDownList", "UIElement@+ getItem(uint) const", asMETHOD(DropDownList, getItem), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DropDownList", "array<UIElement@>@ getItems() const", asFUNCTION(DropDownListGetItems), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("DropDownList", "uint getSelection() const", asMETHOD(DropDownList, getSelection), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DropDownList", "UIElement@+ getSelectedItem() const", asMETHOD(DropDownList, getSelectedItem), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DropDownList", "ListView@+ getListView() const", asMETHOD(DropDownList, getListView), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DropDownList", "UIElement@+ getPlaceholder() const", asMETHOD(DropDownList, getPlaceholder), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DropDownList", "bool getResizePopup() const", asMETHOD(DropDownList, getResizePopup), asCALL_THISCALL);
+    registerRefCasts<UIElement, DropDownList>(engine, "UIElement", "DropDownList");
+}
+
 static void registerWindow(asIScriptEngine* engine)
 {
     registerBorderImage<Window>(engine, "Window");
@@ -401,6 +431,7 @@ void registerUILibrary(asIScriptEngine* engine)
     registerText(engine);
     registerLineEdit(engine);
     registerMenu(engine);
+    registerDropDownList(engine);
     registerWindow(engine);
     registerUI(engine);
 }

+ 34 - 9
Engine/UI/DropDownList.cpp

@@ -27,21 +27,20 @@
 #include "UIEvents.h"
 #include "Window.h"
 
+#include "DebugNew.h"
+
 DropDownList::DropDownList(const std::string& name) :
-    Menu(name)
+    Menu(name),
+    mResizePopup(false)
 {
     Window* window = new Window();
     setPopup(window);
-    setResizePopup(true);
     
     mListView = new ListView();
     mListView->setScrollBarsVisible(false, false);
-    // By default grow the popup and listview according to the items
-    mListView->getScrollPanel()->setLayout(O_VERTICAL, LM_RESIZECHILDREN, LM_RESIZEELEMENT);
-    mListView->setLayout(O_VERTICAL, LM_RESIZECHILDREN, LM_RESIZEELEMENT);
     mListView->setFocusMode(FM_RESETFOCUS);
+    mPopup->setLayout(LM_VERTICAL);
     mPopup->addChild(mListView);
-    mPopup->setLayout(O_VERTICAL, LM_RESIZECHILDREN, LM_RESIZEELEMENT);
     mPlaceholder = new UIElement();
     addChild(mPlaceholder);
     
@@ -79,6 +78,8 @@ void DropDownList::setStyle(const XMLElement& element, ResourceCache* cache)
     }
     if (element.hasChildElement("selection"))
         setSelection(element.getChildElement("selection").getInt("value"));
+    if (element.hasChildElement("resizepopup"))
+        setResizePopup(element.getChildElement("resizepopup").getBool("enable"));
 }
 
 void DropDownList::getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads, const IntRect& currentScissor)
@@ -101,10 +102,29 @@ void DropDownList::getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>
     }
 }
 
-void DropDownList::onResize()
+void DropDownList::onShowPopup()
 {
-    Menu::onResize();
-    setPopupOffset(0, getHeight());
+    // Resize the popup to match the size of the list content, and optionally match the button width
+    UIElement* content = mListView->getContentElement();
+    content->updateLayout();
+    const IntVector2& contentSize = content->getSize();
+    const IntRect& border = mPopup->getLayoutBorder();
+    mPopup->setSize(mResizePopup ? getWidth() : contentSize.mX + border.mLeft + border.mRight, contentSize.mY + border.mTop + 
+        border.mBottom);
+    
+    // Check if popup fits below the button. If not, show above instead
+    bool showAbove = false;
+    UIElement* root = getRootElement();
+    if (root)
+    {
+        const IntVector2& screenPos = getScreenPosition();
+        if ((screenPos.mY + getHeight() + mPopup->getHeight() > root->getHeight()) && (screenPos.mY - mPopup->getHeight() >= 0))
+            showAbove = true;
+    }
+    if (!showAbove)
+        setPopupOffset(0, getHeight());
+    else
+        setPopupOffset(0, -mPopup->getHeight());
 }
 
 void DropDownList::addItem(UIElement* item)
@@ -140,6 +160,11 @@ void DropDownList::setSelection(unsigned index)
     mListView->setSelection(index);
 }
 
+void DropDownList::setResizePopup(bool enable)
+{
+    mResizePopup = enable;
+}
+
 unsigned DropDownList::getNumItems() const
 {
     return mListView->getNumItems();

+ 9 - 3
Engine/UI/DropDownList.h

@@ -32,7 +32,7 @@ class DropDownList : public Menu
     
 public:
     //! Construct with name
-    DropDownList(const std::string& name);
+    DropDownList(const std::string& name = std::string());
     //! Destruct
     ~DropDownList();
     
@@ -40,8 +40,8 @@ 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();
+    //! React to the popup being shown
+    virtual void onShowPopup();
     
     //! Add item to the end of the list
     void addItem(UIElement* item);
@@ -53,6 +53,8 @@ public:
     void removeAllItems();
     //! Set selection
     void setSelection(unsigned index);
+    //! Set whether popup should be automatically resized to match the dropdown button width
+    void setResizePopup(bool enable);
     
     //! Return number of items
     unsigned getNumItems() const;
@@ -68,12 +70,16 @@ public:
     ListView* getListView() const { return mListView; }
     //! Return selected item placeholder element
     UIElement* getPlaceholder() const { return mPlaceholder; }
+    //! Return whether popup should be automatically resized
+    bool getResizePopup() const { return mResizePopup; }
     
 protected:
     //! Listview element
     SharedPtr<ListView> mListView;
     //! Selected item placeholder element
     SharedPtr<UIElement> mPlaceholder;
+    //! Resize popup flag
+    bool mResizePopup;
     
 private:
     //! Handle listview item selected event

+ 10 - 3
Engine/UI/ListView.cpp

@@ -36,9 +36,7 @@ ListView::ListView(const std::string& name) :
 {
     UIElement* container = new UIElement();
     container->setEnabled(true);
-    container->setLayout(O_VERTICAL, LM_RESIZECHILDREN, LM_RESIZEELEMENT);
-    
-    mScrollPanel->setLayout(O_HORIZONTAL, LM_RESIZECHILDREN, LM_FREE);
+    container->setLayout(LM_VERTICAL);
     setContentElement(container);
     
     subscribeToEvent(EVENT_TRYFOCUS, EVENT_HANDLER(ListView, handleTryFocus));
@@ -143,6 +141,15 @@ void ListView::onKey(int key, int buttons, int qualifiers)
     }
 }
 
+void ListView::onResize()
+{
+    ScrollView::onResize();
+    
+    // Set the content element width to match the scrollpanel
+    const IntRect& clipBorder = mScrollPanel->getClipBorder();
+    mContentElement->setWidth(mScrollPanel->getWidth() - clipBorder.mLeft - clipBorder.mRight);
+}
+
 void ListView::onFocus()
 {
     updateSelectionEffect();

+ 2 - 0
Engine/UI/ListView.h

@@ -43,6 +43,8 @@ public:
     virtual void onWheel(int delta, int buttons, int qualifiers);
     //! React to a key press
     virtual void onKey(int key, int buttons, int qualifiers);
+    //! React to resize
+    virtual void onResize();
     //! React to defocus
     virtual void onDefocus();
     //! React to focus

+ 3 - 14
Engine/UI/Menu.cpp

@@ -54,8 +54,6 @@ void Menu::setStyle(const XMLElement& element, ResourceCache* cache)
             setPopup(root->getChild(element.getChildElement("popup").getString("name"), true));
     }
     
-    if (element.hasChildElement("resizepopup"))
-        setResizePopup(element.getChildElement("resizepopup").getBool("enable"));
     if (element.hasChildElement("popupoffset"))
         setPopupOffset(element.getChildElement("popupoffset").getIntVector2("value"));
 }
@@ -77,10 +75,8 @@ void Menu::onClick(const IntVector2& position, const IntVector2& screenPosition,
     }
 }
 
-void Menu::onResize()
+void Menu::onShowPopup()
 {
-    if ((mPopup) && (mResizePopup))
-        mPopup->setWidth(getWidth());
 }
 
 void Menu::setPopup(UIElement* popup)
@@ -112,15 +108,6 @@ void Menu::setPopupOffset(int x, int y)
     mPopupOffset = IntVector2(x, y);
 }
 
-void Menu::setResizePopup(bool enable)
-{
-    if (enable != mResizePopup)
-    {
-        mResizePopup = enable;
-        onResize();
-    }
-}
-
 void Menu::showPopup(bool enable)
 {
     if (!mPopup)
@@ -133,6 +120,8 @@ void Menu::showPopup(bool enable)
     
     if (enable)
     {
+        onShowPopup();
+        
         mPopup->setPosition(getScreenPosition() + mPopupOffset);
         mPopup->setVisible(true);
         mPopup->setOrigin(this);

+ 2 - 8
Engine/UI/Menu.h

@@ -40,8 +40,8 @@ class Menu : public Button
     virtual void setStyle(const XMLElement& element, ResourceCache* cache);
     //! React to mouse click
     virtual void onClick(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers);
-    //! React to resize
-    virtual void onResize();
+    //! React to the popup being shown
+    virtual void onShowPopup();
     
     //! Set popup element to show on selection
     void setPopup(UIElement* element);
@@ -49,8 +49,6 @@ class Menu : public Button
     void setPopupOffset(const IntVector2& offset);
     //! Set popup element offset
     void setPopupOffset(int x, int y);
-    //! Set whether automatically resizes the popup to match width
-    void setResizePopup(bool enable);
     //! Force the popup to show or hide
     void showPopup(bool enable);
     
@@ -60,8 +58,6 @@ class Menu : public Button
     const IntVector2& getPopupOffset() const { return mPopupOffset; }
     //! Return whether popup is open
     bool getShowPopup() const { return mShowPopup; }
-    //! Return whether automatically resizes the popup
-    bool getResizePopup() const { return mResizePopup; }
     
 protected:
     //! Popup element
@@ -70,8 +66,6 @@ protected:
     IntVector2 mPopupOffset;
     //! Show popup flag
     bool mShowPopup;
-    //! Resize popup automatically flag
-    bool mResizePopup;
     
 private:
     //! Handle focus change attempt in case the popup needs to be hidden

+ 4 - 1
Engine/UI/ScrollBar.cpp

@@ -158,7 +158,10 @@ void ScrollBar::setOrientation(Orientation orientation)
     }
     
     onResize();
-    setLayout(orientation, LM_RESIZECHILDREN, LM_RESIZECHILDREN);
+    if (orientation == O_HORIZONTAL)
+        setLayout(LM_HORIZONTAL);
+    else
+        setLayout(LM_VERTICAL);
 }
 
 void ScrollBar::setRange(float range)

+ 1 - 1
Engine/UI/ScrollView.cpp

@@ -102,7 +102,7 @@ void ScrollView::onWheel(int delta, int buttons, int qualifiers)
     if (delta > 0)
         mVerticalScrollBar->stepBack();
     if (delta < 0)
-        mHorizontalScrollBar->stepForward();
+        mVerticalScrollBar->stepForward();
 }
 
 void ScrollView::onKey(int key, int buttons, int qualifiers)

+ 4 - 3
Engine/UI/Text.cpp

@@ -102,11 +102,12 @@ void Text::getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads,
         batch.mBlendMode = BLEND_ALPHA;
         batch.mScissor = currentScissor;
         batch.mTexture = 0;
-        batch.addQuad(*this, 0, 0, getWidth(), getHeight(), 0, 0, 0, 0, mSelected ? mSelectionColor : mHoverColor);
+        batch.addQuad(*this, 0, 0, getWidth(), getHeight(), 0, 0, 0, 0, (mSelected && (mSelectionColor.mA > 0.0f)) ? mSelectionColor :
+            mHoverColor);
         UIBatch::addOrMerge(batch, batches);
     }
     
-    // Partial Selection batch
+    // Partial selection batch
     if ((!mSelected) && (mSelectionLength) && (mCharSizes.size() >= mSelectionStart + mSelectionLength) && (mSelectionColor.mA > 0.0f))
     {
         UIBatch batch;
@@ -434,7 +435,7 @@ void Text::updateText(bool inResize)
     if (!mWordwrap)
         setMinSize(width, height);
     else
-        setMinSize(0, height);
+        setMinHeight(height);
 }
 
 void Text::validateSelection()

+ 102 - 169
Engine/UI/UIElement.cpp

@@ -46,9 +46,7 @@ UIElement::UIElement(const std::string& name) :
     mSelected(false),
     mVisible(true),
     mHovering(false),
-    mLayoutOrientation(O_VERTICAL),
-    mHorizontalLayoutMode(LM_FREE),
-    mVerticalLayoutMode(LM_FREE),
+    mLayoutMode(LM_FREE),
     mLayoutSpacing(0),
     mLayoutBorder(IntRect::sZero),
     mResizeNestingLevel(0),
@@ -98,10 +96,22 @@ void UIElement::setStyle(const XMLElement& element, ResourceCache* cache)
         setHeight(element.getChildElement("height").getInt("value"));
     if (element.hasChildElement("minsize"))
         setMinSize(element.getChildElement("minsize").getIntVector2("value"));
+    if (element.hasChildElement("minwidth"))
+        setMinWidth(element.getChildElement("minwidth").getInt("value"));
+    if (element.hasChildElement("minheight"))
+        setMinHeight(element.getChildElement("minheight").getInt("value"));
     if (element.hasChildElement("maxsize"))
         setMaxSize(element.getChildElement("maxsize").getIntVector2("value"));
+    if (element.hasChildElement("maxwidth"))
+        setMinWidth(element.getChildElement("maxwidth").getInt("value"));
+    if (element.hasChildElement("maxheight"))
+        setMinHeight(element.getChildElement("maxheight").getInt("value"));
     if (element.hasChildElement("fixedsize"))
         setFixedSize(element.getChildElement("fixedsize").getIntVector2("value"));
+    if (element.hasChildElement("fixedwidth"))
+        setFixedWidth(element.getChildElement("fixedwidth").getInt("value"));
+    if (element.hasChildElement("fixedheight"))
+        setFixedHeight(element.getChildElement("fixedheight").getInt("value"));
     if (element.hasChildElement("alignment"))
     {
         XMLElement alignElem = element.getChildElement("alignment");
@@ -176,41 +186,20 @@ void UIElement::setStyle(const XMLElement& element, ResourceCache* cache)
     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;
+        std::string mode = layoutElem.getStringLower("mode");
+        if (mode == "free")
+            mLayoutMode = LM_FREE;
+        if ((mode == "horizontal") || (mode == "h"))
+            mLayoutMode = LM_HORIZONTAL;
+        if ((mode == "vertical") || (mode == "v"))
+            mLayoutMode = LM_VERTICAL;
         
         if (layoutElem.hasAttribute("spacing"))
             mLayoutSpacing = max(layoutElem.getInt("spacing"), 0);
         if (layoutElem.hasAttribute("border"))
-            mLayoutBorder = layoutElem.getIntRect("border");
-        
-        updateLayout();
+            setLayoutBorder(layoutElem.getIntRect("border"));
+        else
+            updateLayout();
     }
 }
 
@@ -624,20 +613,29 @@ void UIElement::setStyleAuto(XMLFile* file, ResourceCache* cache)
     setStyle(element, cache);
 }
 
-void UIElement::setLayout(Orientation orientation, LayoutMode horizontal, LayoutMode vertical, int spacing, const IntRect& border)
+void UIElement::setLayout(LayoutMode mode, int spacing, const IntRect& border)
 {
-    mLayoutOrientation = orientation;
-    mHorizontalLayoutMode = horizontal;
-    mVerticalLayoutMode = vertical;
+    mLayoutMode = mode;
     mLayoutSpacing = max(spacing, 0);
-    mLayoutBorder = border;
-    
+    mLayoutBorder = IntRect(max(border.mLeft, 0), max(border.mTop, 0), max(border.mRight, 0), max(border.mBottom, 0));
+    updateLayout();
+}
+
+void UIElement::setLayoutSpacing(int spacing)
+{
+    mLayoutSpacing = max(spacing, 0);
+    updateLayout();
+}
+
+void UIElement::setLayoutBorder(const IntRect& border)
+{
+    mLayoutBorder = IntRect(max(border.mLeft, 0), max(border.mTop, 0), max(border.mRight, 0), max(border.mBottom, 0));
     updateLayout();
 }
 
 void UIElement::updateLayout()
 {
-    if ((mUpdateLayoutNestingLevel) || ((mHorizontalLayoutMode == LM_FREE) && (mVerticalLayoutMode == LM_FREE)))
+    if ((mLayoutMode == LM_FREE) || (mUpdateLayoutNestingLevel))
         return;
     
     ++mUpdateLayoutNestingLevel;
@@ -647,146 +645,78 @@ void UIElement::updateLayout()
     std::vector<int> minSizes;
     std::vector<int> maxSizes;
     
-    if (mLayoutOrientation == O_HORIZONTAL)
+    if (mLayoutMode == LM_HORIZONTAL)
     {
-        int maxChildHeight = 0;
-        int maxChildMinHeight = 0;
+        int minChildHeight = 0;
         
-        if (mHorizontalLayoutMode == LM_RESIZEELEMENT)
+        for (unsigned i = 0; i < mChildren.size(); ++i)
         {
-            for (unsigned i = 0; i < mChildren.size(); ++i)
-            {
-                if (!mChildren[i]->isVisible())
-                    continue;
-                sizes.push_back(mChildren[i]->getWidth());
-                maxChildHeight = max(maxChildHeight, mChildren[i]->getHeight());
-                maxChildMinHeight = max(maxChildMinHeight, mChildren[i]->getMinHeight());
-            }
-            
-            if (mVerticalLayoutMode == LM_RESIZEELEMENT)
-                setMinHeight(maxChildMinHeight + mLayoutBorder.mTop + mLayoutBorder.mBottom);
-            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)
-            {
-                if (!mChildren[i]->isVisible())
-                    continue;
-                mChildren[i]->setHorizontalAlignment(HA_LEFT);
-                mChildren[i]->setPosition(position, getLayoutChildPosition(mChildren[i]).mY);
-                if (mVerticalLayoutMode == LM_RESIZECHILDREN)
-                    mChildren[i]->setHeight(getHeight() - mLayoutBorder.mTop - mLayoutBorder.mBottom);
-                position += mChildren[i]->getWidth();
-                position += mLayoutSpacing;
-            }
+            if (!mChildren[i]->isVisible())
+                continue;
+            positions.push_back(0);
+            sizes.push_back(mChildren[i]->getWidth());
+            minSizes.push_back(mChildren[i]->getMinWidth());
+            maxSizes.push_back(mChildren[i]->getMaxWidth());
+            minChildHeight = max(minChildHeight, mChildren[i]->getMinHeight());
         }
         
-        if (mHorizontalLayoutMode == LM_RESIZECHILDREN)
+        calculateLayout(positions, sizes, minSizes, maxSizes, getWidth(), mLayoutBorder.mLeft, mLayoutBorder.mRight,
+            mLayoutSpacing);
+        
+        int width = calculateLayoutParentSize(sizes, mLayoutBorder.mLeft, mLayoutBorder.mRight, mLayoutSpacing);
+        int height = max(getHeight(), minChildHeight + mLayoutBorder.mTop + mLayoutBorder.mBottom);
+        int minWidth = calculateLayoutParentSize(minSizes, mLayoutBorder.mLeft, mLayoutBorder.mRight, mLayoutSpacing);
+        int minHeight = minChildHeight + mLayoutBorder.mTop + mLayoutBorder.mBottom;
+        setMinSize(minWidth, minHeight);
+        setSize(width, height);
+        
+        unsigned j = 0;
+        for (unsigned i = 0; i < mChildren.size(); ++i)
         {
-            for (unsigned i = 0; i < mChildren.size(); ++i)
-            {
-                if (!mChildren[i]->isVisible())
-                    continue;
-                positions.push_back(0);
-                sizes.push_back(mChildren[i]->getWidth());
-                minSizes.push_back(mChildren[i]->getMinWidth());
-                maxSizes.push_back(mChildren[i]->getMaxWidth());
-                maxChildHeight = max(maxChildHeight, mChildren[i]->getHeight());
-                maxChildMinHeight = max(maxChildMinHeight, mChildren[i]->getMinHeight());
-            }
-            
-            if (mVerticalLayoutMode == LM_RESIZEELEMENT)
-            {
-                setMinHeight(maxChildMinHeight + mLayoutBorder.mTop + mLayoutBorder.mBottom);
-                setHeight(maxChildHeight + mLayoutBorder.mTop + mLayoutBorder.mBottom);
-            }
-            
-            calculateLayout(positions, sizes, minSizes, maxSizes, getWidth(), mLayoutBorder.mLeft, mLayoutBorder.mRight,
-                mLayoutSpacing);
-            
-            unsigned j = 0;
-            for (unsigned i = 0; i < mChildren.size(); ++i)
-            {
-                if (!mChildren[i]->isVisible())
-                    continue;
-                mChildren[i]->setHorizontalAlignment(HA_LEFT);
-                mChildren[i]->setPosition(positions[j], getLayoutChildPosition(mChildren[i]).mY);
-                mChildren[i]->setSize(sizes[j], mVerticalLayoutMode == LM_RESIZECHILDREN ? getHeight() - mLayoutBorder.mTop -
-                    mLayoutBorder.mBottom : mChildren[i]->getHeight());
-                ++j;
-            }
+            if (!mChildren[i]->isVisible())
+                continue;
+            mChildren[i]->setHorizontalAlignment(HA_LEFT);
+            mChildren[i]->setPosition(positions[j], getLayoutChildPosition(mChildren[i]).mY);
+            mChildren[i]->setSize(sizes[j], height - mLayoutBorder.mTop - mLayoutBorder.mBottom);
+            ++j;
         }
     }
     
-    if (mLayoutOrientation == O_VERTICAL)
+    if (mLayoutMode == LM_VERTICAL)
     {
-        int maxChildWidth = 0;
-        int maxChildMinWidth = 0;
+        int minChildWidth = 0;
+        int maxChildWidth = M_MAX_INT;
         
-        if (mVerticalLayoutMode == LM_RESIZEELEMENT)
+        for (unsigned i = 0; i < mChildren.size(); ++i)
         {
-            for (unsigned i = 0; i < mChildren.size(); ++i)
-            {
-                if (!mChildren[i]->isVisible())
-                    continue;
-                sizes.push_back(mChildren[i]->getHeight());
-                maxChildWidth = max(maxChildWidth, mChildren[i]->getWidth());
-                maxChildMinWidth = max(maxChildMinWidth, mChildren[i]->getMinWidth());
-            }
-            
-            if (mHorizontalLayoutMode == LM_RESIZEELEMENT)
-                setMinWidth(maxChildMinWidth + mLayoutBorder.mLeft + mLayoutBorder.mRight);
-            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)
-            {
-                if (!mChildren[i]->isVisible())
-                    continue;
-                mChildren[i]->setVerticalAlignment(VA_TOP);
-                mChildren[i]->setPosition(getLayoutChildPosition(mChildren[i]).mX, position);
-                if (mHorizontalLayoutMode == LM_RESIZECHILDREN)
-                    mChildren[i]->setWidth(getWidth() - mLayoutBorder.mLeft - mLayoutBorder.mRight);
-                position += mChildren[i]->getHeight();
-                position += mLayoutSpacing;
-            }
+            if (!mChildren[i]->isVisible())
+                continue;
+            positions.push_back(0);
+            sizes.push_back(mChildren[i]->getHeight());
+            minSizes.push_back(mChildren[i]->getMinHeight());
+            maxSizes.push_back(mChildren[i]->getMaxHeight());
+            minChildWidth = max(minChildWidth, mChildren[i]->getMinWidth());
         }
-        else
+        
+        calculateLayout(positions, sizes, minSizes, maxSizes, getHeight(), mLayoutBorder.mTop, mLayoutBorder.mBottom,
+            mLayoutSpacing);
+        
+        int height = calculateLayoutParentSize(sizes, mLayoutBorder.mTop, mLayoutBorder.mBottom, mLayoutSpacing);
+        int width = max(getWidth(), minChildWidth + mLayoutBorder.mLeft + mLayoutBorder.mRight);
+        int minHeight = calculateLayoutParentSize(minSizes, mLayoutBorder.mTop, mLayoutBorder.mBottom, mLayoutSpacing);
+        int minWidth = minChildWidth + mLayoutBorder.mLeft + mLayoutBorder.mRight;
+        setMinSize(minWidth, minHeight);
+        setSize(width, height);
+        
+        unsigned j = 0;
+        for (unsigned i = 0; i < mChildren.size(); ++i)
         {
-            for (unsigned i = 0; i < mChildren.size(); ++i)
-            {
-                if (!mChildren[i]->isVisible())
-                    continue;
-                positions.push_back(0);
-                sizes.push_back(mChildren[i]->getHeight());
-                minSizes.push_back(mChildren[i]->getMinHeight());
-                maxSizes.push_back(mChildren[i]->getMaxHeight());
-                maxChildWidth = max(maxChildWidth, mChildren[i]->getWidth());
-                maxChildMinWidth = max(maxChildMinWidth, mChildren[i]->getMinWidth());
-            }
-            
-            if (mHorizontalLayoutMode == LM_RESIZEELEMENT)
-            {
-                setMinWidth(maxChildMinWidth + mLayoutBorder.mLeft + mLayoutBorder.mRight);
-                setWidth(maxChildWidth + mLayoutBorder.mLeft + mLayoutBorder.mRight);
-            }
-            
-            calculateLayout(positions, sizes, minSizes, maxSizes, getHeight(), mLayoutBorder.mTop, mLayoutBorder.mBottom,
-                mLayoutSpacing);
-            
-            unsigned j = 0;
-            for (unsigned i = 0; i < mChildren.size(); ++i)
-            {
-                if (!mChildren[i]->isVisible())
-                    continue;
-                mChildren[i]->setVerticalAlignment(VA_TOP);
-                mChildren[i]->setPosition(getLayoutChildPosition(mChildren[i]).mX, positions[j]);
-                mChildren[i]->setSize(mHorizontalLayoutMode == LM_RESIZECHILDREN ? getWidth() - mLayoutBorder.mLeft -
-                    mLayoutBorder.mRight : mChildren[i]->getWidth(), sizes[j]);
-                ++j;
-            }
+            if (!mChildren[i]->isVisible())
+                continue;
+            mChildren[i]->setVerticalAlignment(VA_TOP);
+            mChildren[i]->setPosition(getLayoutChildPosition(mChildren[i]).mX, positions[j]);
+            mChildren[i]->setSize(width - mLayoutBorder.mLeft - mLayoutBorder.mRight, sizes[j]);
+            ++j;
         }
     }
     
@@ -1100,6 +1030,9 @@ int UIElement::calculateLayoutParentSize(const std::vector<int>& sizes, int begi
     int width = begin + end;
     for (unsigned i = 0; i < sizes.size(); ++i)
     {
+        // If we are calculating maximum size, and the default is specified, do not overflow it
+        if (sizes[i] == M_MAX_INT)
+            return M_MAX_INT;
         width += sizes[i];
         if (i < sizes.size() - 1)
             width += spacing;
@@ -1108,12 +1041,12 @@ int UIElement::calculateLayoutParentSize(const std::vector<int>& sizes, int begi
 }
 
 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)
+        const std::vector<int>& maxSizes, int targetSize, int begin, int end, int spacing)
 {
     unsigned numChildren = sizes.size();
     if (!numChildren)
         return;
-    int targetTotalSize = targetWidth - begin - end - (numChildren - 1) * spacing;
+    int targetTotalSize = targetSize - begin - end - (numChildren - 1) * spacing;
     if (targetTotalSize < 0)
         targetTotalSize = 0;
     int targetChildSize = targetTotalSize / numChildren;

+ 14 - 16
Engine/UI/UIElement.h

@@ -80,9 +80,12 @@ enum FocusMode
 //! Layout operation mode
 enum LayoutMode
 {
+    //! No layout operations will be performed
     LM_FREE = 0,
-    LM_RESIZECHILDREN,
-    LM_RESIZEELEMENT
+    //! Layout child elements horizontally and resize them to fit. Resize element if necessary
+    LM_HORIZONTAL,
+    //! Layout child elements vertically and resize them to fit. Resize element if necessary
+    LM_VERTICAL
 };
 
 class ResourceCache;
@@ -209,8 +212,11 @@ public:
     //! Set style from an XML file. Find the style element automatically
     void setStyleAuto(XMLFile* file, ResourceCache* cache);
     //! Set layout
-    void setLayout(Orientation orientation, LayoutMode horizontal, LayoutMode vertical, int spacing = 0,
-        const IntRect& border = IntRect::sZero);
+    void setLayout(LayoutMode mode, int spacing = 0, const IntRect& border = IntRect::sZero);
+    //! Set layout spacing
+    void setLayoutSpacing(int spacing);
+    //! Set layout border
+    void setLayoutBorder(const IntRect& border);
     //! Manually update layout. Should not be necessary in most cases, but is provided for completeness
     void updateLayout();
     //! Bring UI element to front
@@ -280,12 +286,8 @@ 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 mode
+    LayoutMode getLayoutMode() const { return mLayoutMode; }
     //! Return layout spacing
     int getLayoutSpacing() const { return mLayoutSpacing; }
     //! Return layout border
@@ -371,12 +373,8 @@ protected:
     bool mHovering;
     //! Userdata
     Variant mUserData;
-    //! Layout orientation
-    Orientation mLayoutOrientation;
-    //! Horizontal layout mode
-    LayoutMode mHorizontalLayoutMode;
-    //! Vertical layout mode
-    LayoutMode mVerticalLayoutMode;
+    //! Layout mode
+    LayoutMode mLayoutMode;
     //! Layout spacing
     int mLayoutSpacing;
     //! Layout borders