Parcourir la source

Initial ListView implementation.
UI bugfixes.

Lasse Öörni il y a 15 ans
Parent
commit
cae8301062

+ 25 - 25
Bin/Data/Scripts/GraphicsTest.as

@@ -64,7 +64,7 @@ void initScene()
     engine.setDefaultScene(testScene);
 
     PhysicsWorld@ world = testScene.getPhysicsWorld();
-    
+
     // Set the physics world parameters
     world.setGravity(Vector3(0.0, -9.81, 0.0));
     world.setFps(100);
@@ -81,7 +81,7 @@ void initScene()
     sunLight.setSpecularIntensity(1.0);
     //sunLight.setCastShadows(true);
     //sunLight.setShadowCascade(CascadeParameters(3, 0.95, 0.2, 500.0));
-    
+
     // Create a zone to control the ambient lighting
     Entity@ zoneEntity = testScene.createEntity();
     Zone@ zone = zoneEntity.createComponent("Zone");
@@ -100,14 +100,14 @@ void initScene()
             body.setCollisionShape(cache.getResource("CollisionShape", "Physics/Box.xml"));
             body.setCollisionGroup(2);
             body.setCollisionMask(1);
-            
+
             StaticModel@ object = newEntity.createComponent("StaticModel");
             object.setModel(cache.getResource("Model", "Models/Box.mdl"));
             object.setMaterial(cache.getResource("Material", "Materials/Test.xml"));
             body.addChild(object);
         }
     }
-    
+
     // Create 2 occluder walls
     for (int x = 0; x < 2; x++)
     {
@@ -119,7 +119,7 @@ void initScene()
         body.setCollisionShape(cache.getResource("CollisionShape", "Physics/Box.xml"));
         body.setCollisionGroup(2);
         body.setCollisionMask(1);
-        
+
         StaticModel@ object = newEntity.createComponent("StaticModel");
         object.setModel(cache.getResource("Model", "Models/Box.mdl"));
         object.setMaterial(cache.getResource("Material", "Materials/Test.xml"));
@@ -137,7 +137,7 @@ void initScene()
         body.setCollisionShape(cache.getResource("CollisionShape", "Physics/Mushroom.xml"));
         body.setCollisionGroup(2);
         body.setCollisionMask(1);
-        
+
         StaticModel@ object = newEntity.createComponent("StaticModel");
         object.setModel(cache.getResource("Model", "Models/Mushroom.mdl"));
         object.setMaterial(cache.getResource("Material", "Materials/Mushroom.xml"));
@@ -336,7 +336,7 @@ void createCamera()
     @camera = cameraEntity.createComponent("Camera");
     camera.setAspectRatio(float(renderer.getWidth()) / float(renderer.getHeight()));
     camera.setPosition(Vector3(-50.0, 2.0, -50.0));
-    
+
     @cameraLight = cameraEntity.createComponent("Light");
     cameraLight.setLightType(LIGHT_SPOT);
     cameraLight.setDirection(Vector3(0.0, 0.0, 1.0));
@@ -374,7 +374,7 @@ void handleUpdate(StringHash eventType, VariantMap& eventData)
             camera.translateRelative(Vector3(-10, 0, 0) * timeStep * speedMultiplier);
         if (input.getKeyDown('D'))
             camera.translateRelative(Vector3(10, 0, 0) * timeStep * speedMultiplier);
-        
+
         if (input.getKeyPress('1'))
         {
             int nextRenderMode = renderer.getRenderMode();
@@ -390,11 +390,11 @@ void handleUpdate(StringHash eventType, VariantMap& eventData)
                 if (nextRenderMode > 2)
                     nextRenderMode = 0;
             }
-            
+
             renderer.setMode(RenderMode(nextRenderMode), renderer.getWidth(), renderer.getHeight(), renderer.getFullscreen(),
                 renderer.getVsync(), renderer.getMultiSample());
         }
-        
+
         if (input.getKeyPress('2'))
         {
             texturequality++;
@@ -410,34 +410,34 @@ void handleUpdate(StringHash eventType, VariantMap& eventData)
                 materialquality = 0;
             pipeline.setMaterialQuality(materialquality);
         }
-        
+
         if (input.getKeyPress('4'))
         {
             usespecular = !usespecular;
             pipeline.setSpecularLighting(usespecular);
         }
-        
+
         if (input.getKeyPress('5'))
         {
             drawshadows = !drawshadows;
             pipeline.setDrawShadows(drawshadows);
         }
-        
+
         if (input.getKeyPress('6'))
         {
             shadowmapsize *= 2;
             if (shadowmapsize > 2048)
                 shadowmapsize = 512;
-            
+
             pipeline.setShadowMapSize(shadowmapsize);
         }
