Procházet zdrojové kódy

Initial DropDownList implementation.
Renamed MenuItem to Menu.
Bugfixes.

Lasse Öörni před 15 roky
rodič
revize
dd24a35ab6

+ 81 - 8
Bin/Data/UI/DefaultStyle.xml

@@ -22,6 +22,80 @@
         <imagerect value="0 0 12 24" />
         <imagerect value="0 0 12 24" />
         <hotspot value="0 0" />
         <hotspot value="0 0" />
     </element>
     </element>
+    <element type="DropDownList">
+        <texture name="Textures/UI.png" />
+        <imagerect value="16 0 32 16" />
+        <border value="4 4 4 4" />
+        <pressedoffset value="16 0" />
+        <hoveroffset value="0 16" />
+        <labeloffset value="-1 1" />
+        <popup>
+            <texture name="Textures/UI.png" />
+            <imagerect value="48 0 64 16" />
+            <border value="3 3 3 3" />
+        </popup>
+        <listview>
+            <horizontalscrollbar>
+                <height value="12" />
+                <backbutton>
+                    <texture name="Textures/UI.png" />
+                    <imagerect horizontal="32 32 48 48" vertical="0 32 16 48" />
+                    <border value="3 3 3 3" />
+                    <pressedoffset value="64 0" />
+                    <hoveroffset value="0 16" />
+                </backbutton>
+                <forwardbutton>
+                    <texture name="Textures/UI.png" />
+                    <imagerect horizontal="48 32 64 48" vertical="16 32 32 48" />
+                    <border value="3 3 3 3" />
+                    <pressedoffset value="64 0" />
+                    <hoveroffset value="0 16" />
+                </forwardbutton>
+                <slider>
+                    <texture name="Textures/UI.png" />
+                    <imagerect value="48 0 64 16" />
+                    <border value="3 3 3 3" />
+                    <knob>
+                        <texture name="Textures/UI.png" />
+                        <imagerect value="16 0 32 16" />
+                        <border value="4 4 4 4" />
+                        <hoveroffset value="0 16" />
+                    </knob>
+                </slider>
+            </horizontalscrollbar>
+            <verticalscrollbar>
+                <width value="12" />
+                <backbutton>
+                    <texture name="Textures/UI.png" />
+                    <imagerect horizontal="32 32 48 48" vertical="0 32 16 48" />
+                    <border value="3 3 3 3" />
+                    <pressedoffset value="64 0" />
+                    <hoveroffset value="0 16" />
+                </backbutton>
+                <forwardbutton>
+                    <texture name="Textures/UI.png" />
+                    <imagerect horizontal="48 32 64 48" vertical="16 32 32 48" />
+                    <border value="3 3 3 3" />
+                    <pressedoffset value="64 0" />
+                    <hoveroffset value="0 16" />
+                </forwardbutton>
+                <slider>
+                    <texture name="Textures/UI.png" />
+                    <imagerect value="48 0 64 16" />
+                    <border value="3 3 3 3" />
+                    <knob>
+                        <texture name="Textures/UI.png" />
+                        <imagerect value="16 0 32 16" />
+                        <border value="4 4 4 4" />
+                        <hoveroffset value="0 16" />
+                    </knob>
+                </slider>
+            </verticalscrollbar>
+            <scrollpanel>
+                <color value="0 0 0 0" />
+            </scrollpanel>
+        </listview>
+    </element>
     <element type="LineEdit">
     <element type="LineEdit">
         <texture name="Textures/UI.png" />
         <texture name="Textures/UI.png" />
         <imagerect value="112 0 128 16" />
         <imagerect value="112 0 128 16" />
@@ -39,13 +113,6 @@
             <imagerect value="12 0 16 16" />
             <imagerect value="12 0 16 16" />
         </cursor>
         </cursor>
     </element>
     </element>
-    <element type="MenuItem">
-        <texture name="Textures/UI.png" />
-        <imagerect value="96 0 112 16" />
-        <border value="2 2 2 2" />
-        <pressedoffset value="16 0" />
-        <hoveroffset value="0 16" />
-    </element>
     <element type="ListView">
     <element type="ListView">
         <horizontalscrollbar>
         <horizontalscrollbar>
             <height value="12" />
             <height value="12" />
@@ -110,7 +177,13 @@
             <clipborder value="1 1 1 1" />
             <clipborder value="1 1 1 1" />
         </scrollpanel>
         </scrollpanel>
     </element>
     </element>
-
+    <element type="Menu">
+        <texture name="Textures/UI.png" />
+        <imagerect value="96 0 112 16" />
+        <border value="2 2 2 2" />
+        <pressedoffset value="16 0" />
+        <hoveroffset value="0 16" />
+    </element>
     <element type="ScrollBar">
     <element type="ScrollBar">
         <backbutton>
         <backbutton>
             <size value="12 12" />
             <size value="12 12" />

+ 35 - 5
Bin/Data/UI/TestLayout.xml

