Procházet zdrojové kódy

Initial FileSelector implementation.
Console & DebugHud changed to load style from XML file.
UI bugfixes.

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

+ 32 - 10
Bin/Data/Scripts/GraphicsTest.as

@@ -29,6 +29,8 @@ array<Entity@> animatingObjects;
 array<Entity@> billboards;
 array<Entity@> lights;
 
+FileSelector@ fileSelector;
+
 void start()
 {
     if (engine.isHeadless())
@@ -303,31 +305,51 @@ void animateScene(float timeStep)
 
 void initConsole()
 {
+    XMLFile@ uiStyle = cache.getResource("XMLFile", "UI/DefaultStyle.xml");
+
     Console@ console = engine.createConsole();
+    console.setStyle(uiStyle);
     console.setNumRows(16);
-    console.setFont(cache.getResource("Font", "cour.ttf"), 12);
-    BorderImage@ cursor = console.getLineEdit().getCursor();
-    cursor.setWidth(4);
-    cursor.setTexture(cache.getResource("Texture2D", "Textures/UI.png"));
-    cursor.setImageRect(12, 0, 16, 16);
 
     engine.createDebugHud();
-    debugHud.setFont(cache.getResource("Font", "cour.ttf"), 12);
+    debugHud.setStyle(uiStyle);
     debugHud.setMode(DEBUGHUD_SHOW_STATS | DEBUGHUD_SHOW_MODE);
 }
 
 void initUI()
 {
     XMLFile@ uiStyle = cache.getResource("XMLFile", "UI/DefaultStyle.xml");
-    
+
     Cursor@ cursor = Cursor("Cursor");
     cursor.setStyleAuto(uiStyle);
     cursor.setPosition(renderer.getWidth() / 2, renderer.getHeight() / 2);
     ui.setCursor(cursor);
 
-    //XMLFile@ uiLayout = cache.getResource("XMLFile", "UI/TestLayout.xml");
-    //UIElement@ layoutRoot = ui.loadLayout(uiLayout, uiStyle);
-    //uiRoot.addChild(layoutRoot);
+	/*
+    XMLFile@ uiLayout = cache.getResource("XMLFile", "UI/TestLayout.xml");
+    UIElement@ layoutRoot = ui.loadLayout(uiLayout, uiStyle);
+    uiRoot.addChild(layoutRoot);
+
+    @fileSelector = FileSelector();
+    fileSelector.setStyle(uiStyle);
+    fileSelector.setTitle("Load file");
+    fileSelector.setButtonTexts("Load", "Cancel");
+    fileSelector.getWindow().setPosition(200, 100);
+
+    array<string> filters;
+    filters.resize(2);
+    filters[0] = "*.*";
+    filters[1] = "*.exe";
+    fileSelector.setFilters(filters, 0);
+
+    subscribeToEvent(fileSelector, "FileSelected", "handleFileSelected");
+    */
+}
+
+void handleFileSelected(StringHash eventType, VariantMap& eventData)
+{
+	unsubscribeFromEvent(fileSelector, "FileSelected");
+	@fileSelector = null;
 }
 
 void createCamera()

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

@@ -77,16 +77,14 @@ void initConsole()
     if (engine.isHeadless())
         return;
 
+    XMLFile@ uiStyle = cache.getResource("XMLFile", "UI/DefaultStyle.xml");
+
     Console@ console = engine.createConsole();
+    console.setStyle(uiStyle);
     console.setNumRows(16);
-    console.setFont(cache.getResource("Font", "cour.ttf"), 12);
-    BorderImage@ cursor = console.getLineEdit().getCursor();
-    cursor.setWidth(4);
-    cursor.setTexture(cache.getResource("Texture2D", "Textures/UI.png"));
-    cursor.setImageRect(12, 0, 16, 16);
 
     engine.createDebugHud();
-    debugHud.setFont(cache.getResource("Font", "cour.ttf"), 12);
+    debugHud.setStyle(uiStyle);
 }
 
 void initScene()

+ 60 - 2
Bin/Data/UI/DefaultStyle.xml

@@ -104,7 +104,7 @@
         <hoveroffset value="0 16" />
         <text>
             <position value="1 1" />
-            <font name="Cour.ttf" size="10" />
+            <font name="Cour.ttf" size="12" />
             <selectioncolor value="1.0 0.5 0.5" />
         </text>
         <cursor>
@@ -299,6 +299,64 @@
         <resizable enable="true" />
     </element>
     <element type="Text">
-        <font name="Cour.ttf" size="10" />
+        <font name="Cour.ttf" size="12" />
+    </element>
+    <element type="DebugHudText">
+        <font name="Cour.ttf" size="12" />
+    </element>
+    <element type="ConsoleBackground">
+        <layout spacing="0" border="4 4 4 4" />
+        <color topleft="0 0.25 0 0.75" topright="0 0.25 0 0.75" bottomleft="0.25 0.75 0.25 0.75" bottomright="0.25 0.75 0.25 0.75" />
+    </element>
+    <element type="ConsoleText">
+        <font name="Cour.ttf" size="12" />
+    </element>
+    <element type="ConsoleLineEdit">
+        <color value="0 0 0 0.5" />
+        <text>
+            <font name="Cour.ttf" size="12" />
+            <selectioncolor value="0 0.5 0 0.75" />
+        </text>
+        <cursor>
+            <size value="4 16" />
+            <texture name="Textures/UI.png" />
+            <imagerect value="12 0 16 16" />
+        </cursor>
+    </element>
+    <element type="FileSelector">
+        <size value="400 300" />
+        <minsize value="400 300" />
+        <movable enable="true" />
+        <resizable enable="true" />
+        <resizeborder value="8 8 8 8" />
+        <layout spacing="4" border="8 8 8 8" />
+    </element>
+    <element type="FileSelectorButton">
+        <fixedsize value="96 24" />
+    </element>
+    <element type="FileSelectorButtonText">
+        <font name="Cour.ttf" size="12" />
+    </element>
+    <element type="FileSelectorFilterList">
+        <fixedwidth value="64" />
+        <layout mode="horizontal" border="4 0 4 0" />
+        <resizepopup enable="true" />
+        <popup>
+            <layout border="4 4 4 4" />
+        </popup>
+    </element>
+    <element type="FileSelectorFilterText">
+        <font name="Cour.ttf" size="12" />
+        <hovercolor value="0.5 0.75 0.5" />
+    </element>
+    <element type="FileSelectorLayout">
+        <layout spacing="4" />
+    </element>
+    <element type="FileSelectorListText">
+        <font name="Cour.ttf" size="12" />
+        <selectioncolor value="0.5 0.75 0.5" />
+    </element>
+    <element type="FileSelectorTitleText">
+        <font name="Cour.ttf" size="15" />
     </element>
 </elements>

+ 49 - 16
Engine/Common/File.cpp

@@ -34,7 +34,7 @@
 
 static std::set<std::string> allowedDirectories;
 
-void scanDirectoryInternal(std::vector<std::string>& result, std::string path, const std::string& filter, bool recursive);
+void scanDirectoryInternal(std::vector<std::string>& result, std::string path, const std::string& filter, bool recursive, bool directories, bool hidden);
 
 File::File(const std::string& fileName, FileMode mode) :
     mHandle(0),
@@ -180,14 +180,25 @@ bool fileExists(const std::string& fileName)
     if (!checkDirectoryAccess(getPath(fileName)))
         return false;
     
-    FILE* file = fopen(getOSPath(fileName).c_str(), "rb");
-    if (file)
-    {
-        fclose(file);
-        return true;
-    }
-    else
+    std::string fixedName = getOSPath(unfixPath(fileName), true);
+    DWORD attributes = GetFileAttributes(fixedName.c_str());
+    if ((attributes == INVALID_FILE_ATTRIBUTES) || (attributes & FILE_ATTRIBUTE_DIRECTORY))
         return false;
+    
+    return true;
+}
+
+bool directoryExists(const std::string& pathName)
+{
+    if (!checkDirectoryAccess(pathName))
+        return false;
+    
+    std::string fixedName = getOSPath(unfixPath(pathName), true);
+    DWORD attributes = GetFileAttributes(fixedName.c_str());
+    if ((attributes == INVALID_FILE_ATTRIBUTES) || (!(attributes & FILE_ATTRIBUTE_DIRECTORY)))
+        return false;
+    
+    return true;
 }
 
 void createDirectory(const std::string& pathName)
@@ -203,7 +214,14 @@ void createDirectory(const std::string& pathName)
     SAFE_EXCEPTION("Failed to create directory " + pathName);
 }
 