-        
+
         if (input.getKeyPress('7'))
         {
             hiresshadowmap = !hiresshadowmap;
             pipeline.setShadowMapHiresDepth(hiresshadowmap);
         }
-        
+
         if (input.getKeyPress('8'))
         {
             useocclusion = !useocclusion;
@@ -459,14 +459,14 @@ void handleUpdate(StringHash eventType, VariantMap& eventData)
                 camera.removeChild(cameraLight, true);
             }
         }
-        
+
         if (input.getKeyPress(' '))
         {
             drawdebug++;
             if (drawdebug > 2) drawdebug = 0;
             engine.setDebugDrawMode(drawdebug);
         }
-        
+
         if (input.getKeyPress('P'))
         {
             paused = !paused;
@@ -488,14 +488,14 @@ void handleUpdate(StringHash eventType, VariantMap& eventData)
             pipeline.setEdgeFilter(params);
         }
     }
-    
+
     if (input.getKeyPress(KEY_ESC))
-	{
-		if (ui.getFocusElement() is null)
-	        engine.exit();
-	    else
-		    console.setVisible(false);
-	}
+    {
+        if (ui.getFocusElement() is null)
+            engine.exit();
+        else
+            console.setVisible(false);
+    }
 }
 
 void handleKeyDown(StringHash eventType, VariantMap& eventData)

+ 6 - 6
Bin/Data/Scripts/NinjaSnowWar.as

@@ -214,12 +214,12 @@ void handleUpdate(StringHash eventType, VariantMap& eventData)
     }
 
     if (input.getKeyPress(KEY_ESC))
-	{
-		if (!console.isVisible())
-	        engine.exit();
-	    else
-		    console.setVisible(false);
-	}
+    {
+        if (!console.isVisible())
+            engine.exit();
+        else
+        console.setVisible(false);
+    }
 
     if (!paused)
         updateControls();

+ 65 - 0
Bin/Data/UI/DefaultStyle.xml

@@ -46,6 +46,71 @@
         <pressedoffset value="16 0" />
         <hoveroffset value="0 16" />
     </element>
+    <element type="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>
+            <texture name="Textures/UI.png" />
+            <imagerect value="112 0 128 16" />
+            <border value="2 2 2 2" />
+            <clipborder value="1 1 1 1" />
+        </scrollpanel>
+    </element>
+
     <element type="ScrollBar">
         <backbutton>
             <size value="12 12" />

+ 69 - 9
Bin/Data/UI/TestLayout.xml

@@ -23,15 +23,75 @@
     <element type="CheckBox">
         <position value="40 130" />
     </element>
-    <element type="Text" name="ScrollViewText" >
-        <position value="1 0" />
-        <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." />
-        <wordwrap enable="true" />
-    </element>
-    <element type="ScrollView">
-        <contentelement name="ScrollViewText" />
+	<element type="Text" name="Item1">
+	    <font name="times.ttf" size="15" />
+	    <text value="Audio" />
+        <selectioncolor value="0.5 0.75 0.5" />
+	</element>
+	<element type="Text" name="Item2">
+	    <font name="times.ttf" size="15" />
+	    <text value="Common" />
+        <selectioncolor value="0.5 0.75 0.5" />
+	</element>
+	<element type="Text" name="Item3">
+	    <font name="times.ttf" size="15" />
+	    <text value="Engine" />
+        <selectioncolor value="0.5 0.75 0.5" />
+	</element>
+	<element type="Text" name="Item4">
+	    <font name="times.ttf" size="15" />
+	    <text value="Input" />
+        <selectioncolor value="0.5 0.75 0.5" />
+	</element>
+	<element type="Text" name="Item5">
+	    <font name="times.ttf" size="15" />
+	    <text value="Math" />
+        <selectioncolor value="0.5 0.75 0.5" />
+	</element>
+	<element type="Text" name="Item6">
+	    <font name="times.ttf" size="15" />
+	    <text value="Network" />
+        <selectioncolor value="0.5 0.75 0.5" />
+	</element>
+	<element type="Text" name="Item7">
+	    <font name="times.ttf" size="15" />
+	    <text value="Physics" />
+        <selectioncolor value="0.5 0.75 0.5" />
+	</element>
+	<element type="Text" name="Item8">
+	    <font name="times.ttf" size="15" />
+	    <text value="Renderer" />
+        <selectioncolor value="0.5 0.75 0.5" />
+	</element>
+	<element type="Text" name="Item9">
+	    <font name="times.ttf" size="15" />
+	    <text value="Resource" />
+        <selectioncolor value="0.5 0.75 0.5" />
+	</element>
+	<element type="Text" name="Item10">
+	    <font name="times.ttf" size="15" />
+	    <text value="Scene" />
+        <selectioncolor value="0.5 0.75 0.5" />
+	</element>
+	<element type="Text" name="Item11">
+	    <font name="times.ttf" size="15" />
+	    <text value="UI" />
+        <selectioncolor value="0.5 0.75 0.5" />
+	</element>
+    <element type="ListView">
+        <position value="150 100" />
+        <size value="212 112" />
+        <listitem name="Item1" />
+        <listitem name="Item2" />
+        <listitem name="Item3" />
+        <listitem name="Item4" />
+        <listitem name="Item5" />
+        <listitem name="Item6" />
+        <listitem name="Item7" />
+        <listitem name="Item8" />
+        <listitem name="Item9" />
+        <listitem name="Item10" />
+        <listitem name="Item11" />
     </element>
     <element type="LineEdit">
         <position value="150 230" />

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