@@ -23,6 +23,36 @@
     <element type="CheckBox">
     <element type="CheckBox">
         <position value="40 130" />
         <position value="40 130" />
     </element>
     </element>
+
+    <element type="Text" name="PopupItem1">
+        <font name="cour.ttf" size="12" />
+        <text value="Deferred" />
+        <hovercolor value="0.5 0.75 0.5" />
+    </element>
+    <element type="Text" name="PopupItem2">
+        <font name="cour.ttf" size="12" />
+        <text value="Prepass" />
+        <hovercolor value="0.5 0.75 0.5" />
+    </element>
+    <element type="Text" name="PopupItem3">
+        <font name="cour.ttf" size="12" />
+        <text value="Forward" />
+        <hovercolor value="0.5 0.75 0.5" />
+    </element>
+    <element type="DropDownList">
+        <position value="10 160" />
+        <size value="100 30" />
+        <popupitem name="PopupItem1" />
+        <popupitem name="PopupItem2" />
+        <popupitem name="PopupItem3" />
+        <placeholder>
+            <alignment horizontal="center" vertical="center" />
+        </placeholder>
+        <popup>
+            <layout border="8 8 8 8" />
+        </popup>
+    </element>
+
     <element type="Text" name="Item1">
     <element type="Text" name="Item1">
         <font name="cour.ttf" size="12" />
         <font name="cour.ttf" size="12" />
         <text value="Audio" />
         <text value="Audio" />
@@ -103,7 +133,7 @@
     </element>
     </element>
     <element type="Window" name="TestPopup">
     <element type="Window" name="TestPopup">
         <layout orientation="vertical" horizontal="resizeelement" vertical="resizeelement" spacing="4" border="8 8 8 8" />
         <layout orientation="vertical" horizontal="resizeelement" vertical="resizeelement" spacing="4" border="8 8 8 8" />
-        <element type="MenuItem">
+        <element type="Menu">
             <size value="80 16" />
             <size value="80 16" />
             <element type="Text">
             <element type="Text">
                 <text value="MenuItem3" />
                 <text value="MenuItem3" />
@@ -111,7 +141,7 @@
                 <alignment horizontal="center" vertical="center" />
                 <alignment horizontal="center" vertical="center" />
             </element>
             </element>
         </element>
         </element>
-        <element type="MenuItem">
+        <element type="Menu">
             <size value="80 16" />
             <size value="80 16" />
             <element type="Text">
             <element type="Text">
                 <text value="MenuItem4" />
                 <text value="MenuItem4" />
@@ -119,7 +149,7 @@
                 <alignment horizontal="center" vertical="center" />
                 <alignment horizontal="center" vertical="center" />
             </element>
             </element>
         </element>
         </element>
-        <element type="MenuItem">
+        <element type="Menu">
             <size value="80 16" />
             <size value="80 16" />
             <element type="Text">
             <element type="Text">
                 <text value="MenuItem5" />
                 <text value="MenuItem5" />
@@ -128,7 +158,7 @@
             </element>
             </element>
         </element>
         </element>
     </element>
     </element>
-    <element type="MenuItem">
+    <element type="Menu">
         <position value="150 10" />
         <position value="150 10" />
         <size value="80 16" />
         <size value="80 16" />
         <popup name="TestPopup" />
         <popup name="TestPopup" />
@@ -139,7 +169,7 @@
             <alignment horizontal="center" vertical="center" />
             <alignment horizontal="center" vertical="center" />
         </element>
         </element>
     </element>
     </element>
-    <element type="MenuItem">
+    <element type="Menu">
         <position value="150 40" />
         <position value="150 40" />
         <size value="80 16" />
         <size value="80 16" />
         <element type="Text">
         <element type="Text">

+ 12 - 12
Engine/Engine/RegisterUI.cpp

@@ -29,7 +29,7 @@
 #include "Font.h"
 #include "Font.h"
 #include "LineEdit.h"
 #include "LineEdit.h"
 #include "ListView.h"
 #include "ListView.h"
-#include "MenuItem.h"
+#include "Menu.h"
 #include "RegisterTemplates.h"
 #include "RegisterTemplates.h"
 #include "ScrollBar.h"
 #include "ScrollBar.h"
 #include "ScrollView.h"
 #include "ScrollView.h"
@@ -271,17 +271,17 @@ static void registerLineEdit(asIScriptEngine* engine)
     registerRefCasts<UIElement, LineEdit>(engine, "UIElement", "LineEdit");
     registerRefCasts<UIElement, LineEdit>(engine, "UIElement", "LineEdit");
 }
 }
 
 