-std::vector<std::string> scanDirectory(const std::string& pathName, const std::string& filter, bool recursive)
+std::string getWorkingDirectory()
+{
+    char currentDir[MAX_PATH];
+    GetCurrentDirectory(MAX_PATH, currentDir);
+    return fixPath(std::string(currentDir));
+}
+
+std::vector<std::string> scanDirectory(const std::string& pathName, const std::string& filter, bool recursive, bool directories, bool hidden)
 {
     std::vector<std::string> ret;
     
@@ -216,7 +234,7 @@ std::vector<std::string> scanDirectory(const std::string& pathName, const std::s
     if (SetCurrentDirectory(getOSPath(pathName, true).c_str()) == FALSE)
         return ret;
     
-    scanDirectoryInternal(ret, "", filter, recursive);
+    scanDirectoryInternal(ret, "", filter, recursive, directories, hidden);
     SetCurrentDirectory(oldDir);
     
     return ret;
@@ -329,6 +347,18 @@ std::string fixPath(const std::string& path)
     return replace(ret, '\\', '/');
 }
 
+std::string unfixPath(const std::string& path)
+{
+    if (!path.empty())
+    {
+        char last = path[path.length() - 1];
+        if ((last == '/') || (last == '\\'))
+            return path.substr(0, path.length() - 1);
+    }
+    
+    return path;
+}
+
 std::string getOSPath(const std::string& pathName, bool forNativeApi)
 {
     // On MSVC, replace slash always with backslash. On MinGW only if going to do Win32 native calls
@@ -342,7 +372,7 @@ std::string getOSPath(const std::string& pathName, bool forNativeApi)
         return pathName;
 }
 
-void scanDirectoryInternal(std::vector<std::string>& result, std::string path, const std::string& filter, bool recursive)
+void scanDirectoryInternal(std::vector<std::string>& result, std::string path, const std::string& filter, bool recursive, bool directories, bool hidden)
 {
     path = fixPath(path);
     std::string pathAndFilter = getOSPath(path + filter, true);
@@ -354,15 +384,18 @@ void scanDirectoryInternal(std::vector<std::string>& result, std::string path, c
         do
         {
             std::string fileName((const char*)&info.cFileName[0]);
-            // Avoid files like . .. and .svn
-            if ((!fileName.empty()) && (fileName[0] != '.'))
+            if (!fileName.empty())
             {
+                if ((info.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) && (!hidden))
+                    continue;
                 if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
                 {
-                    if (recursive)
-                        scanDirectoryInternal(result, path + fileName, filter, true);
+                    if (directories)
+                        result.push_back(path + fileName);
+                    if ((recursive) && (fileName != ".") && (fileName != ".."))
+                        scanDirectoryInternal(result, path + fileName, filter, recursive, directories, hidden);
                 }
-                else
+                else if (!directories)
                     result.push_back(path + fileName);
             }
         } 

+ 7 - 1
Engine/Common/File.h

@@ -88,10 +88,14 @@ private:
 
 //! Check if a file exists
 bool fileExists(const std::string& fileName);
+//! Check if a directory exists
+bool directoryExists(const std::string& pathName);
 //! Create a directory
 void createDirectory(const std::string& pathName);
+//! Return the absolute working directory
+std::string getWorkingDirectory();
 //! Scan a directory for specified files
-std::vector<std::string> scanDirectory(const std::string& pathName, const std::string& filter, bool recursive);
+std::vector<std::string> scanDirectory(const std::string& pathName, const std::string& filter, bool recursive, bool directories, bool hidden);
 //! Register a path as being allowed to access
 void registerDirectory(const std::string& pathName);
 //! Check if a path is allowed to be accessed. If no paths defined, all are allowed
@@ -108,6 +112,8 @@ std::string getExtension(const std::string& fullPath, bool lowerCaseExtension =
 std::string getFileNameAndExtension(const std::string& fullPath, bool lowerCaseExtension = true);
 //! Fix a path so that it contains a slash in the end, and convert backslashes to slashes
 std::string fixPath(const std::string& pathName);
+//! Remove the slash or backslash from the end of a path if exists
+std::string unfixPath(const std::string& pathName);
 //! Convert a path to the format required by the operating system
 std::string getOSPath(const std::string& pathName, bool forNativeApi = false);
 

+ 1 - 1
Engine/Engine/Client.cpp

@@ -724,7 +724,7 @@ unsigned Client::checkPackages()
     
     // To avoid resource version conflicts and to keep the amount of open packages reasonable, remove all existing
     // downloaded packages from the resource cache first
-    std::vector<std::string> downloadedPackages = scanDirectory(mDownloadDirectory, "*.pak", false);
+    std::vector<std::string> downloadedPackages = scanDirectory(mDownloadDirectory, "*.pak", false, false, false);
     std::vector<SharedPtr<PackageFile> > registeredPackages = mCache->getPackageFiles();
     for (std::vector<SharedPtr<PackageFile> >::iterator i = registeredPackages.begin(); i != registeredPackages.end();)
     {

+ 45 - 36
Engine/Engine/Console.cpp

@@ -29,6 +29,7 @@
 #include "LineEdit.h"
 #include "Renderer.h"
 #include "RendererEvents.h"
+#include "ResourceCache.h"
 #include "ScriptEngine.h"
 #include "StringUtils.h"
 #include "Text.h"
@@ -37,9 +38,10 @@
 
 #include "DebugNew.h"
 
+static const int DEFAULT_CONSOLE_ROWS = 16;
+
 Console::Console(Engine* engine) :
-    mEngine(engine),
-    mFontSize(DEFAULT_FONT_SIZE)
+    mEngine(engine)
 {
     LOGINFO("Console created");
     
@@ -55,25 +57,20 @@ Console::Console(Engine* engine) :
         
         mBackground = new BorderImage();
         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->setVisible(false); // Hide by default
         mBackground->setPriority(200); // Show on top of the debug HUD
-        mBackground->setLayout(LM_VERTICAL, 0, IntRect(4, 4, 4, 4));
+        mBackground->setLayout(LM_VERTICAL);
         
         mLineEdit = new LineEdit();
-        mLineEdit->setColor(Color(0.0f, 0.0f, 0.0f, 0.5f));
-        mLineEdit->getTextElement()->setSelectionColor(Color(0.0f, 0.5f, 0.0f, 0.75f));
         mLineEdit->setFocusMode(FM_FOCUSABLE); // Do not allow defocus with ESC
         mBackground->addChild(mLineEdit);
         
         uiRoot->addChild(mBackground);
         
+        setNumRows(DEFAULT_CONSOLE_ROWS);
         updateElements();
         
         subscribeToEvent(mLineEdit, EVENT_TEXTFINISHED, EVENT_HANDLER(Console, handleTextFinished));
@@ -98,6 +95,9 @@ void Console::write(const std::string& message)
 {
     if (!mRows.size())
         return;
+    // If the rows are not fully initialized yet, do not write the message
+    if (!mRows[mRows.size() - 1])
+        return;
     
     // Be prepared for possible multi-line messages
     std::vector<std::string> rows = split(message, '\n');
@@ -111,6 +111,29 @@ void Console::write(const std::string& message)
     }
 }
 
+void Console::setStyle(XMLFile* style)
+{
+    if ((!style) || (!mEngine) || (!mBackground) || (!mLineEdit))
+        return;
+    
+    mStyle = style;
+    ResourceCache* cache = mEngine->getResourceCache();
+    XMLElement backgroundElem = UIElement::getStyleElement(style, "ConsoleBackground");
+    if (backgroundElem)
+        mBackground->setStyle(backgroundElem, cache);
+    XMLElement textElem = UIElement::getStyleElement(style, "ConsoleText");
+    if (textElem)
+    {
+        for (unsigned i = 0; i < mRows.size(); ++i)
+            mRows[i]->setStyle(textElem, cache);
+    }
+    XMLElement lineEditElem = UIElement::getStyleElement(style, "ConsoleLineEdit");
+    if (lineEditElem)
+        mLineEdit->setStyle(lineEditElem, cache);
+    
+    updateElements();
+}
+
 void Console::setVisible(bool enable)
 {
     if (!mBackground)
@@ -130,16 +153,21 @@ void Console::toggle()
 
 void Console::setNumRows(unsigned rows)
 {
-    if (!mBackground)
+    if ((!mBackground) || (!rows))
         return;
     
     mBackground->removeAllChildren();
-    mRows.clear();
     
     mRows.resize(rows);
     for (unsigned i = 0; i < mRows.size(); ++i)
     {
-        mRows[i] = new Text();
+        if (!mRows[i])
+        {
+            mRows[i] = new Text();
+            XMLElement textElem = UIElement::getStyleElement(mStyle, "ConsoleText");
+            if (textElem)
+                mRows[i]->setStyle(textElem, mEngine->getResourceCache());
+        }
         mBackground->addChild(mRows[i]);
     }
     mBackground->addChild(mLineEdit);
@@ -147,15 +175,11 @@ void Console::setNumRows(unsigned rows)
     updateElements();
 }
 
-void Console::setFont(Font* font, int size)
+void Console::updateElements()
 {
-    if (!mBackground)
-        return;
-    
-    mFont = font;
-    mFontSize = max(size, 1);
-    
-    updateElements();
+    int width = mEngine->getRenderer()->getWidth();
+    mLineEdit->setFixedHeight(mLineEdit->getTextElement()->getRowHeight());
+    mBackground->setFixedWidth(width);
 }
 
 bool Console::isVisible() const
@@ -165,21 +189,6 @@ bool Console::isVisible() const
     return mBackground->isVisible();
 }
 
-void Console::updateElements()
-{
-    int width = mEngine->getRenderer()->getWidth();
-    
-    if (mFont)
-    {
-        for (unsigned i = 0; i < mRows.size(); ++i)
-            mRows[i]->setFont(mFont, mFontSize);
-        mLineEdit->getTextElement()->setFont(mFont, mFontSize);
-    }
-    
-    mLineEdit->setFixedHeight(mLineEdit->getTextElement()->getRowHeight());
-    mBackground->setFixedWidth(width);
-}
-
 void Console::handleTextFinished(StringHash eventType, VariantMap& eventData)
 {
     using namespace TextFinished;

+ 14 - 13
Engine/Engine/Console.h

@@ -31,8 +31,9 @@
 class BorderImage;
 class Engine;
 class Font;
-class Text;
 class LineEdit;
+class Text;
+class XMLFile;
 
 //! A console window with log history and AngelScript prompt
 class Console : public RefCounted, public EventListener, public LogListener
@@ -46,27 +47,29 @@ public:
     //! Write a log message
     virtual void write(const std::string& message);
     
+    //! Set UI elements' style from an XML file
+    void setStyle(XMLFile* style);
     //! Show or hide. Showing automatically focuses the line edit
     void setVisible(bool enable);
     //! Toggle visibility
     void toggle();
     //! Set number of rows
     void setNumRows(unsigned rows);
-    //! Set font to use
-    void setFont(Font* font, int size);
+    //! Update elements to layout properly. Call this after manually adjusting the sub-elements
+    void updateElements();
     
-    //! Return whether is visible
-    bool isVisible() const;
-    //! Return number of rows
-    unsigned getNumRows() const { return mRows.size(); }
+    //! Return the UI style file
+    XMLFile* getStyle() const { return mStyle; }
     //! Return the background element
     BorderImage* getBackground() const { return mBackground; }
     //! Return the line edit element
     LineEdit* getLineEdit() const { return mLineEdit; }
+    //! Return whether is visible
+    bool isVisible() const;
+    //! Return number of rows
+    unsigned getNumRows() const { return mRows.size(); }
     
 private:
-    //! Update layout
-    void updateElements();
     //! Handle enter pressed on the line edit
     void handleTextFinished(StringHash eventType, VariantMap& eventData);
     //! Handle rendering window resize
@@ -74,16 +77,14 @@ private:
     
     //! Engine
     Engine* mEngine;
+    //! UI style file
+    SharedPtr<XMLFile> mStyle;
     //! Background
     SharedPtr<BorderImage> mBackground;
-    //! Font
-    SharedPtr<Font> mFont;
     //! Text rows
     std::vector<SharedPtr<Text> > mRows;
     //! Line edit
     SharedPtr<LineEdit> mLineEdit;
-    //! Font size
-    int mFontSize;
 };
 
 #endif // ENGINE_CONSOLE_H

+ 11 - 5
Engine/Engine/DebugHud.cpp

@@ -195,14 +195,20 @@ void DebugHud::update(float timeStep)
     }
 }
 
-void DebugHud::setFont(Font* font, int size)
+void DebugHud::setStyle(XMLFile* style)
 {
-    if ((!mStatsText) || (!mModeText) || (!mProfilerText))
+    if ((!style) || (!mEngine) || (!mStatsText) || (!mModeText) || (!mProfilerText))
         return;
     
-    mStatsText->setFont(font, size);
-    mModeText->setFont(font, size);
-    mProfilerText->setFont(font, size);
+    mStyle = style;
+    ResourceCache* cache = mEngine->getResourceCache();
+    XMLElement textElem = UIElement::getStyleElement(style, "DebugHudText");
+    if (textElem)
+    {
+        mStatsText->setStyle(textElem, cache);
+        mModeText->setStyle(textElem, cache);
+        mProfilerText->setStyle(textElem, cache);
+    }
 }
 
 void DebugHud::setMode(unsigned mode)

+ 13 - 2
Engine/Engine/DebugHud.h

@@ -29,6 +29,7 @@
 class Engine;
 class Font;
 class Text;
+class XMLFile;
 
 static const unsigned DEBUGHUD_SHOW_NONE = 0x0;
 static const unsigned DEBUGHUD_SHOW_STATS = 0x1;
@@ -46,8 +47,8 @@ public:
     
     //! Update
     void update(float timeStep);
-    //! Set font to use
-    void setFont(Font* font, int size);
+    //! Set UI elements' style from an XML file
+    void setStyle(XMLFile* style);
     //! Set elements to show
     void setMode(unsigned mode);
     //! Set profiler accumulation interval
@@ -57,6 +58,14 @@ public:
     //! Toggle all elements
     void toggleAll();
     
+    //! Return the UI style file
+    XMLFile* getStyle() const { return mStyle; }
+    //! Return rendering stats text
+    Text* getStatsText() const { return mStatsText; }
+    //! Return rendering mode text
+    Text* getModeText() const { return mModeText; }
+    //! Return profiler text
+    Text* getProfilerText() const { return mProfilerText; }
     //! Return currently shown elements
     unsigned getMode() const;
     //! Return profiler accumulation interval
@@ -65,6 +74,8 @@ public:
 private:
     //! Engine
     Engine* mEngine;
+    //! UI style file
+    SharedPtr<XMLFile> mStyle;
     //! Rendering stats text
     SharedPtr<Text> mStatsText;
     //! Rendering mode text

+ 5 - 3
Engine/Engine/RegisterCommon.cpp

@@ -522,9 +522,9 @@ static bool VariantEqualsBuffer(const VectorBuffer& buffer, Variant* ptr)
     return (*ptr) == buffer.getBuffer();
 }
 
-static CScriptArray* ScanDirectory(const std::string& pathName, const std::string& filter, bool recursive)
+static CScriptArray* ScanDirectory(const std::string& pathName, const std::string& filter, bool recursive, bool directories, bool hidden)
 {
-    std::vector<std::string> result = scanDirectory(pathName, filter, recursive);
+    std::vector<std::string> result = scanDirectory(pathName, filter, recursive, directories, hidden);
     return vectorToArray<std::string>(result, "array<string>");
 }
 
@@ -556,13 +556,15 @@ static void registerSerialization(asIScriptEngine* engine)
     engine->RegisterObjectMethod("File", "uint getChecksum()", asMETHOD(File, getChecksum), asCALL_THISCALL);
     
     engine->RegisterGlobalFunction("bool fileExists(const string& in)", asFUNCTION(fileExists), asCALL_CDECL);
+    engine->RegisterGlobalFunction("bool directoryExists(const string& in)", asFUNCTION(directoryExists), asCALL_CDECL);
     engine->RegisterGlobalFunction("void createDirectory(const string& in)", asFUNCTION(createDirectory), asCALL_CDECL);
-    engine->RegisterGlobalFunction("array<string>@ scanDirectory(const string& in, const string& in, bool)", asFUNCTION(ScanDirectory), asCALL_CDECL);
+    engine->RegisterGlobalFunction("array<string>@ scanDirectory(const string& in, const string& in, bool, bool, bool)", asFUNCTION(ScanDirectory), asCALL_CDECL);
     engine->RegisterGlobalFunction("string getPath(const string& in)", asFUNCTION(getPath), asCALL_CDECL);
     engine->RegisterGlobalFunction("string getFileName(const string& in)", asFUNCTION(getFileName), asCALL_CDECL);
     engine->RegisterGlobalFunction("string getExtension(const string& in, bool)", asFUNCTION(getExtension), asCALL_CDECL);
     engine->RegisterGlobalFunction("string getFileNameAndExtension(const string& in, bool)", asFUNCTION(getFileNameAndExtension), asCALL_CDECL);
     engine->RegisterGlobalFunction("string fixPath(const string& in)", asFUNCTION(fixPath), asCALL_CDECL);
+    engine->RegisterGlobalFunction("string unfixPath(const string& in)", asFUNCTION(unfixPath), asCALL_CDECL);
     
     engine->RegisterObjectType("VectorBuffer", sizeof(VectorBuffer), asOBJ_VALUE | asOBJ_APP_CLASS_CDA);
     engine->RegisterObjectBehaviour("VectorBuffer", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ConstructVectorBuffer), asCALL_CDECL_OBJLAST);

+ 11 - 4
Engine/Engine/RegisterEngine.cpp

@@ -374,14 +374,17 @@ static void registerConsole(asIScriptEngine* engine)
     engine->RegisterObjectType("Console", 0, asOBJ_REF);
     engine->RegisterObjectBehaviour("Console", asBEHAVE_ADDREF, "void f()", asMETHOD(Console, addRef), asCALL_THISCALL);
     engine->RegisterObjectBehaviour("Console", asBEHAVE_RELEASE, "void f()", asMETHOD(Console, releaseRef), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Console", "void write(const string& in)", asMETHOD(Console, write), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Console", "void setStyle(XMLFile@+ file)", asMETHOD(Console, setStyle), asCALL_THISCALL);
     engine->RegisterObjectMethod("Console", "void setVisible(bool)", asMETHOD(Console, setVisible), asCALL_THISCALL);
     engine->RegisterObjectMethod("Console", "void toggle()", asMETHOD(Console, toggle), asCALL_THISCALL);
     engine->RegisterObjectMethod("Console", "void setNumRows(uint)", asMETHOD(Console, setNumRows), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Console", "void setFont(Font@+, int)", asMETHOD(Console, setFont), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Console", "bool isVisible() const", asMETHOD(Console, isVisible), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Console", "uint getNumRows() const", asMETHOD(Console, getNumRows), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Console", "void updateElements()", asMETHOD(Console, updateElements), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Console", "XMLFile@+ getStyle() const", asMETHOD(Console, getStyle), asCALL_THISCALL);
     engine->RegisterObjectMethod("Console", "BorderImage@+ getBackground() const", asMETHOD(Console, getBackground), asCALL_THISCALL);
     engine->RegisterObjectMethod("Console", "LineEdit@+ getLineEdit() const", asMETHOD(Console, getLineEdit), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Console", "bool isVisible() const", asMETHOD(Console, isVisible), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Console", "uint getNumRows() const", asMETHOD(Console, getNumRows), asCALL_THISCALL);
     
     engine->RegisterGlobalFunction("Console@+ getConsole()", asFUNCTION(GetConsole), asCALL_CDECL);
     engine->RegisterGlobalFunction("Console@+ get_console()", asFUNCTION(GetConsole), asCALL_CDECL);
@@ -402,11 +405,15 @@ static void registerDebugHud(asIScriptEngine* engine)
     engine->RegisterObjectType("DebugHud", 0, asOBJ_REF);
     engine->RegisterObjectBehaviour("DebugHud", asBEHAVE_ADDREF, "void f()", asMETHOD(DebugHud, addRef), asCALL_THISCALL);
     engine->RegisterObjectBehaviour("DebugHud", asBEHAVE_RELEASE, "void f()", asMETHOD(DebugHud, releaseRef), asCALL_THISCALL);
-    engine->RegisterObjectMethod("DebugHud", "void setFont(Font@+, int)", asMETHOD(DebugHud, setFont), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DebugHud", "void setStyle(XMLFile@+)", asMETHOD(DebugHud, setStyle), asCALL_THISCALL);
     engine->RegisterObjectMethod("DebugHud", "void setMode(uint)", asMETHOD(DebugHud, setMode), asCALL_THISCALL);
     engine->RegisterObjectMethod("DebugHud", "void setProfilerInterval(float)", asMETHOD(DebugHud, setProfilerInterval), asCALL_THISCALL);
     engine->RegisterObjectMethod("DebugHud", "void toggle(uint)", asMETHOD(DebugHud, toggle), asCALL_THISCALL);
     engine->RegisterObjectMethod("DebugHud", "void toggleAll()", asMETHOD(DebugHud, toggleAll), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DebugHud", "XMLFile@+ getStyle() const", asMETHOD(DebugHud, getStyle), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DebugHud", "Text@+ getStatsText() const", asMETHOD(DebugHud, getStatsText), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DebugHud", "Text@+ getModeText() const", asMETHOD(DebugHud, getModeText), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DebugHud", "Text@+ getProfilerText() const", asMETHOD(DebugHud, getProfilerText), asCALL_THISCALL);
     engine->RegisterObjectMethod("DebugHud", "uint getMode() const", asMETHOD(DebugHud, getMode), asCALL_THISCALL);
     engine->RegisterObjectMethod("DebugHud", "float getProfilerInterval() const", asMETHOD(DebugHud, getProfilerInterval), asCALL_THISCALL);
     

+ 1 - 0
Engine/Engine/RegisterResource.cpp

@@ -156,6 +156,7 @@ static void registerXMLElement(asIScriptEngine* engine)
     engine->RegisterObjectMethod("XMLElement", "bool getBool(const string& in) const", asMETHOD(XMLElement, getBool), asCALL_THISCALL);
     engine->RegisterObjectMethod("XMLElement", "BoundingBox getBoundingBox() const", asMETHOD(XMLElement, getBoundingBox), asCALL_THISCALL);
     engine->RegisterObjectMethod("XMLElement", "XMLElement getChildElement(const string& in) const", asMETHOD(XMLElement, getChildElement), asCALL_THISCALL);
+    engine->RegisterObjectMethod("XMLElement", "XMLElement getParentElement() const", asMETHOD(XMLElement, getParentElement), asCALL_THISCALL);
     engine->RegisterObjectMethod("XMLElement", "Color getColor(const string& in) const", asMETHOD(XMLElement, getColor), asCALL_THISCALL);
     engine->RegisterObjectMethod("XMLElement", "float getFloat(const string& in) const", asMETHOD(XMLElement, getFloat), asCALL_THISCALL);
     engine->RegisterObjectMethod("XMLElement", "int getInt(const string& in) const", asMETHOD(XMLElement, getInt), asCALL_THISCALL);

+ 1 - 2
Engine/Engine/RegisterScript.cpp

@@ -36,8 +36,7 @@ static bool ScriptFileExecute(const std::string& declaration, CScriptArray* srcP
         return false;
     
     unsigned numParams = srcParams->GetSize();
-    std::vector<Variant> destParams;
-    destParams.resize(numParams);
+    std::vector<Variant> destParams(numParams);
     
     for (unsigned i = 0; i < numParams; ++i)
         destParams[i] = *(static_cast<Variant*>(srcParams->At(i)));

+ 2 - 0
Engine/Engine/RegisterTemplates.h

@@ -450,6 +450,8 @@ template <class T> void registerUIElement(asIScriptEngine* engine, const char* c
     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 disableLayoutUpdate()", asMETHOD(T, disableLayoutUpdate), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "void enableLayoutUpdate()", asMETHOD(T, enableLayoutUpdate), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void bringToFront()", asMETHOD(T, bringToFront), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void addChild(UIElement@+)", asMETHOD(T, addChild), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void removeChild(UIElement@+)", asMETHOD(T, removeChild), asCALL_THISCALL);

+ 48 - 0
Engine/Engine/RegisterUI.cpp

@@ -27,6 +27,7 @@
 #include "Cursor.h"
 #include "DropDownList.h"
 #include "Engine.h"
+#include "FileSelector.h"
 #include "Font.h"
 #include "LineEdit.h"
 #include "ListView.h"
@@ -329,6 +330,52 @@ static void registerWindow(asIScriptEngine* engine)
     registerRefCasts<UIElement, Window>(engine, "UIElement", "Window");
 }
 
+static FileSelector* ConstructFileSelector()
+{
+    try
+    {
+        return new FileSelector(getEngine()->getUI());
+    }
+    catch (Exception& e)
+    {
+        // Rethrow after FileSelector has been deallocated
+        SAFE_RETHROW_RET(e, 0);
+    }
+}
+
+static void FileSelectorSetFilters(CScriptArray* filters, unsigned defaultIndex, FileSelector* ptr)
+{
+    if (!filters)
+        return;
+    
+    unsigned numFilters = filters->GetSize();
+    std::vector<std::string> destFilters(numFilters);
+    
+    for (unsigned i = 0; i < numFilters; ++i)
+        destFilters[i] = *(static_cast<std::string*>(filters->At(i)));
+    
+    ptr->setFilters(destFilters, defaultIndex);
+}
+
+static void registerFileSelector(asIScriptEngine* engine)
+{
+    engine->RegisterObjectType("FileSelector", 0, asOBJ_REF);
+    engine->RegisterObjectBehaviour("FileSelector", asBEHAVE_ADDREF, "void f()", asMETHOD(FileSelector, addRef), asCALL_THISCALL);
+    engine->RegisterObjectBehaviour("FileSelector", asBEHAVE_RELEASE, "void f()", asMETHOD(FileSelector, releaseRef), asCALL_THISCALL);
+    engine->RegisterObjectBehaviour("FileSelector", asBEHAVE_FACTORY, "FileSelector@+ f()", asFUNCTION(ConstructFileSelector), asCALL_CDECL);
+    engine->RegisterObjectMethod("FileSelector", "void setStyle(XMLFile@+)", asMETHOD(FileSelector, setStyle), asCALL_THISCALL);
+    engine->RegisterObjectMethod("FileSelector", "void setTitle(const string& in)", asMETHOD(FileSelector, setTitle), asCALL_THISCALL);
+    engine->RegisterObjectMethod("FileSelector", "void setButtonTexts(const string& in, const string& in)", asMETHOD(FileSelector, setButtonTexts), asCALL_THISCALL);
+    engine->RegisterObjectMethod("FileSelector", "void setPath(const string& in)", asMETHOD(FileSelector, setPath), asCALL_THISCALL);
+    engine->RegisterObjectMethod("FileSelector", "void setFileName(const string& in)", asMETHOD(FileSelector, setFileName), asCALL_THISCALL);
+    engine->RegisterObjectMethod("FileSelector", "void setFilters(array<string>@+, uint)", asFUNCTION(FileSelectorSetFilters), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("FileSelector", "Window@+ getWindow() const", asMETHOD(FileSelector, getWindow), asCALL_THISCALL);
+    engine->RegisterObjectMethod("FileSelector", "const string& getPath() const", asMETHOD(FileSelector, getPath), asCALL_THISCALL);
+    engine->RegisterObjectMethod("FileSelector", "const string& getFileName() const", asMETHOD(FileSelector, getFileName), asCALL_THISCALL);
+    engine->RegisterObjectMethod("FileSelector", "const string& getFilter() const", asMETHOD(FileSelector, getFilter), asCALL_THISCALL);
+    registerRefCasts<EventListener, FileSelector>(engine, "EventListener", "FileSelector");
+}
+
 static UI* GetUI()
 {
     return getEngine()->getUI();
@@ -435,5 +482,6 @@ void registerUILibrary(asIScriptEngine* engine)
     registerMenu(engine);
     registerDropDownList(engine);
     registerWindow(engine);
+    registerFileSelector(engine);
     registerUI(engine);
 }

+ 1 - 1
Engine/Resource/ResourceCache.cpp

@@ -61,7 +61,7 @@ void ResourceCache::addResourcePath(const std::string& path)
     mResourcePaths.push_back(fixedPath);
     
     // Scan the path for files recursively and add their hash-to-name mappings
-    std::vector<std::string> fileNames = scanDirectory(fixedPath, "*.*", true);
+    std::vector<std::string> fileNames = scanDirectory(fixedPath, "*.*", true, false, false);
     for (unsigned i = 0; i < fileNames.size(); ++i)
         registerHash(fileNames[i]);
     

+ 8 - 0
Engine/Resource/XMLElement.cpp

@@ -268,6 +268,14 @@ XMLElement XMLElement::getNextElement(const std::string& name) const
         return XMLElement(mElement->NextSiblingElement(name.c_str()));
 }
 
+XMLElement XMLElement::getParentElement() const
+{
+    if (!mElement)
+        return XMLElement();
+    
+    return XMLElement(dynamic_cast<TiXmlElement*>(mElement->Parent()));
+}
+
 bool XMLElement::hasAttribute(const std::string& name) const
 {
     if (!mElement)

+ 2 - 0
Engine/Resource/XMLElement.h

@@ -105,6 +105,8 @@ public:
     XMLElement getChildElement(const std::string& name = std::string()) const;
     //! Return next sibling element
     XMLElement getNextElement(const std::string& name = std::string()) const;
+    //! Return parent element
+    XMLElement getParentElement() const;
     //! Return whether has an attribute
     bool hasAttribute(const std::string& name) const;
     //! Return attribute, or empty if missing

+ 438 - 0
Engine/UI/FileSelector.cpp

@@ -0,0 +1,438 @@
+//
+// 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 "Exception.h"
+#include "File.h"
+#include "FileSelector.h"
+#include "LineEdit.h"
+#include "ListView.h"
+#include "Text.h"
+#include "UI.h"
+#include "UIEvents.h"
+#include "Window.h"
+
+#include <algorithm>
+
+#include "DebugNew.h"
+
+static bool compareEntries(const FileSelectorEntry& lhs, const FileSelectorEntry& rhs)
+{
+    if ((lhs.mDirectory) && (!rhs.mDirectory))
+        return true;
+    if ((!lhs.mDirectory) && (rhs.mDirectory))
+        return false;
+    return lhs.mName < rhs.mName;
+}
+
+FileSelector::FileSelector(UI* ui) :
+    mUI(ui),
+    mIgnoreEvents(false)
+{
+    if (!mUI)
+        EXCEPTION("Null UI for FileSelector");
+    
+    mWindow = new Window();
+    mWindow->setLayout(LM_VERTICAL);
+    
+    mTitleText = new Text();
+    mWindow->addChild(mTitleText);
+    
+    mPathEdit = new LineEdit();
+    mWindow->addChild(mPathEdit);
+    
+    mFileList = new ListView();
+    mWindow->addChild(mFileList);
+    
+    mFileNameLayout = new UIElement();
+    mFileNameLayout->setLayout(LM_HORIZONTAL);
+    
+    mFileNameEdit = new LineEdit();
+    mFileNameLayout->addChild(mFileNameEdit);
+    
+    mFilterList = new DropDownList();
+    mFileNameLayout->addChild(mFilterList);
+    
+    mWindow->addChild(mFileNameLayout);
+    
+    mButtonLayout = new UIElement();
+    mButtonLayout->setLayout(LM_HORIZONTAL);
+    
+    mButtonLayout->addChild(new UIElement()); // Add spacer
+    
+    mOKButton = new Button();
+    mOKButtonText = new Text();
+    mOKButtonText->setAlignment(HA_CENTER, VA_CENTER);
+    mOKButton->addChild(mOKButtonText);
+    mButtonLayout->addChild(mOKButton);
+    
+    mButtonLayout->addChild(new UIElement()); // Add spacer
+    
+    mCancelButton = new Button();
+    mCancelButtonText = new Text();
+    mCancelButtonText->setAlignment(HA_CENTER, VA_CENTER);
+    mCancelButton->addChild(mCancelButtonText);
+    mButtonLayout->addChild(mCancelButton);
+    
+    mButtonLayout->addChild(new UIElement()); // Add spacer
+    
+    mWindow->addChild(mButtonLayout);
+    
+    mUI->getRootElement()->addChild(mWindow);
+    mWindow->bringToFront();
+    
+    std::vector<std::string> defaultFilters;
+    defaultFilters.push_back("*.*");
+    setFilters(defaultFilters, 0);
+    setPath(getWorkingDirectory());
+    
+    subscribeToEvent(mFilterList, EVENT_ITEMSELECTED, EVENT_HANDLER(FileSelector, handleFilterChanged));
+    subscribeToEvent(mPathEdit, EVENT_TEXTFINISHED, EVENT_HANDLER(FileSelector, handlePathChanged));
+    subscribeToEvent(mFileList, EVENT_ITEMSELECTED, EVENT_HANDLER(FileSelector, handleFileSelected));
+    subscribeToEvent(mFileList, EVENT_ITEMDOUBLECLICKED, EVENT_HANDLER(FileSelector, handleFileDoubleClicked));
+    subscribeToEvent(mOKButton, EVENT_PRESSED, EVENT_HANDLER(FileSelector, handleOKPressed));
+    subscribeToEvent(mCancelButton, EVENT_PRESSED, EVENT_HANDLER(FileSelector, handleCancelPressed));
+}
+
+FileSelector::~FileSelector()
+{
+    mUI->getRootElement()->removeChild(mWindow);
+}
+
+void FileSelector::setStyle(XMLFile* style)
+{
+    if (!style)
+        return;
+    
+    mStyle = style;
+    ResourceCache* cache = mUI->getResourceCache();
+    
+    XMLElement windowElem = UIElement::getStyleElement(style, "Window");
+    if (windowElem)
+        mWindow->setStyle(windowElem, cache);
+    windowElem = UIElement::getStyleElement(style, "FileSelector");
+    if (windowElem)
+        mWindow->setStyle(windowElem, cache);
+    XMLElement titleElem = UIElement::getStyleElement(style, "FileSelectorTitleText");
+    if (titleElem)
+        mTitleText->setStyle(titleElem, cache);
+    XMLElement textElem = UIElement::getStyleElement(style, "FileSelectorButtonText");
+    if (textElem)
+    {
+        mOKButtonText->setStyle(textElem, cache);
+        mCancelButtonText->setStyle(textElem, cache);
+    }
+    XMLElement layoutElem = UIElement::getStyleElement(style, "FileSelectorLayout");
+    if (layoutElem)
+    {
+        mFileNameLayout->setStyle(layoutElem, cache);
+        mButtonLayout->setStyle(layoutElem, cache);
+    }
+    XMLElement listViewElem = UIElement::getStyleElement(style, "ListView");
+    if (listViewElem)
+        mFileList->setStyle(listViewElem, cache);
+    XMLElement lineEditElem = UIElement::getStyleElement(style, "LineEdit");
+    if (lineEditElem)
+    {
+        mFileNameEdit->setStyle(lineEditElem, cache);
+        mPathEdit->setStyle(lineEditElem, cache);
+    }
+    XMLElement dropDownElem = UIElement::getStyleElement(style, "DropDownList");
+    if (dropDownElem)
+        mFilterList->setStyle(dropDownElem, cache);
+    dropDownElem = UIElement::getStyleElement(style, "FileSelectorFilterList");
+    if (dropDownElem)
+        mFilterList->setStyle(dropDownElem, cache);
+    XMLElement buttonElem = UIElement::getStyleElement(style, "Button");
+    if (buttonElem)
+    {
+        mOKButton->setStyle(buttonElem, cache);
+        mCancelButton->setStyle(buttonElem, cache);
+    }
+    buttonElem = UIElement::getStyleElement(style, "FileSelectorButton");
+    if (buttonElem)
+    {
+        mOKButton->setStyle(buttonElem, cache);
+        mCancelButton->setStyle(buttonElem, cache);
+    }
+    textElem = UIElement::getStyleElement(style, "FileSelectorFilterText");
+    if (textElem)
+    {
+        std::vector<UIElement*> listTexts = mFilterList->getListView()->getContentElement()->getChildren();
+        for (unsigned i = 0; i < listTexts.size(); ++i)
+            listTexts[i]->setStyle(textElem, cache);
+    }
+    textElem = UIElement::getStyleElement(style, "FileSelectorListText");
+    if (textElem)
+    {
+        std::vector<UIElement*> listTexts = mFileList->getContentElement()->getChildren();
+        for (unsigned i = 0; i < listTexts.size(); ++i)
+            listTexts[i]->setStyle(textElem, cache);
+    }
+    
+    updateElements();
+}
+
+void FileSelector::setTitle(const std::string& text)
+{
+    mTitleText->setText(text);
+}
+
+void FileSelector::setButtonTexts(const std::string& okText, const std::string& cancelText)
+{
+    mOKButtonText->setText(okText);
+    mCancelButtonText->setText(cancelText);
+}
+
+void FileSelector::setPath(const std::string& path)
+{
+    if (directoryExists(path))
+    {
+        mPath = fixPath(path);
+        mIgnoreEvents = true;
+        mPathEdit->setText(mPath);
+        mIgnoreEvents = false;
+        refreshFiles();
+    }
+    else
+    {
+        // If path was invalid, restore the old path to the line edit
+        if (mPathEdit->getText() != mPath)
+        {
+            mIgnoreEvents = true;
+            mPathEdit->setText(mPath);
+            mIgnoreEvents = false;
+        }
+    }
+}
+
+void FileSelector::setFileName(const std::string& fileName)
+{
+    mIgnoreEvents = true;
+    mFileNameEdit->setText(fileName);
+    mIgnoreEvents = false;
+}
+
+void FileSelector::setFilters(const std::vector<std::string>& filters, unsigned defaultIndex)
+{
+    if (filters.empty())
+        return;
+    
+    mIgnoreEvents = true;
+    mFilters = filters;
+    mFilterList->removeAllItems();
+    for (unsigned i = 0; i < mFilters.size(); ++i)
+    {
+        Text* filterText = new Text();
+        filterText->setText(mFilters[i]);
+        XMLElement textElem = UIElement::getStyleElement(mStyle, "FileSelectorFilterText");
+        if (textElem)
+            filterText->setStyle(textElem, mUI->getResourceCache());
+        mFilterList->addItem(filterText);
+    }
+    mFilterList->setSelection(defaultIndex);
+    mIgnoreEvents = false;
+    if (getFilter() != mLastUsedFilter)
+        refreshFiles();
+}
+
+
+void FileSelector::updateElements()
+{
+    {
+        const IntRect& clipBorder = mPathEdit->getClipBorder();
+        mPathEdit->setFixedHeight(mPathEdit->getTextElement()->getRowHeight() + clipBorder.mTop + clipBorder.mBottom);
+    }
+    
+    {
+        const IntRect& clipBorder = mFileNameEdit->getClipBorder();
+        int fileNameHeight = mFileNameEdit->getTextElement()->getRowHeight() + clipBorder.mTop + clipBorder.mBottom;
+        mFileNameEdit->setFixedHeight(fileNameHeight);
+        mFilterList->setFixedHeight(fileNameHeight);
+        mFileNameLayout->setFixedHeight(fileNameHeight);
+    }
+    
+    mButtonLayout->setFixedHeight(max(mOKButton->getHeight(), mCancelButton->getHeight()));
+}
+
+const std::string& FileSelector::getFileName() const
+{
+    return mFileNameEdit->getText();
+}
+
+const std::string& FileSelector::getFilter() const
+{
+    static std::string emptyFilter;
+    
+    Text* selectedFilter = static_cast<Text*>(mFilterList->getSelectedItem());
+    if (selectedFilter)
+        return selectedFilter->getText();
+    
+    return emptyFilter;
+}
+
+void FileSelector::refreshFiles()
+{
+    mIgnoreEvents = true;
+    
+    mFileList->removeAllItems();
+    mFileEntries.clear();
+    
+    try
+    {
+        std::vector<std::string> directories = scanDirectory(mPath, "*.*", false, true, false);
+        std::vector<std::string> files = scanDirectory(mPath, getFilter(), false, false, false);
+        
+        for (unsigned i = 0; i < directories.size(); ++i)
+        {
+            FileSelectorEntry newEntry;
+            newEntry.mName = directories[i];
+            newEntry.mDirectory = true;
+            mFileEntries.push_back(newEntry);
+        }
+
+        for (unsigned i = 0; i < files.size(); ++i)
+        {
+            FileSelectorEntry newEntry;
+            newEntry.mName = files[i];
+            newEntry.mDirectory = false;
+            mFileEntries.push_back(newEntry);
+        }
+    }
+    catch (...)
+    {
+    }
+    
+    // Sort and add to the list view
+    std::sort(mFileEntries.begin(), mFileEntries.end(), compareEntries);
+    UIElement* listContent = mFileList->getContentElement();
+    listContent->disableLayoutUpdate();
+    for (unsigned i = 0; i < mFileEntries.size(); ++i)
+    {
+        std::string displayName;
+        if (mFileEntries[i].mDirectory)
+            displayName = "<DIR> " + mFileEntries[i].mName;
+        else
+            displayName = mFileEntries[i].mName;
+        
+        Text* entryText = new Text();
+        entryText->setText(displayName);
+        XMLElement textElem = UIElement::getStyleElement(mStyle, "FileSelectorListText");
+        if (textElem)
+            entryText->setStyle(textElem, mUI->getResourceCache());
+        mFileList->addItem(entryText);
+    }
+    listContent->enableLayoutUpdate();
+    listContent->updateLayout();
+    
+    mIgnoreEvents = false;
+    
+    // Clear filename from the previous dir
+    setFileName(std::string());
+    mFileList->setSelection(0);
+}
+
+void FileSelector::handleFilterChanged(StringHash eventType, VariantMap& eventData)
+{
+    if (mIgnoreEvents)
+        return;
+    if (getFilter() != mLastUsedFilter)
+        refreshFiles();
+}
+
+void FileSelector::handlePathChanged(StringHash eventType, VariantMap& eventData)
+{
+    if (mIgnoreEvents)
+        return;
+    // Attempt to set path. Restores old if does not exist
+    setPath(mPathEdit->getText());
+}
+
+void FileSelector::handleFileSelected(StringHash eventType, VariantMap& eventData)
+{
+    if (mIgnoreEvents)
+        return;
+    unsigned index = mFileList->getSelection();
+    if (index >= mFileEntries.size())
+        return;
+    // If a file selected, update the filename edit field
+    if (!mFileEntries[index].mDirectory)
+        setFileName(mFileEntries[index].mName);
+}
+
+void FileSelector::handleFileDoubleClicked(StringHash eventType, VariantMap& eventData)
+{
+    if (mIgnoreEvents)
+        return;
+    unsigned index = mFileList->getSelection();
+    if (index >= mFileEntries.size())
+        return;
+    // If a directory doubleclicked, enter it
+    if (mFileEntries[index].mDirectory)
+    {
+        // Recognize . and .. as a special case
+        const std::string& newPath = mFileEntries[index].mName;
+        if ((newPath != ".") &&  (newPath != ".."))
+            setPath(mPath + newPath);
+        else if (newPath == "..")
+        {
+            unsigned pos = unfixPath(mPath).rfind('/');
+            if (pos != std::string::npos)
+                setPath(mPath.substr(0, pos));
+        }
+    }
+    else
+    {
+        using namespace FileSelected;
+        
+        VariantMap eventData;
+        eventData[P_FILENAME] = mPath + mFileEntries[index].mName;
+        eventData[P_OK] = true;
+        sendEvent(EVENT_FILESELECTED, eventData);
+    }
+}
+
+void FileSelector::handleOKPressed(StringHash eventType, VariantMap& eventData)
+{
+    const std::string& fileName = getFileName();
+    if (!fileName.empty())
+    {
+        using namespace FileSelected;
+        
+        VariantMap newEventData;
+        newEventData[P_FILENAME] = mPath + getFileName();
+        newEventData[P_OK] = true;
+        sendEvent(EVENT_FILESELECTED, newEventData);
+    }
+}
+
+void FileSelector::handleCancelPressed(StringHash eventType, VariantMap& eventData)
+{
+    using namespace FileSelected;
+    
+    VariantMap newEventData;
+    newEventData[P_FILENAME] = std::string();
+    newEventData[P_OK] = false;
+    sendEvent(EVENT_FILESELECTED, newEventData);
+}

+ 159 - 0
Engine/UI/FileSelector.h

@@ -0,0 +1,159 @@
+//
+// 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 "EventListener.h"
+#include "SharedPtr.h"
+
+class Button;
+class DropDownList;
+class Font;
+class LineEdit;
+class ListView;
+class ResourceCache;
+class Text;
+class UI;
+class UIElement;
+class Window;
+class XMLFile;
+
+//! File selector entry
+struct FileSelectorEntry
+{
+    //! Name
+    std::string mName;
+    //! Directory flag
+    bool mDirectory;
+};
+
+//! File selector dialog
+class FileSelector : public RefCounted, public EventListener
+{
+public:
+    //! Construct
+    FileSelector(UI* ui);
+    //! Destruct
+    virtual ~FileSelector();
+    
+    //! Set fileselector UI style
+    void setStyle(XMLFile* style);
+    //! Set title text
+    void setTitle(const std::string& text);
+    //! Set button texts
+    void setButtonTexts(const std::string& okText, const std::string& cancelText);
+    //! Set current path
+    void setPath(const std::string& path);
+    //! Set current filename
+    void setFileName(const std::string& fileName);
+    //! Set filters
+    void setFilters(const std::vector<std::string>& filters, unsigned defaultIndex);
+    //! Update elements to layout properly. Call this after manually adjusting the sub-elements
+    void updateElements();
+    
+    //! Return the UI style file
+    XMLFile* getStyle() const { return mStyle; }
+    //! Return fileselector window
+    Window* getWindow() const { return mWindow; }
+    //! Return window title text
+    Text* getTitleText() const { return mTitleText; }
+    //! Return file list
+    ListView* getFileList() const { return mFileList; }
+    //! Return path editor
+    LineEdit* getPathEdit() const { return mPathEdit; }
+    //! Return filename editor
+    LineEdit* getFileNameEdit() const { return mFileNameEdit; }
+    //! Return filter dropdown
+    DropDownList* getFilterList() const { return mFilterList; }
+    //! Return OK button
+    Button* getOKButton() const { return mOKButton; }
+    //! Return OK button text
+    Text* getOKButtonText() const { return mOKButtonText; }
+    //! Return cancel button
+    Button* getCancelButton() const { return mCancelButton; }
+    //! Return cancel button text
+    Text* getCancelButtonText() const { return mCancelButtonText; }
+    //! Return filename & filter layout
+    UIElement* getFileNameLayout() const { return mFileNameLayout; }
+    //! Return button layout
+    UIElement* getButtonLayout() const { return mButtonLayout; }
+    //! Return current path
+    const std::string& getPath() const { return mPath; }
+    //! Return current filename
+    const std::string& getFileName() const;
+    //! Return current filter
+    const std::string& getFilter() const;
+    
+private:
+    //! Refresh the directory listing
+    void refreshFiles();
+    //! Handle filter changed
+    void handleFilterChanged(StringHash eventType, VariantMap& eventData);
+    //! Handle path edited
+    void handlePathChanged(StringHash eventType, VariantMap& eventData);
+    //! Handle file selected from the list
+    void handleFileSelected(StringHash eventType, VariantMap& eventData);
+    //! Handle file doubleclicked from the list (enter directory / OK the file selection)
+    void handleFileDoubleClicked(StringHash eventType, VariantMap& eventData);
+    //! Handle OK button pressed
+    void handleOKPressed(StringHash eventType, VariantMap& eventData);
+    //! Handle cancel button pressed
+    void handleCancelPressed(StringHash eventType, VariantMap& eventData);
+    
+    //! UI subsystem
+    SharedPtr<UI> mUI;
+    //! UI style file
+    SharedPtr<XMLFile> mStyle;
+    //! Fileselector window
+    SharedPtr<Window> mWindow;
+    //! Window title text
+    SharedPtr<Text> mTitleText;
+    //! File list
+    SharedPtr<ListView> mFileList;
+    //! Path editor
+    SharedPtr<LineEdit> mPathEdit;
+    //! Filename editor
+    SharedPtr<LineEdit> mFileNameEdit;
+    //! Filter dropdown
+    SharedPtr<DropDownList> mFilterList;
+    //! OK button
+    SharedPtr<Button> mOKButton;
+    //! OK button text
+    SharedPtr<Text> mOKButtonText;
+    //! Cancel button
+    SharedPtr<Button> mCancelButton;
+    //! Cancel button text
+    SharedPtr<Text> mCancelButtonText;
+    //! Filename and filter layout
+    SharedPtr<UIElement> mFileNameLayout;
+    //! Button layout
+    SharedPtr<UIElement> mButtonLayout;
+    //! Current path
+    std::string mPath;
+    //! Filters
+    std::vector<std::string> mFilters;
+    //! File entries
+    std::vector<FileSelectorEntry> mFileEntries;
+    //! Filter used to get the file list
+    std::string mLastUsedFilter;
+    //! Ignore event flag (used when changing the edit fields programmatically)
+    bool mIgnoreEvents;
+};

+ 0 - 4
Engine/UI/ListView.cpp

@@ -295,10 +295,6 @@ void ListView::ensureItemVisibility()
     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)

+ 2 - 2
Engine/UI/ScrollBar.cpp

@@ -124,7 +124,7 @@ void ScrollBar::setStyle(const XMLElement& element, ResourceCache* cache)
 void ScrollBar::onResize()
 {
     // Disable layout operations while setting the button sizes is incomplete
-    mUpdateLayoutNestingLevel++;
+    disableLayoutUpdate();
     
     if (mSlider->getOrientation() == O_HORIZONTAL)
     {
@@ -139,7 +139,7 @@ void ScrollBar::onResize()
         mForwardButton->setFixedSize(width, width);
     }
     
-    mUpdateLayoutNestingLevel--;
+    enableLayoutUpdate();
 }
 
 void ScrollBar::setOrientation(Orientation orientation)

+ 3 - 5
Engine/UI/Text.cpp

@@ -431,11 +431,9 @@ void Text::updateText(bool inResize)
         mCharPositions[mText.length()] = IntVector2(x, y);
     }
     
-    // Set minimum size to correspond to the text size
-    if (!mWordwrap)
-        setMinSize(width, height);
-    else
-        setMinHeight(height);
+    // Set minimum size according to the text size
+    setMinWidth(width);
+    setFixedHeight(height);
 }
 
 void Text::validateSelection()

+ 8 - 5
Engine/UI/UI.cpp

@@ -500,21 +500,24 @@ void UI::handleMouseButtonDown(StringHash eventType, VariantMap& eventData)
     if ((mCursor) && (mCursor->isVisible()))
     {
         IntVector2 pos = mCursor->getPosition();
-        UIElement* element = getElementAt(pos);
+        WeakPtr<UIElement> element(getElementAt(pos));
         if (element)
         {
             // Handle focusing & bringing to front
             if (button == MOUSEB_LEFT)
             {
                 setFocusElement(element);
-                element->bringToFront();
+                // Must check the pointer after each operation, because any UI event may trigger its destruction
+                if (element)
+                    element->bringToFront();
             }
             
             // Handle click
-            element->onClick(element->screenToElement(pos), pos, mMouseButtons, mQualifiers);
+            if (element)
+                element->onClick(element->screenToElement(pos), pos, mMouseButtons, mQualifiers);
             
             // Handle start of drag
-            if (!mMouseDrag)
+            if ((element) && (!mMouseDrag))
             {
                 mMouseDrag = true;
                 mMouseDragElement = element;
@@ -523,7 +526,7 @@ void UI::handleMouseButtonDown(StringHash eventType, VariantMap& eventData)
         }
         else
         {
-            // If clicked over no element, or a disabled element, lose focus
+            // If clicked over no element, or a disabled element, try to lose focus
             setFocusElement(0);
         }
     }

+ 2 - 2
Engine/UI/UI.h

@@ -48,8 +48,6 @@ public:
     void setCursor(Cursor* cursor);
     //! Set focused UI element
     void setFocusElement(UIElement* element);
-    //! Bring an UI element to front
-    void bringToFront(UIElement* element);
     //! Clear the UI (excluding the cursor)
     void clear();
     //! Update the UI
@@ -63,6 +61,8 @@ public:
     //! Load a UI layout from an XML file. Optionally specify another XML file for element style. Return the root element
     SharedPtr<UIElement> loadLayout(XMLFile* file, XMLFile* styleFile = 0);
     
+    //! Return resource cache
+    ResourceCache* getResourceCache() const { return mCache; }
     //! Return root UI elemenet
     UIElement* getRootElement() const { return mRootElement; }
     //! Return cursor

+ 23 - 11
Engine/UI/UIElement.cpp

@@ -363,7 +363,7 @@ void UIElement::setSize(const IntVector2& size)
         
         if (mResizeNestingLevel == 1)
         {
-            // Check if parent element's layout needs to be updated
+            // Check if parent element's layout needs to be updated first
             if (mParent)
                 mParent->updateLayout();
             
@@ -638,7 +638,8 @@ void UIElement::updateLayout()
     if ((mLayoutMode == LM_FREE) || (mUpdateLayoutNestingLevel))
         return;
     
-    ++mUpdateLayoutNestingLevel;
+    // Prevent further updates while this update happens
+    disableLayoutUpdate();
     
     std::vector<int> positions;
     std::vector<int> sizes;
@@ -720,6 +721,16 @@ void UIElement::updateLayout()
         }
     }
     
+    enableLayoutUpdate();
+}
+
+void UIElement::disableLayoutUpdate()
+{
+    ++mUpdateLayoutNestingLevel;
+}
+
+void UIElement::enableLayoutUpdate()
+{
     --mUpdateLayoutNestingLevel;
 }
 