@@ -32,9 +32,8 @@
     </element>
     <element type="Element">
         <layout orientation="horizontal" horizontal="resizechildren" vertical="resizechildren" spacing="8" />
-    	<element type="Text" name="ScrollViewText" >
+    	<element type="Text" name="ScrollViewText">
 	        <position value="1 0" />
-	        <minsize value="300 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" />

+ 2 - 1
Engine/Engine/RegisterUI.cpp

@@ -156,6 +156,7 @@ static void registerScrollBar(asIScriptEngine* engine)
 static void registerScrollView(asIScriptEngine* engine)
 {
     registerUIElement<ScrollView>(engine, "ScrollView");
+    engine->RegisterObjectMethod("ScrollView", "void setContentElement(UIElement@+)", asMETHOD(ScrollView, setContentElement), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScrollView", "void setViewPosition(const IntVector2& in)", asMETHODPR(ScrollView, setViewPosition, (const IntVector2&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScrollView", "void setViewPosition(int, int)", asMETHODPR(ScrollView, setViewPosition, (int, int), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScrollView", "void setScrollBarsVisible(bool, bool)", asMETHOD(ScrollView, setScrollBarsVisible), asCALL_THISCALL);
@@ -163,7 +164,7 @@ static void registerScrollView(asIScriptEngine* engine)
     engine->RegisterObjectMethod("ScrollView", "void setPageStep(float)", asMETHOD(ScrollView, setPageStep), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScrollView", "void setNormalizeScrollStep(bool)", asMETHOD(ScrollView, setNormalizeScrollStep), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScrollView", "const IntVector2& getViewPosition() const", asMETHOD(ScrollView, getViewPosition), asCALL_THISCALL);
-    engine->RegisterObjectMethod("ScrollView", "UIElement@+ getElement() const", asMETHOD(ScrollView, getElement), asCALL_THISCALL);
+    engine->RegisterObjectMethod("ScrollView", "UIElement@+ getContentElement() const", asMETHOD(ScrollView, getContentElement), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScrollView", "ScrollBar@+ getHorizontalScrollBar() const", asMETHOD(ScrollView, getHorizontalScrollBar), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScrollView", "ScrollBar@+ getVerticalScrollBar() const", asMETHOD(ScrollView, getVerticalScrollBar), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScrollView", "BorderImage@+ getScrollPanel() const", asMETHOD(ScrollView, getScrollPanel), asCALL_THISCALL);

+ 3 - 0
Engine/UI/BaseUIElementFactory.cpp

@@ -27,6 +27,7 @@
 #include "CheckBox.h"
 #include "Cursor.h"
 #include "LineEdit.h"
+#include "ListView.h"
 #include "MenuItem.h"
 #include "ScrollBar.h"
 #include "ScrollView.h"
@@ -46,6 +47,8 @@ UIElement* BaseUIElementFactory::createElement(ShortStringHash type, const std::
         return new Cursor(name);
     if (type == LineEdit::getTypeStatic())
         return new LineEdit(name);
+    if (type == ListView::getTypeStatic())
+        return new ListView(name);
     if (type == MenuItem::getTypeStatic())
         return new MenuItem(name);
     if (type == ScrollBar::getTypeStatic())

+ 2 - 2
Engine/UI/Button.cpp

@@ -84,9 +84,9 @@ void Button::update(float timeStep)
 void Button::getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads, const IntRect& currentScissor)
 {
     IntVector2 offset(IntVector2::sZero);
-    if ((mHovering) || (mSelected))
+    if (mHovering)
         offset += mHoverOffset;
-    if (mPressed)
+    if ((mPressed) || (mSelected))
         offset += mPressedOffset;
     
     BorderImage::getBatches(batches, quads, currentScissor, offset);

+ 286 - 0
Engine/UI/ListView.cpp

@@ -0,0 +1,286 @@
+//
+// 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 "BorderImage.h"
+#include "InputEvents.h"
+#include "ListView.h"
+#include "Log.h"
+#include "UIEvents.h"
+
+ListView::ListView(const std::string& name) :
+    ScrollView(name),
+    mSelection(M_MAX_UNSIGNED)
+{
+    UIElement* container = new UIElement();
+    container->setEnabled(true);
+    container->setLayout(O_VERTICAL, LM_RESIZECHILDREN, LM_RESIZEELEMENT);
+    mScrollPanel->setLayout(O_HORIZONTAL, LM_RESIZECHILDREN, LM_FREE);
+    setContentElement(container);
+    subscribeToEvent(EVENT_TRYFOCUS, EVENT_HANDLER(ListView, handleTryFocus));
+}
+
+ListView::~ListView()
+{
+}
+
+void ListView::setStyle(const XMLElement& element, ResourceCache* cache)
+{
+    ScrollView::setStyle(element, cache);
+    
+    UIElement* root = getRootElement();
+    XMLElement itemElem = element.getChildElement("listitem");
+    if (root)
+    {
+        while (itemElem)
+        {
+            addItem(root->getChild(itemElem.getString("name"), true));
+            itemElem = itemElem.getNextElement("listitem");
+        }
+    }
+}
+
+void ListView::onKey(int key, int buttons, int qualifiers)
+{
+    // If no selection, can not move with keys
+    if ((mSelection == M_MAX_UNSIGNED) || (mItems.empty()))
+        return;
+    
+    switch (key)
+    {
+    case KEY_UP:
+        if (qualifiers & QUAL_CTRL)
+            setSelection(0);
+        else
+            changeSelection(-1);
+        break;
+        
+    case KEY_DOWN:
+        if (qualifiers & QUAL_CTRL)
+            setSelection(mItems.size() - 1);
+        else
+            changeSelection(1);
+        break;
+        
+    case KEY_PAGEUP:
+        changeSelection((int)-mPageStep);
+        break;
+        
+    case KEY_PAGEDOWN:
+        changeSelection((int)mPageStep);
+        break;
+    
+    case KEY_HOME:
+        setSelection(0);
+        break;
+    
+    case KEY_END:
+        setSelection(mItems.size() - 1);
+        break;
+    }
+}
+
+void ListView::addItem(UIElement* item)
+{
+    if ((!item) || (hasItem(item)))
+        return;
+    
+    // Enable input so that clicking the item can be detected
+    item->setEnabled(true);
+    mItems.push_back(SharedPtr<UIElement>(item));
+    mContentElement->addChild(item);
+}
+
+void ListView::addItem(unsigned index, UIElement* item)
+{
+    if ((!item) || (hasItem(item)))
+        return;
+    if (index > mItems.size())
+        index = mItems.size();
+    
+    // Enable input so that clicking the item can be detected
+    item->setEnabled(true);
+    mItems.insert(mItems.begin() + index, SharedPtr<UIElement>(item));
+    updateList();
+    
+    if (mSelection >= index)
+        setSelection(mSelection + 1);
+}
+
+void ListView::setItem(unsigned index, UIElement* item)
+{
+    if (index >= mItems.size())
+    {
+        LOGERROR("Illegal ListView item index");
+        return;
+    }
+    if ((!item) || (hasItem(item)))
+        return;
+    
+    // Enable input so that clicking the item can be detected
+    item->setEnabled(true);
+    mItems[index] = item;
+    updateList();
+}
+
+void ListView::removeItem(UIElement* item)
+{
+    if (!item)
+        return;
+    
+    for (unsigned i = 0; i < mItems.size(); ++i)
+    {
+        if (mItems[i] == item)
+        {
+            mItems.erase(mItems.begin() + i);
+            mContentElement->removeChild(item);
+            
+            if (mSelection == i)
+                setSelection(M_MAX_UNSIGNED);
+            else if (mSelection > i)
+                setSelection(mSelection - 1);
+            
+            return;
+        }
+    }
+}
+
+void ListView::removeItem(unsigned index)
+{
+    if (index >= mItems.size())
+        return;
+    
+    UIElement* item = mItems[index];
+    mItems.erase(mItems.begin() + index);
+    mContentElement->removeChild(item);
+    
+    if (mSelection == index)
+        setSelection(M_MAX_UNSIGNED);
+    else if (mSelection > index)
+        setSelection(mSelection - 1);
+}
+
+void ListView::removeAllItems()
+{
+    setSelection(M_MAX_UNSIGNED);
+    mItems.clear();
+    updateList();
+}
+
+void ListView::setSelection(unsigned index)
+{
+    if (index >= mItems.size())
+        index = M_MAX_UNSIGNED;
+    
+    for (unsigned i = 0; i < mItems.size(); ++i)
+        mItems[i]->setSelected(i == index);
+    mSelection = index;
+    
+    ensureItemVisibility();
+}
+
+void ListView::changeSelection(int delta)
+{
+    if (mSelection == M_MAX_UNSIGNED)
+        return;
+    
+    int newSelection = clamp((int)mSelection + delta, 0, (int)mItems.size() - 1);
+    setSelection(newSelection);
+}
+
+UIElement* ListView::getItem(unsigned index) const
+{
+    if (index >= mItems.size())
+        return 0;
+    
+    return mItems[index];
+}
+
+bool ListView::hasItem(UIElement* item) const
+{
+    for (unsigned i = 0; i < mItems.size(); ++i)
+    {
+        if (mItems[i] == item)
+            return true;
+    }
+    return false;
+}
+
+UIElement* ListView::getSelectedItem() const
+{
+    return getItem(mSelection);
+}
+
+void ListView::updateList()
+{
+    mContentElement->removeAllChildren();
+    for (unsigned i = 0; i < mItems.size(); ++i)
+        mContentElement->addChild(mItems[i]);
+}
+
+void ListView::ensureItemVisibility()
+{
+    UIElement* selected = getSelectedItem();
+    if (!selected)
+        return;
+    
+    IntVector2 currentOffset = selected->getScreenPosition() - mScrollPanel->getScreenPosition() - mContentElement->getPosition();
+    IntVector2 newView = getViewPosition();
+    const IntRect& clipBorder = mScrollPanel->getClipBorder();
+    IntVector2 windowSize(mScrollPanel->getWidth() - clipBorder.mLeft - clipBorder.mRight, mScrollPanel->getHeight() -
+        clipBorder.mTop - clipBorder.mBottom);
+    
+    if (currentOffset.mX < 0)
+        newView.mX += currentOffset.mX;
+    if (currentOffset.mX + selected->getWidth() > windowSize.mX)
+        newView.mX += currentOffset.mX + selected->getWidth() - windowSize.mX;
+    if (currentOffset.mY < 0)
+        newView.mY += currentOffset.mY;
+    if (currentOffset.mY + selected->getHeight() > windowSize.mY)
+        newView.mY += currentOffset.mY + selected->getHeight() - windowSize.mY;
+    
+    setViewPosition(newView);
+}
+
+void ListView::handleTryFocus(StringHash eventType, VariantMap& eventData)
+{
+    using namespace TryFocus;
+    
+    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)
+    {
+        setSelection(M_MAX_UNSIGNED);
+        return;
+    }
+    
+    for (unsigned i = 0; i < mItems.size(); ++i)
+    {
+        if (focusElement == mItems[i])
+        {
+            setSelection(i);
+            return;
+        }
+    }
+}

+ 86 - 0
Engine/UI/ListView.h

@@ -0,0 +1,86 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2011 Lasse Öörni
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#ifndef UI_LISTVIEW_H
+#define UI_LISTVIEW_H
+
+#include "ScrollView.h"
+
+class ListView : public ScrollView
+{
+    DEFINE_TYPE(ListView);
+    
+public:
+    //! Construct with name
+    ListView(const std::string& name = std::string());
+    //! Destruct
+    virtual ~ListView();
+    
+    //! Set UI element style from XML data
+    virtual void setStyle(const XMLElement& element, ResourceCache* cache);
+    //! React to a key press
+    virtual void onKey(int key, int buttons, int qualifiers);
+    
+    //! Add item to the end of the list
+    void addItem(UIElement* item);
+    //! Add item at index
+    void addItem(unsigned index, UIElement* item);
+    //! Set item at index, replacing the previous
+    void setItem(unsigned index, UIElement* item);
+    //! Remove specific item
+    void removeItem(UIElement* item);
+    //! Remove item at index
+    void removeItem(unsigned index);
+    //! Remove all elements
+    void removeAllItems();
+    //! Set selected element
+    void setSelection(unsigned index);
+    //! Move selection by a delta. Clamp at list ends
+    void changeSelection(int delta);
+    
+    //! Return item at index
+    UIElement* getItem(unsigned index) const;
+    //! Return whether has a certain item
+    bool hasItem(UIElement* item) const;
+    //! Return selection index, or M_MAX_UNSIGNED if none selected
+    unsigned getSelection() const { return mSelection; }
+    //! Return selected item, or null if none selected
+    UIElement* getSelectedItem() const;
+    
+protected:
+    //! Update content element when list has changed
+    void updateList();
+    //! Ensure full visibility of the selected item
+    void ensureItemVisibility();
+    
+    //! List items
+    std::vector<SharedPtr<UIElement> > mItems;
+    //! Current selection
+    unsigned mSelection;
+    
+private:
+    //! Handle focus change to check for selection change
+    void handleTryFocus(StringHash eventType, VariantMap& eventData);
+};
+
+#endif // UI_LISTVIEW_H

+ 1 - 14
Engine/UI/MenuItem.cpp

@@ -56,20 +56,6 @@ void MenuItem::setStyle(const XMLElement& element, ResourceCache* cache)
         setPopupOffset(element.getChildElement("popupoffset").getIntVector2("value"));
 }
 
-void MenuItem::update(float timeStep)
-{
-    // Keep pressed state if showing the popup
-    if ((!mHovering) && (!mShowPopup))
-        setPressed(false);
-}
-
-void MenuItem::onHover(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers)
-{
-    // Keep pressed state if showing the popup
-    setPressed(((buttons & MOUSEB_LEFT) != 0) || (mShowPopup));
-    mHovering = true;
-}
-
 void MenuItem::onClick(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers)
 {
     setPressed(true);
@@ -141,6 +127,7 @@ void MenuItem::showPopup(bool enable)
     }
     
     mShowPopup = enable;
+    mSelected = enable;
 }
 
 void MenuItem::handleTryFocus(StringHash eventType, VariantMap& eventData)

+ 0 - 4
Engine/UI/MenuItem.h

@@ -38,10 +38,6 @@ class MenuItem : public Button
     
     //! Set UI element style from XML data
     virtual void setStyle(const XMLElement& element, ResourceCache* cache);
-    //! Perform UI element update
-    virtual void update(float timeStep);
-    //! React to mouse hover
-    virtual void onHover(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers);
     //! React to mouse click
     virtual void onClick(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers);
     

+ 20 - 20
Engine/UI/ScrollView.cpp

@@ -89,7 +89,7 @@ void ScrollView::setStyle(const XMLElement& element, ResourceCache* cache)
     
     UIElement* root = getRootElement();
     if ((root) && (element.hasChildElement("contentelement")))
-        setElement(root->getChild(element.getChildElement("contentelement").getString("name"), true));
+        setContentElement(root->getChild(element.getChildElement("contentelement").getString("name"), true));
     
     // Set the scrollbar orientations again and perform size update now that the style is known
     mHorizontalScrollBar->setOrientation(O_HORIZONTAL);
@@ -102,7 +102,7 @@ void ScrollView::onKey(int key, int buttons, int qualifiers)
     switch (key)
     {
     case KEY_LEFT:
-        if (mHorizontalScrollBar)
+        if (mHorizontalScrollBar->isVisible())
         {
             if (qualifiers & QUAL_CTRL)
                 mHorizontalScrollBar->setValue(0.0f);
@@ -112,7 +112,7 @@ void ScrollView::onKey(int key, int buttons, int qualifiers)
         break;
         
     case KEY_RIGHT:
-        if (mHorizontalScrollBar)
+        if (mHorizontalScrollBar->isVisible())
         {
             if (qualifiers & QUAL_CTRL)
                 mHorizontalScrollBar->setValue(mHorizontalScrollBar->getRange());
@@ -122,7 +122,7 @@ void ScrollView::onKey(int key, int buttons, int qualifiers)
         break;
         
     case KEY_UP:
-        if (mVerticalScrollBar)
+        if (mVerticalScrollBar->isVisible())
         {
             if (qualifiers & QUAL_CTRL)
                 mVerticalScrollBar->setValue(0.0f);
@@ -132,7 +132,7 @@ void ScrollView::onKey(int key, int buttons, int qualifiers)
         break;
         
     case KEY_DOWN:
-        if (mVerticalScrollBar)
+        if (mVerticalScrollBar->isVisible())
         {
             if (qualifiers & QUAL_CTRL)
                 mVerticalScrollBar->setValue(mVerticalScrollBar->getRange());
@@ -142,22 +142,22 @@ void ScrollView::onKey(int key, int buttons, int qualifiers)
         break;
         
     case KEY_PAGEUP:
-        if (mVerticalScrollBar)
+        if (mVerticalScrollBar->isVisible())
             mVerticalScrollBar->changeValue(-mPageStep);
         break;
         
     case KEY_PAGEDOWN:
-        if (mVerticalScrollBar)
+        if (mVerticalScrollBar->isVisible())
             mVerticalScrollBar->changeValue(mPageStep);
         break;
     
     case KEY_HOME:
-        if (mVerticalScrollBar)
+        if (mVerticalScrollBar->isVisible())
             mVerticalScrollBar->setValue(0.0f);
         break;
     
     case KEY_END:
-        if (mVerticalScrollBar)
+        if (mVerticalScrollBar->isVisible())
             mVerticalScrollBar->setValue(mVerticalScrollBar->getRange());
         break;
     }
@@ -178,21 +178,21 @@ void ScrollView::onResize()
     updateViewSize();
 }
 
-void ScrollView::setElement(UIElement* element)
+void ScrollView::setContentElement(UIElement* element)
 {
-    if (element == mElement)
+    if (element == mContentElement)
         return;
     
-    if (mElement)
+    if (mContentElement)
     {
-        mScrollPanel->removeChild(mElement);
-        unsubscribeFromEvent(mElement, EVENT_RESIZED);
+        mScrollPanel->removeChild(mContentElement);
+        unsubscribeFromEvent(mContentElement, EVENT_RESIZED);
     }
-    mElement = element;
-    if (mElement)
+    mContentElement = element;
+    if (mContentElement)
     {
-        mScrollPanel->addChild(mElement);
-        subscribeToEvent(mElement, EVENT_RESIZED, EVENT_HANDLER(ScrollView, handleElementResized));
+        mScrollPanel->addChild(mContentElement);
+        subscribeToEvent(mContentElement, EVENT_RESIZED, EVENT_HANDLER(ScrollView, handleElementResized));
     }
     
     updateViewSize();
@@ -255,8 +255,8 @@ bool ScrollView::getNormalizeScrollStep() const
 void ScrollView::updateViewSize()
 {
     IntVector2 size(IntVector2::sZero);
-    if (mElement)
-        size = mElement->getSize();
+    if (mContentElement)
+        size = mContentElement->getSize();
     
     mViewSize.mX = max(size.mX, mScrollPanel->getWidth());
     mViewSize.mY = max(size.mY, mScrollPanel->getHeight());

+ 3 - 3
Engine/UI/ScrollView.h

@@ -48,7 +48,7 @@ public:
     virtual void onResize();
     
     //! Set content element
-    void setElement(UIElement* element);
+    void setContentElement(UIElement* element);
     //! Set view offset from the top-left corner
     void setViewPosition(const IntVector2& position);
     //! Set view offset from the top-left corner
@@ -65,7 +65,7 @@ public:
     //! Return view offset from the top-left corner
     const IntVector2& getViewPosition() const { return mViewPosition; }
     //! Return content element
-    UIElement* getElement() const { return mElement; }
+    UIElement* getContentElement() const { return mContentElement; }
     //! Return horizontal scroll bar
     ScrollBar* getHorizontalScrollBar() const { return mHorizontalScrollBar; }
     //! Return vertical scroll bar
@@ -92,7 +92,7 @@ protected:
     void updateView(const IntVector2& position);
     
     //! Content element
-    SharedPtr<UIElement> mElement;
+    SharedPtr<UIElement> mContentElement;
     //! Horizontal scroll bar
     SharedPtr<ScrollBar> mHorizontalScrollBar;
     //! Vertical scroll bar

+ 9 - 7
Engine/UI/Text.cpp

@@ -94,20 +94,20 @@ void Text::setStyle(const XMLElement& element, ResourceCache* cache)
 
 void Text::getBatches(std::vector<UIBatch>& batches, std::vector<UIQuad>& quads, const IntRect& currentScissor)
 {
-    // Hovering batch
-    if (((mHovering) || (mSelected)) && (mHoverColor.mA > 0.0f))
+    // Hovering or whole selection batch
+    if ((mHovering && (mHoverColor.mA > 0.0f)) || (mSelected && (mSelectionColor.mA > 0.0f)))
     {
         UIBatch batch;
         batch.begin(&quads);
         batch.mBlendMode = BLEND_ALPHA;
         batch.mScissor = currentScissor;
         batch.mTexture = 0;
-        batch.addQuad(*this, 0, 0, getWidth(), getHeight(), 0, 0, 0, 0, mHoverColor);
+        batch.addQuad(*this, 0, 0, getWidth(), getHeight(), 0, 0, 0, 0, mSelected ? mSelectionColor : mHoverColor);
         UIBatch::addOrMerge(batch, batches);
     }
     
-    // Selection batch
-    if ((mSelectionLength) && (mCharSizes.size() >= mSelectionStart + mSelectionLength) && (mSelectionColor.mA > 0.0f))
+    // Partial Selection batch
+    if ((!mSelected) && (mSelectionLength) && (mCharSizes.size() >= mSelectionStart + mSelectionLength) && (mSelectionColor.mA > 0.0f))
     {
         UIBatch batch;
         batch.begin(&quads);
@@ -430,9 +430,11 @@ void Text::updateText(bool inResize)
         mCharPositions[mText.length()] = IntVector2(x, y);
     }
     
-    // Resize self only when not using wordwrap
+    // Set minimum size to correspond to the text size
     if (!mWordwrap)
-        setSize(width, height);
+        setMinSize(width, height);
+    else
+        setMinSize(0, height);
 }
 
 void Text::validateSelection()

+ 1 - 1
Engine/UI/UI.cpp

@@ -118,7 +118,7 @@ void UI::setFocusElement(UIElement* element)
     eventData[P_ELEMENT] = (void*)element;
     sendEvent(EVENT_TRYFOCUS, eventData);
     
-    // The event receivers may divert the focus
+    // The event receivers may optionally divert the focus
     element = static_cast<UIElement*>(eventData[P_ELEMENT].getPtr());
     
     if (element)

+ 31 - 14
Engine/UI/UIElement.cpp

@@ -376,14 +376,15 @@ void UIElement::setSize(const IntVector2& size)
             
             markDirty();
             onResize();
+            updateLayout();
             
             using namespace Resized;
             
             VariantMap eventData;
             eventData[P_ELEMENT] = (void*)this;
-            sendEvent(mFocus ? EVENT_FOCUSED : EVENT_DEFOCUSED, eventData);
-            
-            updateLayout();
+            eventData[P_WIDTH] = mSize.mX;
+            eventData[P_HEIGHT] = mSize.mY;
+            sendEvent(EVENT_RESIZED, eventData);
         }
     }
     
@@ -422,12 +423,12 @@ void UIElement::setMinSize(int width, int height)
 
 void UIElement::setMinWidth(int width)
 {
-    setMaxSize(IntVector2(width, mMinSize.mY));
+    setMinSize(IntVector2(width, mMinSize.mY));
 }
 
 void UIElement::setMinHeight(int height)
 {
-    setMaxSize(IntVector2(mMinSize.mX, height));
+    setMinSize(IntVector2(mMinSize.mX, height));
 }
 
 void UIElement::setMaxSize(const IntVector2& maxSize)
@@ -645,6 +646,7 @@ void UIElement::updateLayout()
     if (mLayoutOrientation == O_HORIZONTAL)
     {
         int maxChildHeight = 0;
+        int maxChildMinHeight = 0;
         
         if (mHorizontalLayoutMode == LM_RESIZEELEMENT)
         {
@@ -654,11 +656,14 @@ void UIElement::updateLayout()
                     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)
             {
@@ -684,24 +689,28 @@ void UIElement::updateLayout()
                 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 idx = 0;
+            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[idx], getLayoutChildPosition(mChildren[i]).mY);
-                mChildren[i]->setSize(sizes[idx], mVerticalLayoutMode == LM_RESIZECHILDREN ? getHeight() - mLayoutBorder.mTop -
+                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());
-                ++idx;
+                ++j;
             }
         }
     }
@@ -709,6 +718,7 @@ void UIElement::updateLayout()
     if (mLayoutOrientation == O_VERTICAL)
     {
         int maxChildWidth = 0;
+        int maxChildMinWidth = 0;
         
         if (mVerticalLayoutMode == LM_RESIZEELEMENT)
         {
@@ -718,8 +728,11 @@ void UIElement::updateLayout()
                     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));
             
@@ -747,24 +760,28 @@ void UIElement::updateLayout()
                 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 idx = 0;
+            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[idx]);
+                mChildren[i]->setPosition(getLayoutChildPosition(mChildren[i]).mX, positions[j]);
                 mChildren[i]->setSize(mHorizontalLayoutMode == LM_RESIZECHILDREN ? getWidth() - mLayoutBorder.mLeft -
-                    mLayoutBorder.mRight : mChildren[i]->getWidth(), sizes[idx]);
-                ++idx;
+                    mLayoutBorder.mRight : mChildren[i]->getWidth(), sizes[j]);
+                ++j;
             }
         }
     }

+ 1 - 1
Engine/UI/UIElement.h

@@ -198,7 +198,7 @@ public:
     void setFocusMode(FocusMode mode);
     //! Set whether is focused. Usually called by UI
     void setFocus(bool enable);
-    //! Set selected mode. Actual meaning is element dependent, but is visually same as a constant hover
+    //! Set selected mode. Actual meaning is element dependent, for example constant hover or pressed effect
     void setSelected(bool enable);
     //! Set whether is visible
     void setVisible(bool enable);

+ 1 - 1
Engine/UI/UIEvents.h

@@ -31,7 +31,7 @@ DEFINE_EVENT(EVENT_RESIZED, Resized)
 {
     EVENT_PARAM(P_ELEMENT, Element);            // UIElement pointer
     EVENT_PARAM(P_WIDTH, Width);                // int
-    EVENT_PARAM(P_Height, Height);              // int
+    EVENT_PARAM(P_HEIGHT, Height);              // int
 }
 
 //! UI element visibility changed