-static void registerMenuItem(asIScriptEngine* engine)
+static void registerMenu(asIScriptEngine* engine)
 {
 {
-    registerButton<MenuItem>(engine, "MenuItem");
-    engine->RegisterObjectMethod("MenuItem", "void setPopup(UIElement@+)", asMETHOD(MenuItem, setPopup), asCALL_THISCALL);
-    engine->RegisterObjectMethod("MenuItem", "void setPopupOffset(const IntVector2& in)", asMETHODPR(MenuItem, setPopupOffset, (const IntVector2&), void), asCALL_THISCALL);
-    engine->RegisterObjectMethod("MenuItem", "void setPopupOffset(int, int)", asMETHODPR(MenuItem, setPopupOffset, (int, int), void), asCALL_THISCALL);
-    engine->RegisterObjectMethod("MenuItem", "void showPopup(bool)", asMETHOD(MenuItem, showPopup), asCALL_THISCALL);
-    engine->RegisterObjectMethod("MenuItem", "UIElement@+ getPopup() const", asMETHOD(MenuItem, getPopup), asCALL_THISCALL);
-    engine->RegisterObjectMethod("MenuItem", "const IntVector2& getPopupOffset() const", asMETHOD(MenuItem, getPopupOffset), asCALL_THISCALL);
-    engine->RegisterObjectMethod("MenuItem", "bool getShowPopup() const", asMETHOD(MenuItem, getShowPopup), asCALL_THISCALL);
-    registerRefCasts<UIElement, MenuItem>(engine, "UIElement", "MenuItem");
+    registerButton<Menu>(engine, "Menu");
+    engine->RegisterObjectMethod("Menu", "void setPopup(UIElement@+)", asMETHOD(Menu, setPopup), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Menu", "void setPopupOffset(const IntVector2& in)", asMETHODPR(Menu, setPopupOffset, (const IntVector2&), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Menu", "void setPopupOffset(int, int)", asMETHODPR(Menu, setPopupOffset, (int, int), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Menu", "void showPopup(bool)", asMETHOD(Menu, showPopup), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Menu", "UIElement@+ getPopup() const", asMETHOD(Menu, getPopup), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Menu", "const IntVector2& getPopupOffset() const", asMETHOD(Menu, getPopupOffset), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Menu", "bool getShowPopup() const", asMETHOD(Menu, getShowPopup), asCALL_THISCALL);
+    registerRefCasts<UIElement, Menu>(engine, "UIElement", "Menu");
 }
 }
 
 
 static void registerWindow(asIScriptEngine* engine)
 static void registerWindow(asIScriptEngine* engine)
@@ -400,7 +400,7 @@ void registerUILibrary(asIScriptEngine* engine)
     registerListView(engine);
     registerListView(engine);
     registerText(engine);
     registerText(engine);
     registerLineEdit(engine);
     registerLineEdit(engine);
-    registerMenuItem(engine);
+    registerMenu(engine);
     registerWindow(engine);
     registerWindow(engine);
     registerUI(engine);
     registerUI(engine);
 }
 }

+ 6 - 3
Engine/UI/BaseUIElementFactory.cpp

@@ -26,9 +26,10 @@
 #include "Button.h"
 #include "Button.h"
 #include "CheckBox.h"
 #include "CheckBox.h"
 #include "Cursor.h"
 #include "Cursor.h"
+#include "DropDownList.h"
 #include "LineEdit.h"
 #include "LineEdit.h"
 #include "ListView.h"
 #include "ListView.h"
-#include "MenuItem.h"
+#include "Menu.h"
 #include "ScrollBar.h"
 #include "ScrollBar.h"
 #include "ScrollView.h"
 #include "ScrollView.h"
 #include "Slider.h"
 #include "Slider.h"