@@ -1043,7 +1054,7 @@ 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 targetSize, int begin, int end, int spacing)
 {
-    unsigned numChildren = sizes.size();
+    int numChildren = sizes.size();
     if (!numChildren)
         return;
     int targetTotalSize = targetSize - begin - end - (numChildren - 1) * spacing;
@@ -1055,7 +1066,7 @@ void UIElement::calculateLayout(std::vector<int>& positions, std::vector<int>& s
     float acc = 0.0f;
     
     // Initial pass
-    for (unsigned i = 0; i < numChildren; ++i)
+    for (int i = 0; i < numChildren; ++i)
     {
         int targetSize = targetChildSize;
         if (remainder)
@@ -1075,7 +1086,7 @@ void UIElement::calculateLayout(std::vector<int>& positions, std::vector<int>& s
     for (;;)
     {
         int actualTotalSize = 0;
-        for (unsigned i = 0; i < numChildren; ++i)
+        for (int i = 0; i < numChildren; ++i)
             actualTotalSize += sizes[i];
         int error = targetTotalSize - actualTotalSize;
         // Break if no error
@@ -1085,7 +1096,7 @@ void UIElement::calculateLayout(std::vector<int>& positions, std::vector<int>& s
         // Check which of the children can be resized to correct the error. If none, must break
         static std::vector<unsigned> resizable;
         resizable.clear();
-        for (unsigned i = 0; i < numChildren; ++i)
+        for (int i = 0; i < numChildren; ++i)
         {
             if ((error < 0) && (sizes[i] > minSizes[i]))
                 resizable.push_back(i);
@@ -1095,12 +1106,13 @@ void UIElement::calculateLayout(std::vector<int>& positions, std::vector<int>& s
         if (resizable.empty())
             break;
         
-        int errorPerChild = error / resizable.size();
-        remainder = (abs(error)) % resizable.size();
-        add = (float)remainder / resizable.size();
+        int numResizable = resizable.size();
+        int errorPerChild = error / numResizable;
+        remainder = (abs(error)) % numResizable;
+        add = (float)remainder / numResizable;
         acc = 0.0f;
         
-        for (unsigned i = 0; i < resizable.size(); ++i)
+        for (int i = 0; i < numResizable; ++i)
         {
             unsigned idx = resizable[i];
             int targetSize = sizes[idx] + errorPerChild;
@@ -1121,7 +1133,7 @@ void UIElement::calculateLayout(std::vector<int>& positions, std::vector<int>& s
     
     // Calculate final positions
     int position = begin;
-    for (unsigned i = 0; i < numChildren; ++i)
+    for (int i = 0; i < numChildren; ++i)
     {
         positions[i] = position;
         position += sizes[i];

+ 4 - 0
Engine/UI/UIElement.h

@@ -219,6 +219,10 @@ public:
     void setLayoutBorder(const IntRect& border);
     //! Manually update layout. Should not be necessary in most cases, but is provided for completeness
     void updateLayout();
+    //! Disable layout update momentarily, usually for performance reasons when adding several child elements
+    void disableLayoutUpdate();
+    //! Enable layout update
+    void enableLayoutUpdate();
     //! Bring UI element to front
     void bringToFront();
     //! Add a child element

+ 7 - 0
Engine/UI/UIEvents.h

@@ -128,4 +128,11 @@ DEFINE_EVENT(EVENT_ITEMDOUBLECLICKED, ItemDoubleClicked)
     EVENT_PARAM(P_SELECTION, Selection);        // int
 }
 
+//! Fileselector choice
+DEFINE_EVENT(EVENT_FILESELECTED, FileSelected)
+{
+    EVENT_PARAM(P_FILENAME, FileName);          // string
+    EVENT_PARAM(P_OK, Ok);                      // bool
+}
+
 #endif // UI_UIEVENTS_H

+ 1 - 1
Examples/NinjaSnowWar/Game.cpp

@@ -200,7 +200,7 @@ void Game::init()
     mCache->addResourcePath(getSystemFontDirectory());
     
     DebugHud* debugHud = mEngine->createDebugHud();
-    debugHud->setFont(mCache->getResource<Font>("cour.ttf"), 12);
+    debugHud->setStyle(mCache->getResource<XMLFile>("UI/DefaultStyle.xml"));
     
     if (runServer)
     {

+ 1 - 1
Tools/PackageTool/PackageTool.cpp

@@ -86,7 +86,7 @@ void run(const std::vector<std::string>& arguments)
         gBasePath = fixPath(arguments[2]);
     
    // Get the file list recursively
-    std::vector<std::string> fileNames = scanDirectory(dirName, "*.*", true);
+    std::vector<std::string> fileNames = scanDirectory(dirName, "*.*", true, false, false);
     if (!fileNames.size())
         errorExit("No files found");
     for (unsigned i = 0; i < fileNames.size(); ++i)