@@ -45,12 +46,14 @@ UIElement* BaseUIElementFactory::createElement(ShortStringHash type, const std::
         return new CheckBox(name);
         return new CheckBox(name);
     if (type == Cursor::getTypeStatic())
     if (type == Cursor::getTypeStatic())
         return new Cursor(name);
         return new Cursor(name);
+    if (type == DropDownList::getTypeStatic())
+        return new DropDownList(name);
     if (type == LineEdit::getTypeStatic())
     if (type == LineEdit::getTypeStatic())
         return new LineEdit(name);
         return new LineEdit(name);
     if (type == ListView::getTypeStatic())
     if (type == ListView::getTypeStatic())
         return new ListView(name);
         return new ListView(name);
-    if (type == MenuItem::getTypeStatic())
-        return new MenuItem(name);
+    if (type == Menu::getTypeStatic())
+        return new Menu(name);
     if (type == ScrollBar::getTypeStatic())
     if (type == ScrollBar::getTypeStatic())
         return new ScrollBar(name);
         return new ScrollBar(name);
     if (type == ScrollView::getTypeStatic())
     if (type == ScrollView::getTypeStatic())

+ 1 - 1
Engine/UI/BorderImage.h

@@ -29,7 +29,7 @@
 class Texture;
 class Texture;
 class Texture2D;
 class Texture2D;
 
 
-//! An UI element with an image that optionally has a border
+//! An image UI element with optional border
 class BorderImage : public UIElement
 class BorderImage : public UIElement
 {
 {
     DEFINE_TYPE(BorderImage);
     DEFINE_TYPE(BorderImage);

+ 1 - 1
Engine/UI/Button.h

@@ -26,7 +26,7 @@
 
 
 #include "BorderImage.h"
 #include "BorderImage.h"
 
 
-//! An image that reacts to mouse presses
+//! A pushbutton image
 class Button : public BorderImage
 class Button : public BorderImage
 {
 {
     DEFINE_TYPE(Button);
     DEFINE_TYPE(Button);

+ 185 - 0
Engine/UI/DropDownList.cpp

@@ -0,0 +1,185 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2011 Lasse Öörni
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "Precompiled.h"
+#include "DropDownList.h"
+#include "ListView.h"
+#include "UIEvents.h"
+#include "Window.h"
+
+DropDownList::DropDownList(const std::string& name) :
+    Menu(name)
+{
+    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);
+    mPopup->addChild(mListView);
+    mPopup->setLayout(O_VERTICAL, LM_RESIZECHILDREN, LM_RESIZEELEMENT);
+    mPlaceholder = new UIElement();
+    addChild(mPlaceholder);
+    
+    subscribeToEvent(mListView, EVENT_ITEMSELECTED, EVENT_HANDLER(DropDownList, handleItemSelected));
+}
+
+DropDownList::~DropDownList()
+{
+}
+
+void DropDownList::setStyle(const XMLElement& element, ResourceCache* cache)
+{
+    Menu::setStyle(element, cache);
+    
+    XMLElement listElem = element.getChildElement("listview");
+    if (listElem)
+        mListView->setStyle(listElem, cache);
+    XMLElement popupElem = element.getChildElement("popup");
+    if (popupElem)
+        mPopup->setStyle(popupElem, cache);
+    XMLElement placeholderElem = element.getChildElement("placeholder");
+    if (placeholderElem)
+        mPlaceholder->setStyle(placeholderElem, cache);
+    
+    UIElement* root = getRootElement();
+    XMLElement itemElem = element.getChildElement("popupitem");
+    if (root)
+    {
+        while (itemElem)
+        {
+            if (itemElem.hasAttribute("name"))
+                addItem(root->getChild(itemElem.getString("name"), true));
+            itemElem = itemElem.getNextElement("popupitem");
+        }
+    }
+    if (element.hasChildElement("selection"))
+        setSelection(element.getChildElement("selection").getInt("value"));
+}
+
+void DropDownList::getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads, const IntRect& currentScissor)
+{
+    Button::getBatches(batches, quads, currentScissor);
+    
+    UIElement* selectedItem = getSelectedItem();
+    if (selectedItem)
+    {
+        // We can not easily copy the selected item. However, we can re-render it on the placeholder's position
+        const IntVector2& targetPos = mPlaceholder->getScreenPosition();
+        const IntVector2& originalPos = selectedItem->getScreenPosition();
+        IntVector2 offset = targetPos - originalPos;
+        
+        // getBatches() usually resets the hover flag. Therefore get its value and then reset it for the real rendering
+        bool hover = selectedItem->isHovering();
+        selectedItem->setHovering(false);
+        selectedItem->getBatchesWithOffset(offset, batches, quads, currentScissor);
+        selectedItem->setHovering(hover);
+    }
+}
+
+void DropDownList::onResize()
+{
+    Menu::onResize();
+    setPopupOffset(0, getHeight());
+}
+
+void DropDownList::addItem(UIElement* item)
+{
+    mListView->addItem(item);
+    
+    // If there was no selection, set to the first
+    if (mListView->getSelection() == M_MAX_UNSIGNED)
+        mListView->setSelection(0);
+}
+
+void DropDownList::removeItem(UIElement* item)
+{
+    mListView->removeItem(item);
+}
+
+void DropDownList::removeItem(unsigned index)
+{
+    mListView->removeItem(index);
+}
+
+void DropDownList::removeAllItems()
+{
+    mListView->removeAllItems();
+}
+
+void DropDownList::setSelection(unsigned index)
+{
+    // The list should always show a selection if possible, so clamp the selection
+    unsigned numItems = getNumItems();
+    if (index >= numItems)
+        index = numItems - 1;
+    mListView->setSelection(index);
+}
+
+unsigned DropDownList::getNumItems() const
+{
+    return mListView->getNumItems();
+}
+
+UIElement* DropDownList::getItem(unsigned index) const
+{
+    return mListView->getItem(index);
+}
+
+std::vector<UIElement*> DropDownList::getItems() const
+{
+    return mListView->getContentElement()->getChildren();
+}
+
+unsigned DropDownList::getSelection() const
+{
+    return mListView->getSelection();
+}
+
+UIElement* DropDownList::getSelectedItem() const
+{
+    return mListView->getSelectedItem();
+}
+
+void DropDownList::handleItemSelected(StringHash eventType, VariantMap& eventData)
+{
+    // Resize the selection placeholder to match the selected item
+    UIElement* selectedItem = getSelectedItem();
+    if (selectedItem)
+        mPlaceholder->setSize(selectedItem->getSize());
+    
+    // Close the popup as the selection was made
+    if (getShowPopup())
+        showPopup(false);
+    
+    // Send the event forward
+    using namespace ItemSelected;
+    
+    VariantMap newEventData;
+    newEventData[P_ELEMENT] = (void*)this;
+    newEventData[P_SELECTION] = getSelection();
+    sendEvent(EVENT_ITEMSELECTED, eventData);
+}

+ 81 - 0
Engine/UI/DropDownList.h

@@ -0,0 +1,81 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2011 Lasse Öörni
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "Menu.h"
+
+class ListView;
+
+//! A menu item which displays a popup list view
+class DropDownList : public Menu
+{
+    DEFINE_TYPE(DropDownList)
+    
+public:
+    //! Construct with name
+    DropDownList(const std::string& name);
+    //! Destruct
+    ~DropDownList();
+    
+    //! Set UI element style from XML data
+    virtual void setStyle(const XMLElement& element, ResourceCache* cache);
+    //! Return UI rendering batches
+    virtual void getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads, const IntRect& currentScissor);
+    //! React to resize
+    virtual void onResize();
+    
+    //! Add item to the end of the list
+    void addItem(UIElement* item);
+    //! Remove specific item
+    void removeItem(UIElement* item);
+    //! Remove item at index
+    void removeItem(unsigned index);
+    //! Remove all items
+    void removeAllItems();
+    //! Set selection
+    void setSelection(unsigned index);
+    
+    //! Return number of items
+    unsigned getNumItems() const;
+    //! Return item at index
+    UIElement* getItem(unsigned index) const;
+    //! Return all items
+    std::vector<UIElement*> getItems() const;
+    //! Return selection index, or M_MAX_UNSIGNED if none selected
+    unsigned getSelection() const;
+    //! Return selected item, or null if none selected
+    UIElement* getSelectedItem() const;
+    //! Return listview element
+    ListView* getListView() const { return mListView; }
+    //! Return selected item placeholder element
+    UIElement* getPlaceholder() const { return mPlaceholder; }
+    
+protected:
+    //! Listview element
+    SharedPtr<ListView> mListView;
+    //! Selected item placeholder element
+    SharedPtr<UIElement> mPlaceholder;
+    
+private:
+    //! Handle listview item selected event
+    void handleItemSelected(StringHash eventType, VariantMap& eventData);
+};

+ 16 - 8
Engine/UI/ListView.cpp

@@ -27,6 +27,8 @@
 #include "ListView.h"
 #include "ListView.h"
 #include "UIEvents.h"
 #include "UIEvents.h"
 
 
+#include "DebugNew.h"
+
 ListView::ListView(const std::string& name) :
 ListView::ListView(const std::string& name) :
     ScrollView(name),
     ScrollView(name),
     mSelection(M_MAX_UNSIGNED),
     mSelection(M_MAX_UNSIGNED),
@@ -56,7 +58,8 @@ void ListView::setStyle(const XMLElement& element, ResourceCache* cache)
     {
     {
         while (itemElem)
         while (itemElem)
         {
         {
-            addItem(root->getChild(itemElem.getString("name"), true));
+            if (itemElem.hasAttribute("name"))
+                addItem(root->getChild(itemElem.getString("name"), true));
             itemElem = itemElem.getNextElement("listitem");
             itemElem = itemElem.getNextElement("listitem");
         }
         }
     }
     }
@@ -200,9 +203,21 @@ void ListView::setSelection(unsigned index)
     if (index >= getNumItems())
     if (index >= getNumItems())
         index = M_MAX_UNSIGNED;
         index = M_MAX_UNSIGNED;
     
     
+    bool changed = index != mSelection;
+    
     mSelection = index;
     mSelection = index;
     updateSelectionEffect();
     updateSelectionEffect();
     ensureItemVisibility();
     ensureItemVisibility();
+    
+    if (changed)
+    {
+        using namespace ItemSelected;
+        
+        VariantMap eventData;
+        eventData[P_ELEMENT] = (void*)this;
+        eventData[P_SELECTION] = mSelection;
+        sendEvent(EVENT_ITEMSELECTED, eventData);
+    }
 }
 }
 
 
 void ListView::changeSelection(int delta)
 void ListView::changeSelection(int delta)
@@ -281,13 +296,6 @@ void ListView::handleTryFocus(StringHash eventType, VariantMap& eventData)
     
     
     UIElement* focusElement = static_cast<UIElement*>(eventData[P_ELEMENT].getPtr());
     UIElement* focusElement = static_cast<UIElement*>(eventData[P_ELEMENT].getPtr());
     
     
-    // If the scrollpanel itself was clicked, and not the container / items, clear selection
-    if (focusElement == mScrollPanel)
-    {
-        clearSelection();
-        return;
-    }
-    
     unsigned numItems = getNumItems();
     unsigned numItems = getNumItems();
     for (unsigned i = 0; i < numItems; ++i)
     for (unsigned i = 0; i < numItems; ++i)
     {
     {

+ 2 - 1
Engine/UI/ListView.h

@@ -26,6 +26,7 @@
 
 
 #include "ScrollView.h"
 #include "ScrollView.h"
 
 
+//! A scrollable list
 class ListView : public ScrollView
 class ListView : public ScrollView
 {
 {
     DEFINE_TYPE(ListView);
     DEFINE_TYPE(ListView);
@@ -55,7 +56,7 @@ public:
     void removeItem(unsigned index);
     void removeItem(unsigned index);
     //! Remove all items
     //! Remove all items
     void removeAllItems();
     void removeAllItems();
-    //! Set selected item
+    //! Set selection
     void setSelection(unsigned index);
     void setSelection(unsigned index);
     //! Move selection by a delta and clamp at list ends
     //! Move selection by a delta and clamp at list ends
     void changeSelection(int delta);
     void changeSelection(int delta);

+ 38 - 15
Engine/UI/MenuItem.cpp → Engine/UI/Menu.cpp

@@ -23,40 +23,44 @@
 
 
 #include "Precompiled.h"
 #include "Precompiled.h"
 #include "InputEvents.h"
 #include "InputEvents.h"
-#include "MenuItem.h"
+#include "Menu.h"
 #include "UIEvents.h"
 #include "UIEvents.h"
 
 
 #include "DebugNew.h"
 #include "DebugNew.h"
 
 
-MenuItem::MenuItem(const std::string& name) :
+Menu::Menu(const std::string& name) :
     Button(name),
     Button(name),
     mPopupOffset(IntVector2::sZero),
     mPopupOffset(IntVector2::sZero),
     mShowPopup(false)
     mShowPopup(false)
 {
 {
-    subscribeToEvent(EVENT_TRYFOCUS, EVENT_HANDLER(MenuItem, handleTryFocus));
+    subscribeToEvent(EVENT_TRYFOCUS, EVENT_HANDLER(Menu, handleTryFocus));
 }
 }
 
 
-MenuItem::~MenuItem()
+Menu::~Menu()
 {
 {
     if (mPopup)
     if (mPopup)
         mPopup->setOrigin(0);
         mPopup->setOrigin(0);
 }
 }
 
 
-void MenuItem::setStyle(const XMLElement& element, ResourceCache* cache)
+void Menu::setStyle(const XMLElement& element, ResourceCache* cache)
 {
 {
     Button::setStyle(element, cache);
     Button::setStyle(element, cache);
     
     
-    if (element.hasChildElement("popup"))
+    XMLElement popupElem = element.getChildElement("popup");
+    if ((popupElem) && (popupElem.hasAttribute("name")))
     {
     {
         UIElement* root = getRootElement();
         UIElement* root = getRootElement();
         if (root)
         if (root)
             setPopup(root->getChild(element.getChildElement("popup").getString("name"), true));
             setPopup(root->getChild(element.getChildElement("popup").getString("name"), true));
     }
     }
+    
+    if (element.hasChildElement("resizepopup"))
+        setResizePopup(element.getChildElement("resizepopup").getBool("enable"));
     if (element.hasChildElement("popupoffset"))
     if (element.hasChildElement("popupoffset"))
         setPopupOffset(element.getChildElement("popupoffset").getIntVector2("value"));
         setPopupOffset(element.getChildElement("popupoffset").getIntVector2("value"));
 }
 }
 
 
-void MenuItem::onClick(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers)
+void Menu::onClick(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers)
 {
 {
     setPressed(true);
     setPressed(true);
     // Toggle popup visibility if exists
     // Toggle popup visibility if exists
@@ -65,40 +69,59 @@ void MenuItem::onClick(const IntVector2& position, const IntVector2& screenPosit
     // Send event on each click if no popup, or whenever the popup is opened
     // Send event on each click if no popup, or whenever the popup is opened
     if ((!mPopup) || (mShowPopup))
     if ((!mPopup) || (mShowPopup))
     {
     {
-        using namespace ItemSelected;
+        using namespace MenuSelected;
         
         
         VariantMap eventData;
         VariantMap eventData;
         eventData[P_ELEMENT] = (void*)this;
         eventData[P_ELEMENT] = (void*)this;
-        sendEvent(EVENT_ITEMSELECTED, eventData);
+        sendEvent(EVENT_MENUSELECTED, eventData);
     }
     }
 }
 }
 
 
-void MenuItem::setPopup(UIElement* popup)
+void Menu::onResize()
+{
+    if ((mPopup) && (mResizePopup))
+        mPopup->setWidth(getWidth());
+}
+
+void Menu::setPopup(UIElement* popup)
 {
 {
     if (popup == this)
     if (popup == this)
         return;
         return;
+    
     if ((mPopup) && (!popup))
     if ((mPopup) && (!popup))
         showPopup(false);
         showPopup(false);
+    
     mPopup = popup;
     mPopup = popup;
+    
     // Detach from current parent (if any) to only show when it is time
     // Detach from current parent (if any) to only show when it is time
     if (mPopup)
     if (mPopup)
     {
     {
         UIElement* parent = mPopup->getParent();
         UIElement* parent = mPopup->getParent();
-        parent->removeChild(mPopup);
+        if (parent)
+            parent->removeChild(mPopup);
     }
     }
 }
 }
 
 
-void MenuItem::setPopupOffset(const IntVector2& offset)
+void Menu::setPopupOffset(const IntVector2& offset)
 {
 {
     mPopupOffset = offset;
     mPopupOffset = offset;
 }
 }
 
 
-void MenuItem::setPopupOffset(int x, int y)
+void Menu::setPopupOffset(int x, int y)
 {
 {
     mPopupOffset = IntVector2(x, y);
     mPopupOffset = IntVector2(x, y);
 }
 }
 
 
-void MenuItem::showPopup(bool enable)
+void Menu::setResizePopup(bool enable)
+{
+    if (enable != mResizePopup)
+    {
+        mResizePopup = enable;
+        onResize();
+    }
+}
+
+void Menu::showPopup(bool enable)
 {
 {
     if (!mPopup)
     if (!mPopup)
         return;
         return;
@@ -130,7 +153,7 @@ void MenuItem::showPopup(bool enable)
     mSelected = enable;
     mSelected = enable;
 }
 }
 
 
-void MenuItem::handleTryFocus(StringHash eventType, VariantMap& eventData)
+void Menu::handleTryFocus(StringHash eventType, VariantMap& eventData)
 {
 {
     if (!mShowPopup)
     if (!mShowPopup)
         return;
         return;

+ 16 - 8
Engine/UI/MenuItem.h → Engine/UI/Menu.h

@@ -21,25 +21,27 @@
 // THE SOFTWARE.
 // THE SOFTWARE.
 //
 //
 
 
-#ifndef UI_MENUITEM_H
-#define UI_MENUITEM_H
+#ifndef UI_MENU_H
+#define UI_MENU_H
 
 
 #include "Button.h"
 #include "Button.h"
 
 
-//! A button that either triggers a menu selection or shows a popup element
-class MenuItem : public Button
+//! A menu element that optionally shows a popup
+class Menu : public Button
 {
 {
-    DEFINE_TYPE(MenuItem);
+    DEFINE_TYPE(Menu);
     
     
     //! Construct with name
     //! Construct with name
-    MenuItem(const std::string& name = std::string());
+    Menu(const std::string& name = std::string());
     //! Destruct
     //! Destruct
-    virtual ~MenuItem();
+    virtual ~Menu();
     
     
     //! Set UI element style from XML data
     //! Set UI element style from XML data
     virtual void setStyle(const XMLElement& element, ResourceCache* cache);
     virtual void setStyle(const XMLElement& element, ResourceCache* cache);
     //! React to mouse click
     //! React to mouse click
     virtual void onClick(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers);
     virtual void onClick(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers);
+    //! React to resize
+    virtual void onResize();
     
     
     //! Set popup element to show on selection
     //! Set popup element to show on selection
     void setPopup(UIElement* element);
     void setPopup(UIElement* element);
@@ -47,6 +49,8 @@ class MenuItem : public Button
     void setPopupOffset(const IntVector2& offset);
     void setPopupOffset(const IntVector2& offset);
     //! Set popup element offset
     //! Set popup element offset
     void setPopupOffset(int x, int y);
     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
     //! Force the popup to show or hide
     void showPopup(bool enable);
     void showPopup(bool enable);
     
     
@@ -56,6 +60,8 @@ class MenuItem : public Button
     const IntVector2& getPopupOffset() const { return mPopupOffset; }
     const IntVector2& getPopupOffset() const { return mPopupOffset; }
     //! Return whether popup is open
     //! Return whether popup is open
     bool getShowPopup() const { return mShowPopup; }
     bool getShowPopup() const { return mShowPopup; }
+    //! Return whether automatically resizes the popup
+    bool getResizePopup() const { return mResizePopup; }
     
     
 protected:
 protected:
     //! Popup element
     //! Popup element
@@ -64,10 +70,12 @@ protected:
     IntVector2 mPopupOffset;
     IntVector2 mPopupOffset;
     //! Show popup flag
     //! Show popup flag
     bool mShowPopup;
     bool mShowPopup;
+    //! Resize popup automatically flag
+    bool mResizePopup;
     
     
 private:
 private:
     //! Handle focus change attempt in case the popup needs to be hidden
     //! Handle focus change attempt in case the popup needs to be hidden
     void handleTryFocus(StringHash eventType, VariantMap& eventData);
     void handleTryFocus(StringHash eventType, VariantMap& eventData);
 };
 };
 
 
-#endif // UI_MENUITEM_H
+#endif // UI_MENU_H

+ 1 - 0
Engine/UI/ScrollBar.h

@@ -29,6 +29,7 @@
 class Button;
 class Button;
 class Slider;
 class Slider;
 
 
+//! A slider bar with forward and back buttons
 class ScrollBar : public UIElement
 class ScrollBar : public UIElement
 {
 {
     DEFINE_TYPE(ScrollBar);
     DEFINE_TYPE(ScrollBar);

+ 1 - 1
Engine/UI/ScrollView.h

@@ -29,7 +29,7 @@
 class BorderImage;
 class BorderImage;
 class ScrollBar;
 class ScrollBar;
 
 
-//! A scrollable view for showing child widgets
+//! A scrollable view for showing a (possibly large) child element
 class ScrollView : public UIElement
 class ScrollView : public UIElement
 {
 {
     DEFINE_TYPE(ScrollView);
     DEFINE_TYPE(ScrollView);

+ 1 - 1
Engine/UI/Slider.h

@@ -26,7 +26,7 @@
 
 
 #include "BorderImage.h"
 #include "BorderImage.h"
 
 
-//! An image that contains a slideable knob image
+//! A slider bar
 class Slider : public BorderImage
 class Slider : public BorderImage
 {
 {
     DEFINE_TYPE(Slider);
     DEFINE_TYPE(Slider);

+ 21 - 0
Engine/UI/UIElement.cpp

@@ -1039,6 +1039,27 @@ void UIElement::adjustScissor(IntRect& currentScissor)
     }
     }
 }
 }
 
 
+void UIElement::getBatchesWithOffset(IntVector2& offset, std::vector<UIBatch>& batches, std::vector<UIQuad>& quads, IntRect
+    currentScissor)
+{
+    unsigned initialSize = quads.size();
+    getBatches(batches, quads, currentScissor);
+    for (unsigned i = initialSize; i < quads.size(); ++i)
+    {
+        quads[i].mLeft += offset.mX;
+        quads[i].mTop += offset.mY;
+        quads[i].mRight += offset.mX;
+        quads[i].mBottom += offset.mY;
+    }
+    
+    adjustScissor(currentScissor);
+    for (std::vector<SharedPtr<UIElement> >::const_iterator i = mChildren.begin(); i != mChildren.end(); ++i)
+    {
+        if ((*i)->isVisible())
+            (*i)->getBatchesWithOffset(offset, batches, quads, currentScissor);
+    }
+}
+
 XMLElement UIElement::getStyleElement(XMLFile* file, const std::string& typeName)
 XMLElement UIElement::getStyleElement(XMLFile* file, const std::string& typeName)
 {
 {
     if (file)
     if (file)

+ 3 - 0
Engine/UI/UIElement.h

@@ -326,6 +326,9 @@ public:
     void setOrigin(UIElement* origin);
     void setOrigin(UIElement* origin);
     //! Adjust scissor for rendering
     //! Adjust scissor for rendering
     void adjustScissor(IntRect& currentScissor);
     void adjustScissor(IntRect& currentScissor);
+    //! Get UI rendering batches with a specified offset. Also recurses to child elements
+    void getBatchesWithOffset(IntVector2& offset, std::vector<UIBatch>& batches, std::vector<UIQuad>& quads, IntRect
+        currentScissor);
     
     
     //! Return first matching UI style element from an XML file, with freely specified type. If not found, return empty
     //! Return first matching UI style element from an XML file, with freely specified type. If not found, return empty
     static XMLElement getStyleElement(XMLFile* file, const std::string& typeName);
     static XMLElement getStyleElement(XMLFile* file, const std::string& typeName);

+ 8 - 1
Engine/UI/UIEvents.h

@@ -108,10 +108,17 @@ DEFINE_EVENT(EVENT_TEXTFINISHED, TextFinished)
     EVENT_PARAM(P_TEXT, Text);                  // string
     EVENT_PARAM(P_TEXT, Text);                  // string
 }
 }
 
 
-//! Menu item selected
+//! Menu selected
+DEFINE_EVENT(EVENT_MENUSELECTED, MenuSelected)
+{
+    EVENT_PARAM(P_ELEMENT, Element);            // UIElement pointer
+}
+
+//! Listview or DropDownList item selected
 DEFINE_EVENT(EVENT_ITEMSELECTED, ItemSelected)
 DEFINE_EVENT(EVENT_ITEMSELECTED, ItemSelected)
 {
 {
     EVENT_PARAM(P_ELEMENT, Element);            // UIElement pointer
     EVENT_PARAM(P_ELEMENT, Element);            // UIElement pointer
+    EVENT_PARAM(P_SELECTION, Selection);        // int
 }
 }
 
 
 #endif // UI_UIEVENTS_H
 #endif // UI_UIEVENTS_H

+ 1 - 1
Engine/UI/Window.h

@@ -41,7 +41,7 @@ enum WindowDragMode
     DRAG_RESIZE_LEFT
     DRAG_RESIZE_LEFT
 };
 };
 
 
-//! Window that can optionally by moved or resized
+//! A window that can optionally by moved or resized
 class Window : public BorderImage
 class Window : public BorderImage
 {
 {
     DEFINE_TYPE(Window);
     DEFINE_TYPE(Window);