ソースを参照

Initial skeleton of the scene editor.
Font scaling/positioning fixes.
API improvements.

Lasse Öörni 15 年 前
コミット
ec327701e5
46 ファイル変更585 行追加239 行削除
  1. 47 0
      Bin/CoreData/Scripts/Editor.as
  2. 55 0
      Bin/CoreData/Scripts/EditorCamera.as
  3. 47 0
      Bin/CoreData/Scripts/EditorScene.as
  4. 63 0
      Bin/CoreData/Scripts/EditorUI.as
  5. 10 3
      Bin/CoreData/UI/DefaultStyle.xml
  6. 4 4
      Bin/Data/Scripts/GraphicsTest.as
  7. 1 23
      Bin/Data/UI/TestLayout.xml
  8. 2 2
      Bin/Data/UI/TestLayout2.xml
  9. 2 9
      Bin/Data/UI/TestLayout3.xml
  10. 1 0
      Bin/Editor.bat
  11. 51 11
      Engine/Common/File.cpp
  12. 10 6
      Engine/Common/File.h
  13. 9 16
      Engine/Engine/Console.cpp
  14. 3 7
      Engine/Engine/DebugHud.cpp
  15. 4 2
      Engine/Engine/RegisterCommon.cpp
  16. 2 1
      Engine/Engine/RegisterResource.cpp
  17. 7 1
      Engine/Engine/RegisterTemplates.h
  18. 0 3
      Engine/Engine/RegisterUI.cpp
  19. 31 11
      Engine/Input/Input.cpp
  20. 4 0
      Engine/Input/Input.h
  21. 83 15
      Engine/Resource/ResourceCache.cpp
  22. 3 1
      Engine/Resource/ResourceCache.h
  23. 1 0
      Engine/Script/RegisterArray.cpp
  24. 3 1
      Engine/Script/RegisterStdString.cpp
  25. 3 0
      Engine/UI/BorderImage.cpp
  26. 2 0
      Engine/UI/BorderImage.h
  27. 2 0
      Engine/UI/Button.h
  28. 2 0
      Engine/UI/CheckBox.h
  29. 3 0
      Engine/UI/Cursor.cpp
  30. 2 0
      Engine/UI/Cursor.h
  31. 2 0
      Engine/UI/DropDownList.h
  32. 19 50
      Engine/UI/FileSelector.cpp
  33. 25 6
      Engine/UI/Font.cpp
  34. 2 0
      Engine/UI/LineEdit.h
  35. 2 0
      Engine/UI/ListView.h
  36. 3 0
      Engine/UI/Menu.h
  37. 2 0
      Engine/UI/ScrollBar.h
  38. 2 0
      Engine/UI/ScrollView.h
  39. 2 0
      Engine/UI/Slider.h
  40. 25 7
      Engine/UI/Text.cpp
  41. 2 0
      Engine/UI/Text.h
  42. 6 2
      Engine/UI/UI.cpp
  43. 19 39
      Engine/UI/UIElement.cpp
  44. 2 5
      Engine/UI/UIElement.h
  45. 2 0
      Engine/UI/Window.h
  46. 13 14
      Examples/Urho3D/Application.cpp

+ 47 - 0
Bin/CoreData/Scripts/Editor.as

@@ -0,0 +1,47 @@
+// Urho3D editor
+
+#include "Scripts/EditorCamera.as"
+#include "Scripts/EditorScene.as"
+#include "Scripts/EditorUI.as"
+
+void start()
+{
+    if (engine.isHeadless())
+    {
+        errorDialog("Urho3D Editor", "Headless mode is not supported. The program will now exit.");
+        engine.exit();
+        return;
+    }
+    
+    // Free the mouse cursor
+    input.setClipCursor(false);
+
+    subscribeToEvent("Update", "handleUpdate");
+
+    createScene();
+    createCamera();
+    createUI();
+    parseArguments();
+}
+
+void parseArguments()
+{
+    array<string> arguments = getArguments();
+
+    // The first argument should be the editor script name. Scan for a scene to load
+    for (uint i = 1; i < arguments.size(); ++i)
+    {
+        if (arguments[i][0] != '-')
+        {
+            loadScene(getAbsoluteFileName(arguments[i]));
+            break;
+        }
+    }
+}
+
+void handleUpdate(StringHash eventType, VariantMap& eventData)
+{
+    float timeStep = eventData["TimeStep"].getFloat();
+
+    moveCamera(timeStep);
+}

+ 55 - 0
Bin/CoreData/Scripts/EditorCamera.as

@@ -0,0 +1,55 @@
+// Urho3D editor camera functions
+
+Camera@ camera;
+
+float cameraBaseSpeed = 10.0;
+float cameraBaseRotationSpeed = 0.2;
+float cameraShiftSpeedMultiplier = 5.0;
+float cameraCtrlSpeedMultiplier = 0.1;
+float cameraYaw = 0.0;
+float cameraPitch = 0.0;
+
+void createCamera()
+{
+    // Note: this camera will not be bound into a scene entity, so that it does not get listed in the editor UI
+    @camera = editorScene.createComponent("Camera");
+
+    pipeline.setViewport(0, Viewport(editorScene, camera));
+}
+
+void moveCamera(float timeStep)
+{
+    if (ui.getFocusElement() is null)
+    {
+        float speedMultiplier = 1.0f;
+        if (input.getKeyDown(KEY_SHIFT))
+            speedMultiplier = cameraShiftSpeedMultiplier;
+        if (input.getKeyDown(KEY_CTRL))
+            speedMultiplier = cameraCtrlSpeedMultiplier;
+
+        if ((input.getKeyDown('W')) || (input.getKeyDown(KEY_UP)))
+            camera.translateRelative(Vector3(0, 0, cameraBaseSpeed) * timeStep * speedMultiplier);
+        if ((input.getKeyDown('S')) || (input.getKeyDown(KEY_DOWN)))
+            camera.translateRelative(Vector3(0, 0, -cameraBaseSpeed) * timeStep * speedMultiplier);
+        if ((input.getKeyDown('A')) || (input.getKeyDown(KEY_LEFT)))
+            camera.translateRelative(Vector3(-cameraBaseSpeed, 0, 0) * timeStep * speedMultiplier);
+        if ((input.getKeyDown('D')) || (input.getKeyDown(KEY_RIGHT)))
+            camera.translateRelative(Vector3(cameraBaseSpeed, 0, 0) * timeStep * speedMultiplier);
+    }
+    
+    if (input.getMouseButtonDown(MOUSEB_RIGHT))
+    {
+        IntVector2 mouseMove = input.getMouseMove();
+        if ((mouseMove.x != 0) || (mouseMove.y != 0))
+        {
+            cameraYaw += mouseMove.x * cameraBaseRotationSpeed;
+            cameraPitch += mouseMove.y * cameraBaseRotationSpeed;
+            if (cameraPitch < -90.0f)
+                cameraPitch = -90.0f;
+            if (cameraPitch > 90.0f)
+                cameraPitch = 90.0f;
+
+            camera.setRotation(Quaternion(cameraPitch, cameraYaw, 0));
+        }
+    }
+}

+ 47 - 0
Bin/CoreData/Scripts/EditorScene.as

@@ -0,0 +1,47 @@
+// Urho3D editor scene handling
+
+Scene@ editorScene;
+string sceneResourcePath;
+
+void createScene()
+{
+    // Create a scene with default values, these will be overridden when loading scenes
+    @editorScene = engine.createScene("GraphicsTest", BoundingBox(-1000.0, 1000.0), 8, true);
+}
+
+void setResourcePath(string newPath)
+{
+    if (newPath == sceneResourcePath)
+        return;
+
+    cache.releaseAllResources(false);
+
+    // Remove the old scene resource path if any. However make sure that CoreData path never gets removed
+    if ((!sceneResourcePath.empty()) && (sceneResourcePath.find("CoreData") < 0))
+        cache.removeResourcePath(sceneResourcePath);
+
+    cache.addResourcePath(newPath);
+    sceneResourcePath = newPath;
+}
+
+void loadScene(string fileName)
+{
+    // Always load the scene from the filesystem, not from resource paths
+    if (!fileExists(fileName))
+    {
+        logError("No such scene " + fileName);
+        return;
+    }
+
+    // Clear the old scene
+    editorScene.removeAllEntities();
+
+    // Add the new resource path
+    setResourcePath(getPath(fileName));
+
+    File file(fileName, FILE_READ);
+    if (getExtension(fileName) == ".xml")
+        editorScene.loadXML(file);
+    else
+        editorScene.load(file);
+}

+ 63 - 0
Bin/CoreData/Scripts/EditorUI.as

@@ -0,0 +1,63 @@
+// Urho3D editor user interface
+
+const int uiSpacing = 2;
+const IntRect uiSpacingRect(uiSpacing, uiSpacing, uiSpacing, uiSpacing);
+UIElement@ uiMenuBar;
+
+void createUI()
+{
+    XMLFile@ uiStyle = cache.getResource("XMLFile", "UI/DefaultStyle.xml");
+
+    createCursor(uiStyle);
+    createMenuBar(uiStyle);
+    
+    subscribeToEvent("ScreenMode", "handleScreenMode");
+}
+
+void createCursor(XMLFile@ uiStyle)
+{
+    Cursor@ cursor = Cursor("Cursor");
+    cursor.setStyleAuto(uiStyle);
+    cursor.setPosition(input.getMousePosition());
+    ui.setCursor(cursor);
+}
+
+void createMenuBar(XMLFile@ uiStyle)
+{
+    @uiMenuBar = BorderImage("MenuBar");
+    uiMenuBar.setStyle(uiStyle, "EditorMenuBar");
+    uiMenuBar.setLayout(LM_HORIZONTAL, uiSpacing, uiSpacingRect);
+    uiRoot.addChild(uiMenuBar);
+
+    Menu@ fileMenu = createMenu(uiStyle, "File");
+    uiMenuBar.addChild(fileMenu);
+    UIElement@ spacer = UIElement("MenuBarSpacer");
+    uiMenuBar.addChild(spacer);
+
+    resizeUI();
+}
+
+Menu@ createMenu(XMLFile@ uiStyle, string title)
+{
+    Menu@ menu = Menu(title);
+    menu.setStyleAuto(uiStyle);
+    menu.setLayout(LM_HORIZONTAL, 0, IntRect(uiSpacing, 0, uiSpacing, 0));
+
+    Text@ menuText = Text(title + "Text");
+    menuText.setStyle(uiStyle, "EditorMenuText");
+    menuText.setText(title);
+    menu.addChild(menuText);
+    menu.setFixedWidth(menu.getWidth());
+
+    return menu;
+}
+
+void resizeUI()
+{
+    uiMenuBar.setFixedWidth(renderer.getWidth());
+}
+
+void handleScreenMode(StringHash eventType, VariantMap& eventData)
+{
+    resizeUI();
+}

+ 10 - 3
Bin/CoreData/UI/DefaultStyle.xml

@@ -308,8 +308,6 @@
         <imagerect value="48 0 64 16" />
         <border value="3 3 3 3" />
         <resizeborder value="8 8 8 8" />
-        <movable enable="true" />
-        <resizable enable="true" />
     </element>
     <element type="Text">
         <font name="Cour.ttf" size="12" />
@@ -371,6 +369,15 @@
         <selectioncolor value="0.70 0.70 0.70" />
     </element>
     <element type="FileSelectorTitleText">
-        <font name="Cour.ttf" size="15" />
+        <font name="Cour.ttf" size="14" />
+    </element>
+    <element type="EditorMenuBar">
+        <texture name="Textures/UI.png" />
+        <imagerect value="96 0 112 16" />
+        <border value="2 2 2 2" />
+    </element>
+    <element type="EditorMenuText">
+        <font name="segoeui.ttf" size="15" />
+        <fallbackfont name="courier.ttf" size="12" />
     </element>
 </elements>

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

@@ -39,7 +39,7 @@ void start()
         engine.exit();
         return;
     }
-
+         
     initConsole();
     initScene();
     initUI();
@@ -411,11 +411,11 @@ void handleUpdate(StringHash eventType, VariantMap& eventData)
 
     if (ui.getFocusElement() is null)
     {
-        float speedMultiplier = 1.0f;
+        float speedMultiplier = 1.0;
         if (input.getKeyDown(KEY_SHIFT))
-            speedMultiplier = 5.0f;
+            speedMultiplier = 5.0;
         if (input.getKeyDown(KEY_CTRL))
-            speedMultiplier = 0.1f;
+            speedMultiplier = 0.1;
 
         if (input.getKeyDown('W'))
             camera.translateRelative(Vector3(0, 0, 10) * timeStep * speedMultiplier);

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

@@ -1,13 +1,12 @@
 <element type="Window">
     <position value="50 50" />
     <size value="400 300" />
-    <resizable enable="false" />
+    <movable enable="true" />
     <element type="Button">
         <position value="10 10" />
         <size value="100 40" />
         <element type="Text">
             <text value="TEST" />
-            <font name="cour.ttf" size="12" />
             <alignment horizontal="center" vertical="center" />
         </element>
     </element>
@@ -23,19 +22,15 @@
     <element type="CheckBox">
         <position value="40 130" />
     </element>
-
     <element type="Text" name="PopupItem1">
-        <font name="cour.ttf" size="12" />
         <text value="Deferred" />
         <hovercolor value="0.45 0.70 0.45" />
     </element>
     <element type="Text" name="PopupItem2">
-        <font name="cour.ttf" size="12" />
         <text value="Prepass" />
         <hovercolor value="0.45 0.70 0.45" />
     </element>
     <element type="Text" name="PopupItem3">
-        <font name="cour.ttf" size="12" />
         <text value="Forward" />
         <hovercolor value="0.45 0.70 0.45" />
     </element>
@@ -51,59 +46,47 @@
             <layout border="8 8 8 8" />
         </popup>
     </element>
-
     <element type="Text" name="Item1">
-        <font name="cour.ttf" size="12" />
         <text value="Audio" />
         <selectioncolor value="0.45 0.70 0.45" />
     </element>
     <element type="Text" name="Item2">
-        <font name="cour.ttf" size="12" />
         <text value="Common" />
         <selectioncolor value="0.45 0.70 0.45" />
     </element>
     <element type="Text" name="Item3">
-        <font name="cour.ttf" size="12" />
         <text value="Engine" />
         <selectioncolor value="0.45 0.70 0.45" />
     </element>
     <element type="Text" name="Item4">
-        <font name="cour.ttf" size="12" />
         <text value="Input" />
         <selectioncolor value="0.45 0.70 0.45" />
     </element>
     <element type="Text" name="Item5">
-        <font name="cour.ttf" size="12" />
         <text value="Math" />
         <selectioncolor value="0.45 0.70 0.45" />
     </element>
     <element type="Text" name="Item6">
-        <font name="cour.ttf" size="12" />
         <text value="Network" />
         <selectioncolor value="0.45 0.70 0.45" />
     </element>
     <element type="Text" name="Item7">
-        <font name="cour.ttf" size="12" />
         <text value="Physics" />
         <selectioncolor value="0.45 0.70 0.45" />
     </element>
     <element type="Text" name="Item8">
-        <font name="cour.ttf" size="12" />
         <text value="Renderer" />
         <selectioncolor value="0.45 0.70 0.45" />
     </element>
     <element type="Text" name="Item9">
-        <font name="cour.ttf" size="12" />
         <text value="Resource" />
         <selectioncolor value="0.45 0.70 0.45" />
     </element>
     <element type="Text" name="Item10">
-        <font name="cour.ttf" size="12" />
         <text value="Scene" />
         <selectioncolor value="0.45 0.70 0.45" />
     </element>
     <element type="Text" name="Item11">
-        <font name="cour.ttf" size="12" />
         <text value="UI" />
         <selectioncolor value="0.45 0.70 0.45" />
     </element>
@@ -139,7 +122,6 @@
             <layout mode="vertical" />
             <element type="Text">
                 <text value="MenuItem3" />
-                <font name="cour.ttf" size="12" />
                 <alignment horizontal="center" vertical="center" />
             </element>
         </element>
@@ -147,7 +129,6 @@
             <layout mode="vertical" />
             <element type="Text">
                 <text value="MenuItem4" />
-                <font name="cour.ttf" size="12" />
                 <alignment horizontal="center" vertical="center" />
             </element>
         </element>
@@ -155,7 +136,6 @@
             <layout mode="vertical" />
             <element type="Text">
                 <text value="MenuItem5" />
-                <font name="cour.ttf" size="12" />
                 <alignment horizontal="center" vertical="center" />
             </element>
         </element>
@@ -167,7 +147,6 @@
         <popupoffset value="0 16" />
         <element type="Text">
             <text value="MenuItem1" />
-            <font name="cour.ttf" size="12" />
             <alignment horizontal="center" vertical="center" />
         </element>
     </element>
@@ -176,7 +155,6 @@
         <size value="80 16" />
         <element type="Text">
             <text value="MenuItem2" />
-            <font name="cour.ttf" size="12" />
             <alignment horizontal="center" vertical="center" />
         </element>
     </element>

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

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

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

@@ -1,6 +1,7 @@
 <element type="Window">
     <position value="50 50" />
     <size value="400 300" />
+    <movable enable="true" />
     <resizable enable="true" />
     <layout mode="vertical" border="8 8 8 8" spacing="8" />
     <clipborder value="8 8 8 8" />
@@ -10,7 +11,6 @@
         <element type="Button">
             <element type="Text">
                 <text value="TEST1" />
-                <font name="cour.ttf" size="12" />
                 <alignment horizontal="center" vertical="center" />
             </element>
         </element>
@@ -18,14 +18,12 @@
             <visible enable="false" />
             <element type="Text">
                 <text value="TEST2" />
-                <font name="cour.ttf" size="12" />
                 <alignment horizontal="center" vertical="center" />
             </element>
         </element>
         <element type="Button">
             <element type="Text">
                 <text value="TEST3" />
-                <font name="cour.ttf" size="12" />
                 <alignment horizontal="center" vertical="center" />
             </element>
         </element>
@@ -35,14 +33,13 @@
     	<element type="Text" name="ScrollViewText">
 	        <position value="1 0" />
 	        <fixedwidth value="400" />
-	        <font name="times.ttf" size="15" />
+	        <font name="times.ttf" size="12" />
             <text value="At some point the fire had started.\n  There was total chaos. The locks of the wooden barracks had been blown apart by gunfire from shotguns and assault rifles, and the trainees were pouring out in confusion. But the primary target still lay ahead.\n  The administrative building. Where the identities and evaluations of the trainees were stored.\n  That was first class knowledge. If the Agents could bring that knowledge to the public, and cross-reference the records - mostly boys of young age - with lists of known missing persons, then they could prove the existence of SCEPTRE." />
 	        <wordwrap enable="true" />
 	    </element>
         <element type="Button">
             <element type="Text">
                 <text value="TEST4" />
-                <font name="cour.ttf" size="12" />
                 <alignment horizontal="center" vertical="center" />
             </element>
         </element>
@@ -52,7 +49,6 @@
         <element type="Button">
             <element type="Text">
                 <text value="TEST5" />
-                <font name="cour.ttf" size="12" />
                 <alignment horizontal="center" vertical="center" />
             </element>
         </element>
@@ -62,21 +58,18 @@
         <element type="Button">
             <element type="Text">
                 <text value="TEST6" />
-                <font name="cour.ttf" size="12" />
                 <alignment horizontal="center" vertical="center" />
             </element>
         </element>
         <element type="Button">
             <element type="Text">
                 <text value="TEST7" />
-                <font name="cour.ttf" size="12" />
                 <alignment horizontal="center" vertical="center" />
             </element>
         </element>
         <element type="Button">
             <element type="Text">
                 <text value="TEST8" />
-                <font name="cour.ttf" size="12" />
                 <alignment horizontal="center" vertical="center" />
             </element>
         </element>

+ 1 - 0
Bin/Editor.bat

@@ -0,0 +1 @@
+Urho3D Scripts/Editor.as -w -x800 -y600 %1 %2 %3 %4 %5 %6 %7 %8

+ 51 - 11
Engine/Common/File.cpp

@@ -316,18 +316,18 @@ bool checkDirectoryAccess(const std::string& pathName)
     return false;
 }
 
-void splitPath(const std::string& fullPath, std::string& pathName, std::string& fileName, std::string& extension, bool lowerCaseExtension)
+void splitPath(const std::string& fullPath, std::string& pathName, std::string& fileName, std::string& extension)
 {
     std::string fullPathCopy = replace(fullPath, '\\', '/');
     
     size_t extPos = fullPathCopy.rfind('.');
     if (extPos != std::string::npos)
     {
-        extension = fullPathCopy.substr(extPos);
+        extension = toLower(fullPathCopy.substr(extPos));
         fullPathCopy = fullPathCopy.substr(0, extPos);
     }
     else
-        extension = "";
+        extension.clear();
     
     size_t pathPos = fullPathCopy.rfind('/');
     if (pathPos != std::string::npos)
@@ -338,11 +338,8 @@ void splitPath(const std::string& fullPath, std::string& pathName, std::string&
     else
     {
         fileName = fullPathCopy;
-        pathName = "";
+        pathName.clear();
     }
-    
-    if (lowerCaseExtension)
-        extension = toLower(extension);
 }
 
 std::string getPath(const std::string& fullPath)
@@ -359,17 +356,17 @@ std::string getFileName(const std::string& fullPath)
     return file;
 }
 
-std::string getExtension(const std::string& fullPath, bool lowerCaseExtension)
+std::string getExtension(const std::string& fullPath)
 {
     std::string path, file, extension;
-    splitPath(fullPath, path, file, extension, lowerCaseExtension);
+    splitPath(fullPath, path, file, extension);
     return extension;
 }
 
-std::string getFileNameAndExtension(const std::string& fileName, bool lowerCaseExtension)
+std::string getFileNameAndExtension(const std::string& fileName)
 {
     std::string path, file, extension;
-    splitPath(fileName, path, file, extension, lowerCaseExtension);
+    splitPath(fileName, path, file, extension);
     return file + extension;
 }
 
@@ -400,6 +397,49 @@ std::string unfixPath(const std::string& path)
     return path;
 }
 
+std::string getParentPath(const std::string& path)
+{
+    unsigned pos = unfixPath(path).rfind('/');
+    if (pos != std::string::npos)
+        return path.substr(0, pos);
+    else
+        return path;
+}
+
+std::string getAbsoluteFileName(const std::string& fileName)
+{
+    //! \todo Though this routine does not use Win32 API calls, it assumes Win32 filename structure
+    if (fileName.empty())
+        return fileName;
+    
+    std::string fixedPath = replace(fileName, '\\', '/');
+    // Check for a network path or a drive letter, in this case we do not have to do anything
+    if (fixedPath.length() >= 2)
+    {
+        if ((fixedPath[1] == ':') || (fixedPath.substr(0, 2) == "//"))
+            return fixedPath;
+        // Remove redundant ./ if exists
+        if (fixedPath.substr(0, 2) == "./")
+            fixedPath = fixedPath.substr(2);
+    }
+    
+    std::string workingDir = getCurrentDirectory();
+    
+    // If path is absolute in relation to current drive letter, just add it
+    if (fixedPath[0] == '/')
+        return workingDir.substr(0, 2) + fixedPath;
+    
+    // Navigate any ../ in the filename
+    //! \todo Only supported in the beginning
+    while ((fixedPath.length() >= 3) && (fixedPath.substr(0,3) == "../"))
+    {
+        fixedPath = fixedPath.substr(3);
+        workingDir = getParentPath(workingDir);
+    }
+    
+    return workingDir + fixedPath;
+}
+
 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

+ 10 - 6
Engine/Common/File.h

@@ -113,20 +113,24 @@ bool systemOpenFile(const std::string& fileName, const std::string& mode = std::
 void registerDirectory(const std::string& pathName);
 //! Check if a path is allowed to be accessed. If no paths defined, all are allowed
 bool checkDirectoryAccess(const std::string& pathName);
-//! Split a full path to path, filename and extension. Optionally convert the extension to lowercase
-void splitPath(const std::string& fullPath, std::string& pathName, std::string& fileName, std::string& extension, bool lowerCaseExtension = true);
+//! Split a full path to path, filename and extension. The extension will be converted to lowercase
+void splitPath(const std::string& fullPath, std::string& pathName, std::string& fileName, std::string& extension);
 //! Return the path from a full path
 std::string getPath(const std::string& fullPath);
 //! Return the filename from a full path
 std::string getFileName(const std::string& fullPath);
-//! Return the extension from a full path and optionally convert to lowercase
-std::string getExtension(const std::string& fullPath, bool lowerCaseExtension = true);
-//! Return the filename and extension from a full path. Optionally convert the extension to lowercase
-std::string getFileNameAndExtension(const std::string& fullPath, bool lowerCaseExtension = true);
+//! Return the extension from a full path, converted to lowercase
+std::string getExtension(const std::string& fullPath);
+//! Return the filename and extension from a full path. The extension will be converted to lowercase
+std::string getFileNameAndExtension(const std::string& fullPath);
 //! 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);
+//! Return the parent path, or the path itself if not available
+std::string getParentPath(const std::string& pathName);
+//! Return the absolute filename using the current working directory
+std::string getAbsoluteFileName(const std::string& fileName);
 //! Convert a path to the format required by the operating system
 std::string getOSPath(const std::string& pathName, bool forNativeApi = false);
 

+ 9 - 16
Engine/Engine/Console.cpp

@@ -100,18 +100,13 @@ void Console::setStyle(XMLFile* style)
     
     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);
+    
+    mBackground->setStyle(style, "ConsoleBackground", cache);
+    
+    for (unsigned i = 0; i < mRows.size(); ++i)
+        mRows[i]->setStyle(style, "ConsoleText", cache);
+    
+    mLineEdit->setStyle(style, "ConsoleLineEdit", cache);
     
     updateElements();
 }
@@ -146,9 +141,7 @@ void Console::setNumRows(unsigned rows)
         if (!mRows[i])
         {
             mRows[i] = new Text();
-            XMLElement textElem = UIElement::getStyleElement(mStyle, "ConsoleText");
-            if (textElem)
-                mRows[i]->setStyle(textElem, mEngine->getResourceCache());
+            mRows[i]->setStyle(mStyle, "ConsoleText", mEngine->getResourceCache());
         }
         mRowContainer->addChild(mRows[i]);
     }
@@ -204,7 +197,7 @@ void Console::handleTextFinished(StringHash eventType, VariantMap& eventData)
             mHistory.erase(mHistory.begin());
         mHistoryPosition = mHistory.size();
         
-        mCurrentRow = std::string();
+        mCurrentRow.clear();
         mLineEdit->setText(mCurrentRow);
     }
 }

+ 3 - 7
Engine/Engine/DebugHud.cpp

@@ -202,13 +202,9 @@ void DebugHud::setStyle(XMLFile* style)
     
     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);
-    }
+    mStatsText->setStyle(style, "DebugHudText", cache);
+    mModeText->setStyle(style, "DebugHudText", cache);
+    mProfilerText->setStyle(style, "DebugHudText", cache);
 }
 
 void DebugHud::setMode(unsigned mode)

+ 4 - 2
Engine/Engine/RegisterCommon.cpp

@@ -604,10 +604,12 @@ static void registerSerialization(asIScriptEngine* engine)
     engine->RegisterGlobalFunction("bool systemOpenFile(const string& in, const string& in)", asFUNCTION(systemOpenFile), 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 getExtension(const string& in)", asFUNCTION(getExtension), asCALL_CDECL);
+    engine->RegisterGlobalFunction("string getFileNameAndExtension(const string& in)", 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->RegisterGlobalFunction("string getParentPath(const string& in)", asFUNCTION(getParentPath), asCALL_CDECL);
+    engine->RegisterGlobalFunction("string getAbsoluteFileName(const string& in)", asFUNCTION(getAbsoluteFileName), 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);

+ 2 - 1
Engine/Engine/RegisterResource.cpp

@@ -96,7 +96,7 @@ static void registerResourceCache(asIScriptEngine* engine)
     engine->RegisterObjectType("ResourceCache", 0, asOBJ_REF);
     engine->RegisterObjectBehaviour("ResourceCache", asBEHAVE_ADDREF, "void f()", asMETHOD(ResourceCache, addRef), asCALL_THISCALL);
     engine->RegisterObjectBehaviour("ResourceCache", asBEHAVE_RELEASE, "void f()", asMETHOD(ResourceCache, releaseRef), asCALL_THISCALL);
-    engine->RegisterObjectMethod("ResourceCache", "void addResourcePath(const string& in)", asMETHOD(ResourceCache, addResourcePath), asCALL_THISCALL);
+    engine->RegisterObjectMethod("ResourceCache", "bool addResourcePath(const string& in)", asMETHOD(ResourceCache, addResourcePath), asCALL_THISCALL);
     engine->RegisterObjectMethod("ResourceCache", "void addPackageFile(PackageFile@+)", asMETHOD(ResourceCache, addPackageFile), asCALL_THISCALL);
     engine->RegisterObjectMethod("ResourceCache", "bool addManualResource(Resource@+)", asMETHOD(ResourceCache, addManualResource), asCALL_THISCALL);
     engine->RegisterObjectMethod("ResourceCache", "void removeResourcePath(const string& in)", asMETHOD(ResourceCache, removeResourcePath), asCALL_THISCALL);
@@ -105,6 +105,7 @@ static void registerResourceCache(asIScriptEngine* engine)
     engine->RegisterObjectMethod("ResourceCache", "void releaseResource(const string& in, const string& in, bool)", asFUNCTION(ResourceCacheReleaseResource), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("ResourceCache", "void releaseResources(const string& in, bool)", asFUNCTION(ResourceCacheReleaseResources), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("ResourceCache", "void releaseResources(const string& in, const string& in, bool)", asFUNCTION(ResourceCacheReleaseResourcesPartial), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("ResourceCache", "void releaseAllResources(bool)", asMETHOD(ResourceCache, releaseAllResources), asCALL_THISCALL);
     engine->RegisterObjectMethod("ResourceCache", "bool reloadResource(Resource@+)", asMETHOD(ResourceCache, reloadResource), asCALL_THISCALL);
     engine->RegisterObjectMethod("ResourceCache", "void setMemoryBudget(const string& in, uint)", asFUNCTION(ResourceCacheSetMemoryBudget), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("ResourceCache", "bool exists(const string& in) const", asMETHODPR(ResourceCache, exists, (const std::string&) const, bool), asCALL_THISCALL);

+ 7 - 1
Engine/Engine/RegisterTemplates.h

@@ -420,6 +420,12 @@ template <class T> void UIElementSetStyle(const XMLElement& element, T* ptr)
     ptr->setStyle(element, getEngine()->getResourceCache());
 }
 
+//! Template function for setting UI element style from an XML file
+template <class T> void UIElementSetStyleName(XMLFile* file, const std::string& typeName, T* ptr)
+{
+    ptr->setStyle(file, typeName, getEngine()->getResourceCache());
+}
+
 //! Template function for setting UI element style from an XML file
 template <class T> void UIElementSetStyleAuto(XMLFile* file, T* ptr)
 {
@@ -473,6 +479,7 @@ template <class T> void registerUIElement(asIScriptEngine* engine, const char* c
     engine->RegisterObjectMethod(className, "void setVisible(bool)", asMETHOD(T, setVisible), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setFocusMode(FocusMode)", asMETHOD(T, setFocusMode), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setDragDropMode(uint)", asMETHOD(T, setDragDropMode), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "void setStyle(XMLFile@+, const string& in)", asFUNCTION(UIElementSetStyleName<T>), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod(className, "void setStyleAuto(XMLFile@+)", asFUNCTION(UIElementSetStyleAuto<T>), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod(className, "void setLayout(LayoutMode, int, const IntRect&)", asMETHOD(T, setLayout), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void setLayoutSpacing(int)", asMETHOD(T, setLayoutSpacing), asCALL_THISCALL);
@@ -523,7 +530,6 @@ template <class T> void registerUIElement(asIScriptEngine* engine, const char* c
     engine->RegisterObjectMethod(className, "UIElement@+ getChild(const string& in, bool) const", asMETHODPR(T, getChild, (const std::string&, bool) const, UIElement*), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "UIElement@+ getParent() const", asMETHOD(T, getParent), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "UIElement@+ getRootElement() const", asMETHOD(T, getRootElement), asCALL_THISCALL);
-    engine->RegisterObjectMethod(className, "XMLElement getStyleElement(XMLFile@+) const", asMETHODPR(T, getStyleElement, (XMLFile*) const, XMLElement), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "IntVector2 screenToElement(const IntVector2& in)", asMETHOD(T, screenToElement), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "IntVector2 elementToScreen(const IntVector2& in)", asMETHOD(T, elementToScreen), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "bool isInside(IntVector2, bool)", asMETHOD(T, isInside), asCALL_THISCALL);

+ 0 - 3
Engine/Engine/RegisterUI.cpp

@@ -82,9 +82,6 @@ static void registerUIElement(asIScriptEngine* engine)
     
     registerUIElement<UIElement>(engine, "UIElement");
     
-    // Register static function for getting UI style XML element
-    engine->RegisterGlobalFunction("XMLElement getStyleElement(XMLFile@+, const string& in)", asFUNCTIONPR(UIElement::getStyleElement, (XMLFile*, const std::string&), XMLElement), asCALL_CDECL);
-    
     // Register Variant getPtr() for UIElement
     engine->RegisterObjectMethod("Variant", "UIElement@+ getUIElement() const", asFUNCTION(getVariantPtr<UIElement>), asCALL_CDECL_OBJLAST);
 }

+ 31 - 11
Engine/Input/Input.cpp

@@ -35,6 +35,7 @@
 Input::Input(Renderer* renderer) :
     mRenderer(renderer),
     mClipCursor(true),
+    mShowCursor(true),
     mToggleFullscreen(true),
     mActive(false),
     mMinimized(false),
@@ -207,6 +208,7 @@ void Input::handleWindowMessage(StringHash eventType, VariantMap& eventData)
     
     int msg = eventData[P_MSG].getInt();
     int wParam = eventData[P_WPARAM].getInt();
+    int lParam = eventData[P_LPARAM].getInt();
     
     switch (msg)
     {
@@ -314,6 +316,16 @@ void Input::handleWindowMessage(StringHash eventType, VariantMap& eventData)
         mSuppressNextChar = false;
         eventData[P_HANDLED] = true;
         break;
+        
+    case WM_SETCURSOR:
+        if ((lParam & 0xffff) == HTCLIENT)
+        {
+            setCursorVisible(false);
+            eventData[P_HANDLED] = true;
+        }
+        else
+            setCursorVisible(true);
+        break;
     }
 }
 
@@ -334,14 +346,12 @@ void Input::makeActive()
         return;
     }
     
-    if (!mActive)
-        ShowCursor(FALSE);
-    
     mActive = true;
     mActivated = false;
     
     // Re-establish mouse cursor clipping if necessary
     setClipCursor(mClipCursor);
+    setCursorVisible(false);
     
     sendEvent(EVENT_ACTIVATED);
 }
@@ -356,17 +366,14 @@ void Input::makeInactive()
         return;
     }
     
-    if (mActive)
-    {
-        ShowCursor(TRUE);
-        ReleaseCapture();
-    }
-    
-    ClipCursor(0);
-    
     mActive = false;
     mActivated = false;
     
+    // Free and show the mouse cursor
+    ReleaseCapture();
+    ClipCursor(0);
+    setCursorVisible(true);
+    
     sendEvent(EVENT_INACTIVATED);
 }
 
@@ -506,3 +513,16 @@ void Input::checkMouseMove()
         sendEvent(EVENT_MOUSEMOVE, eventData);
     }
 }
+
+void Input::setCursorVisible(bool enable)
+{
+    // When inactive, always show the cursor
+    if (!mActive)
+        enable = true;
+    
+    if (mShowCursor == enable)
+        return;
+    
+    ShowCursor(enable ? TRUE : FALSE);
+    mShowCursor = enable;
+}

+ 4 - 0
Engine/Input/Input.h

@@ -106,6 +106,8 @@ private:
     void mouseWheelChange(int delta);
     //! Check for mouse move and send event if moved
     void checkMouseMove();
+    //! Internal function to show/hide the operating system mouse cursor
+    void setCursorVisible(bool enable);
     
     //! Renderer subsystem
     SharedPtr<Renderer> mRenderer;
@@ -125,6 +127,8 @@ private:
     int mMouseMoveWheel;
     //! Mouse cursor confine flag
     bool mClipCursor;
+    //! Mouse cursor show/hide flag
+    bool mShowCursor;
     //! Fullscreen toggle flag
     bool mToggleFullscreen;
     //! Active flag

+ 83 - 15
Engine/Resource/ResourceCache.cpp

@@ -53,26 +53,66 @@ void ResourceCache::addResourceFactory(ResourceFactory* factory)
     mFactories.push_back(SharedPtr<ResourceFactory>(factory));
 }
 
-void ResourceCache::addResourcePath(const std::string& path)
+bool ResourceCache::addResourcePath(const std::string& path)
 {
+    if (!directoryExists(path))
+    {
+        LOGERROR("Could not open directory " + path);
+        return false;
+    }
+    
     std::string fixedPath = fixPath(path);
+    
+    // Check for the existence of some known resource subdirectories to decide if we should add the parent directory instead
+    static const std::string checkDirs[] = {
+        "Fonts",
+        "Materials",
+        "Models",
+        "Music",
+        "Particle",
+        "Physics",
+        "Scripts",
+        "Sounds",
+        "Shaders",
+        "Textures",
+        "UI",
+        ""
+    };
+    
+    bool pathHasKnownDirs = false;
+    for (unsigned i = 0; !checkDirs[i].empty(); ++i)
+    {
+        if (directoryExists(fixedPath + checkDirs[i]))
+        {
+            pathHasKnownDirs = true;
+            break;
+        }
+    }
+    if (!pathHasKnownDirs)
+    {
+        std::string parentPath = getParentPath(fixedPath);
+        bool parentHasKnownDirs = false;
+        for (unsigned i = 0; !checkDirs[i].empty(); ++i)
+        {
+            if (directoryExists(parentPath + checkDirs[i]))
+            {
+                parentHasKnownDirs = true;
+                break;
+            }
+        }
+        // If path does not have known subdirectories, but the parent path has, use the parent instead
+        if (parentHasKnownDirs)
+            fixedPath = parentPath;
+    }
+    
     std::string pathLower = toLower(fixedPath);
     // Check that the same path does not already exist
     for (unsigned i = 0; i < mResourcePaths.size(); ++i)
     {
         if (toLower(mResourcePaths[i]) == pathLower)
-            return;
-    }
-    
-    if (!directoryExists(path))
-    {
-        LOGERROR("Could not open directory " + path);
-        return;
+            return true;
     }
     
-    checkDirectoryAccess(path);
-    
-
     mResourcePaths.push_back(fixedPath);
     
     // Scan the path for files recursively and add their hash-to-name mappings
@@ -82,6 +122,7 @@ void ResourceCache::addResourcePath(const std::string& path)
         registerHash(fileNames[i]);
     
     LOGINFO("Added resource path " + fixedPath);
+    return true;
 }
 
 void ResourceCache::addPackageFile(PackageFile* package, bool addAsFirst)
@@ -124,12 +165,14 @@ bool ResourceCache::addManualResource(Resource* resource)
 
 void ResourceCache::removeResourcePath(const std::string& path)
 {
+    std::string fixedPath = toLower(fixPath(path));
     for (std::vector<std::string>::iterator i = mResourcePaths.begin(); i != mResourcePaths.end();)
     {
-        if (i->find(path) == 0)
+        if (toLower(*i) == fixedPath)
+        {
             i = mResourcePaths.erase(i);
-        else
-            ++i;
+            return;
+        }
     }
 }
 
@@ -149,9 +192,11 @@ void ResourceCache::removePackageFile(PackageFile* package, bool releaseResource
 
 void ResourceCache::removePackageFile(const std::string& fileName, bool releaseResources, bool forceRelease)
 {
+    std::string fileNameLower = toLower(fileName);
+    
     for (std::vector<SharedPtr<PackageFile> >::iterator i = mPackages.begin(); i != mPackages.end(); ++i)
     {
-        if ((*i)->getName() == fileName)
+        if (toLower((*i)->getName()) == fileNameLower)
         {
             if (releaseResources)
                 releasePackageResources(*i, forceRelease);
@@ -238,6 +283,29 @@ void ResourceCache::releaseResources(ShortStringHash type, const std::string& pa
         updateResourceGroup(type);
 }
 
+void ResourceCache::releaseAllResources(bool force)
+{
+    for (std::map<ShortStringHash, ResourceGroup>::iterator i = mResourceGroups.begin();
+        i != mResourceGroups.end(); ++i)
+    {
+        bool released = false;
+        
+        for (std::map<StringHash, SharedPtr<Resource> >::iterator j = i->second.mResources.begin();
+            j != i->second.mResources.end();)
+        {
+            std::map<StringHash, SharedPtr<Resource> >::iterator current = j++;
+            // If other references exist, do not release, unless forced
+            if ((current->second.getRefCount() == 1) || (force))
+            {
+                i->second.mResources.erase(current);
+                released = true;
+            }
+        }
+        if (released)
+            updateResourceGroup(i->first);
+    }
+}
+
 bool ResourceCache::reloadResource(Resource* resource)
 {
     if (!resource)

+ 3 - 1
Engine/Resource/ResourceCache.h

@@ -65,7 +65,7 @@ public:
     //! Add a resource factory
     void addResourceFactory(ResourceFactory* factory);
     //! Add a resource load path
-    void addResourcePath(const std::string& path);
+    bool addResourcePath(const std::string& path);
     //! Add a package file for loading resources from
     void addPackageFile(PackageFile* package, bool addAsFirst = false);
     //! Add a manually created resource. Must be uniquely named
@@ -84,6 +84,8 @@ public:
     void releaseResources(ShortStringHash type, bool force = false);
     //! Release resources of a specific type and partial name
     void releaseResources(ShortStringHash type, const std::string& partialName, bool force = false);
+    //! Release all resources
+    void releaseAllResources(bool force = false);
     //! Reload a resource. Return false and release it if fails
     bool reloadResource(Resource* resource);
     //! Set memory budget for a specific resource type, default 0 is unlimited

+ 1 - 0
Engine/Script/RegisterArray.cpp

@@ -544,6 +544,7 @@ void registerArray(asIScriptEngine* engine)
     engine->RegisterObjectMethod("array<T>", "void remove(uint)", asMETHOD(CScriptArray, RemoveAt), asCALL_THISCALL);
     engine->RegisterObjectMethod("array<T>", "void push(const T& in)", asMETHOD(CScriptArray, InsertLast), asCALL_THISCALL);
     engine->RegisterObjectMethod("array<T>", "void pop()", asMETHOD(CScriptArray, RemoveLast), asCALL_THISCALL);
+    engine->RegisterObjectMethod("array<T>", "uint length() const", asMETHOD(CScriptArray, GetSize), asCALL_THISCALL);
     engine->RegisterObjectMethod("array<T>", "uint size() const", asMETHOD(CScriptArray, GetSize), asCALL_THISCALL);
     engine->RegisterObjectMethod("array<T>", "void resize(uint)", asMETHODPR(CScriptArray, Resize, (asUINT), void), asCALL_THISCALL);
     engine->RegisterDefaultArrayType("array<T>");

+ 3 - 1
Engine/Script/RegisterStdString.cpp

@@ -196,7 +196,9 @@ void registerStdString(asIScriptEngine *engine)
     engine->RegisterObjectMethod("string", "bool opEquals(const string& in) const", asFUNCTIONPR(std::operator ==, (const std::string&, const std::string&), bool), asCALL_CDECL_OBJFIRST);
     engine->RegisterObjectMethod("string", "int opCmp(const string& in) const", asFUNCTION(StringCmp), asCALL_CDECL_OBJFIRST);
     engine->RegisterObjectMethod("string", "string opAdd(const string& in) const", asFUNCTIONPR(std::operator +, (const std::string&, const std::string&), std::string), asCALL_CDECL_OBJFIRST);
-    engine->RegisterObjectMethod("string", "uint length() const", asMETHOD(std::string, size), asCALL_THISCALL);
+    engine->RegisterObjectMethod("string", "uint length() const", asMETHOD(std::string, length), asCALL_THISCALL);
+    engine->RegisterObjectMethod("string", "uint size() const", asMETHOD(std::string, size), asCALL_THISCALL);
+    engine->RegisterObjectMethod("string", "bool empty() const", asMETHOD(std::string, empty), asCALL_THISCALL);
     engine->RegisterObjectMethod("string", "void resize(uint)", asMETHODPR(std::string, resize, (size_t), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("string", "uint8 &opIndex(uint)", asFUNCTION(StringCharAt), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("string", "const uint8 &opIndex(uint) const", asFUNCTION(StringCharAt), asCALL_CDECL_OBJLAST);

+ 3 - 0
Engine/UI/BorderImage.cpp

@@ -42,6 +42,9 @@ BorderImage::~BorderImage()
 
 void BorderImage::setStyle(const XMLElement& element, ResourceCache* cache)
 {
+    if (!cache)
+        return;
+    
     UIElement::setStyle(element, cache);
     
     if (element.hasChildElement("texture"))

+ 2 - 0
Engine/UI/BorderImage.h

@@ -34,6 +34,8 @@ class BorderImage : public UIElement
 {
     DEFINE_TYPE(BorderImage);
     
+    using UIElement::setStyle;
+    
 public:
     //! Construct with name
     BorderImage(const std::string& name = std::string());

+ 2 - 0
Engine/UI/Button.h

@@ -31,6 +31,8 @@ class Button : public BorderImage
 {
     DEFINE_TYPE(Button);
     
+    using UIElement::setStyle;
+    
 public:
     //! Construct with name
     Button(const std::string& name = std::string());

+ 2 - 0
Engine/UI/CheckBox.h

@@ -31,6 +31,8 @@ class CheckBox : public BorderImage
 {
     DEFINE_TYPE(CheckBox);
     
+    using UIElement::setStyle;
+    
 public:
     //! Construct with name
     CheckBox(const std::string& name = std::string());

+ 3 - 0
Engine/UI/Cursor.cpp

@@ -61,6 +61,9 @@ Cursor::~Cursor()
 
 void Cursor::setStyle(const XMLElement& element, ResourceCache* cache)
 {
+    if (!cache)
+        return;
+    
     UIElement::setStyle(element, cache);
     
     XMLElement shapeElem = element.getChildElement("shape");

+ 2 - 0
Engine/UI/Cursor.h

@@ -50,6 +50,8 @@ class Cursor : public BorderImage
 {
     DEFINE_TYPE(Cursor);
     
+    using UIElement::setStyle;
+    
 public:
     //! Construct with name
     Cursor(const std::string& name = std::string());

+ 2 - 0
Engine/UI/DropDownList.h

@@ -30,6 +30,8 @@ class DropDownList : public Menu
 {
     DEFINE_TYPE(DropDownList)
     
+    using UIElement::setStyle;
+    
 public:
     //! Construct with name
     DropDownList(const std::string& name = std::string());

+ 19 - 50
Engine/UI/FileSelector.cpp

@@ -131,61 +131,35 @@ void FileSelector::setStyle(XMLFile* style)
     ResourceCache* cache = mUI->getResourceCache();
     
     mWindow->setStyleAuto(style, cache);
-    XMLElement windowElem = UIElement::getStyleElement(style, "FileSelector");
-    if (windowElem)
-        mWindow->setStyle(windowElem, cache);
+    mWindow->setStyle(style, "FileSelector", cache);
     
-    XMLElement titleElem = UIElement::getStyleElement(style, "FileSelectorTitleText");
-    if (titleElem)
-        mTitleText->setStyle(titleElem, cache);
+    mTitleText->setStyle(style, "FileSelectorTitleText", cache);
     
-    XMLElement textElem = UIElement::getStyleElement(style, "FileSelectorButtonText");
-    if (textElem)
-    {
-        mOKButtonText->setStyle(textElem, cache);
-        mCancelButtonText->setStyle(textElem, cache);
-    }
+    mOKButtonText->setStyle(style, "FileSelectorButtonText", cache);
+    mCancelButtonText->setStyle(style, "FileSelectorButtonText", cache);
     
-    XMLElement layoutElem = UIElement::getStyleElement(style, "FileSelectorLayout");
-    if (layoutElem)
-    {
-        mFileNameLayout->setStyle(layoutElem, cache);
-        mButtonLayout->setStyle(layoutElem, cache);
-    }
+    mFileNameLayout->setStyle(style, "FileSelectorLayout", cache);
+    mButtonLayout->setStyle(style, "FileSelectorLayout", cache);
     
     mFileList->setStyleAuto(style, cache);
     mFileNameEdit->setStyleAuto(style, cache);
     mPathEdit->setStyleAuto(style, cache);
     
     mFilterList->setStyleAuto(style, cache);
-    XMLElement dropDownElem = UIElement::getStyleElement(style, "FileSelectorFilterList");
-    if (dropDownElem)
-        mFilterList->setStyle(dropDownElem, cache);
+    mFilterList->setStyle(style, "FileSelectorFilterList", cache);
     
     mOKButton->setStyleAuto(style, cache);
     mCancelButton->setStyleAuto(style, cache);
-    XMLElement buttonElem = UIElement::getStyleElement(style, "FileSelectorButton");
-    if (buttonElem)
-    {
-        mOKButton->setStyle(buttonElem, cache);
-        mCancelButton->setStyle(buttonElem, cache);
-    }
+    mOKButton->setStyle(style, "FileSelectorButton", cache);
+    mCancelButton->setStyle(style, "FileSelectorButton", 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);
-    }
+    std::vector<UIElement*> filterTexts = mFilterList->getListView()->getContentElement()->getChildren();
+    for (unsigned i = 0; i < filterTexts.size(); ++i)
+        filterTexts[i]->setStyle(style, "FileSelectorFilterText", 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);
-    }
+    std::vector<UIElement*> listTexts = mFileList->getContentElement()->getChildren();
+    for (unsigned i = 0; i < listTexts.size(); ++i)
+        listTexts[i]->setStyle(style, "FileSelectorListText", cache);
     
     updateElements();
 }
@@ -242,9 +216,7 @@ void FileSelector::setFilters(const std::vector<std::string>& filters, unsigned
     {
         Text* filterText = new Text();
         filterText->setText(mFilters[i]);
-        XMLElement textElem = UIElement::getStyleElement(mStyle, "FileSelectorFilterText");
-        if (textElem)
-            filterText->setStyle(textElem, mUI->getResourceCache());
+        filterText->setStyle(mStyle, "FileSelectorFilterText", mUI->getResourceCache());
         mFilterList->addItem(filterText);
     }
     mFilterList->setSelection(defaultIndex);
@@ -330,9 +302,7 @@ void FileSelector::refreshFiles()
         
         Text* entryText = new Text();
         entryText->setText(displayName);
-        XMLElement textElem = UIElement::getStyleElement(mStyle, "FileSelectorListText");
-        if (textElem)
-            entryText->setStyle(textElem, mUI->getResourceCache());
+        entryText->setStyle(mStyle, "FileSelectorListText", mUI->getResourceCache());
         mFileList->addItem(entryText);
     }
     listContent->enableLayoutUpdate();
@@ -359,9 +329,8 @@ bool FileSelector::enterFile()
             setPath(mPath + newPath);
         else if (newPath == "..")
         {
-            unsigned pos = unfixPath(mPath).rfind('/');
-            if (pos != std::string::npos)
-                setPath(mPath.substr(0, pos));
+            std::string parentPath = getParentPath(mPath);
+            setPath(parentPath);
         }
     }
     else

+ 25 - 6
Engine/UI/Font.cpp

@@ -30,10 +30,12 @@
 #include "Texture2D.h"
 
 #include <stb_truetype.h>
-#include <windows.h>
 
 #include "DebugNew.h"
 
+static const float DEFAULT_DPI = 96.0f;
+static const float DEFAULT_PPI = 72.0f;
+
 Font::Font(Renderer* renderer, const std::string& name) :
     Resource(name),
     mRenderer(renderer),
@@ -97,12 +99,10 @@ const FontFace* Font::getFace(int pointSize)
             glyphUsed[newFace.mGlyphIndex[i]] = true;
         }
         
-        // Get row height
+        // Get row height at 96 DPI
         int ascent, descent, lineGap;
         stbtt_GetFontVMetrics(&fontInfo, &ascent, &descent, &lineGap);
-        
-        // Calculate scale (use ascent only)
-        float scale = (float)pointSize / ascent;
+        float scale = (float)pointSize * (DEFAULT_DPI / DEFAULT_PPI) / (ascent - descent);
         
         // Go through glyphs to get their dimensions & offsets
         for (int i = 0; i < fontInfo.numGlyphs; ++i)
@@ -134,17 +134,36 @@ const FontFace* Font::getFace(int pointSize)
             newFace.mGlyphs.push_back(newGlyph);
         }
         
-        // Adjust the Y-offset so that the fonts are top-aligned
+        // Adjust the Y-offset so that the glyphs are top-aligned
         int scaledAscent = (int)(scale * ascent);
+        int minY = M_MAX_INT;
+        int maxY = 0;
+        
         for (int i = 0; i < fontInfo.numGlyphs; ++i)
         {
             if (glyphUsed[i])
+            {
                 newFace.mGlyphs[i].mOffsetY += scaledAscent;
+                minY = min(minY, newFace.mGlyphs[i].mOffsetY);
+                maxY = max(maxY, newFace.mGlyphs[i].mOffsetY + newFace.mGlyphs[i].mHeight);
+            }
         }
         
         // Calculate row advance
         newFace.mRowHeight = (int)(scale * (ascent - descent + lineGap));
         
+        // If font is not yet top-aligned, center it vertically if there is room
+        if (minY > 0)
+        {
+            int actualHeight = maxY - minY;
+            int adjust = max((newFace.mRowHeight - actualHeight) / 2, 0);
+            for (int i = 0; i < fontInfo.numGlyphs; ++i)
+            {
+                if (glyphUsed[i])
+                    newFace.mGlyphs[i].mOffsetY -= adjust;
+            }
+        }
+        
         // Now try to pack into the smallest possible texture
         int texWidth = FONT_TEXTURE_MIN_SIZE;
         int texHeight = FONT_TEXTURE_MIN_SIZE;

+ 2 - 0
Engine/UI/LineEdit.h

@@ -33,6 +33,8 @@ class LineEdit : public BorderImage
 {
     DEFINE_TYPE(LineEdit);
     
+    using UIElement::setStyle;
+    
 public:
     //! Construct with name and initial text
     LineEdit(const std::string& name = std::string(), const std::string& text = std::string());

+ 2 - 0
Engine/UI/ListView.h

@@ -44,6 +44,8 @@ class ListView : public ScrollView
 {
     DEFINE_TYPE(ListView);
     
+    using UIElement::setStyle;
+    
 public:
     //! Construct with name
     ListView(const std::string& name = std::string());

+ 3 - 0
Engine/UI/Menu.h

@@ -31,6 +31,9 @@ class Menu : public Button
 {
     DEFINE_TYPE(Menu);
     
+    using UIElement::setStyle;
+    
+public:
     //! Construct with name
     Menu(const std::string& name = std::string());
     //! Destruct

+ 2 - 0
Engine/UI/ScrollBar.h

@@ -34,6 +34,8 @@ class ScrollBar : public UIElement
 {
     DEFINE_TYPE(ScrollBar);
     
+    using UIElement::setStyle;
+    
 public:
     //! Construct with name
     ScrollBar(const std::string& name = std::string());

+ 2 - 0
Engine/UI/ScrollView.h

@@ -34,6 +34,8 @@ class ScrollView : public UIElement
 {
     DEFINE_TYPE(ScrollView);
     
+    using UIElement::setStyle;
+    
 public:
     //! Construct with name
     ScrollView(const std::string& name = std::string());

+ 2 - 0
Engine/UI/Slider.h

@@ -31,6 +31,8 @@ class Slider : public BorderImage
 {
     DEFINE_TYPE(Slider);
     
+    using UIElement::setStyle;
+    
 public:
     //! Construct with name
     Slider(const std::string& name = std::string());

+ 25 - 7
Engine/UI/Text.cpp

@@ -62,6 +62,9 @@ Text::~Text()
 
 void Text::setStyle(const XMLElement& element, ResourceCache* cache)
 {
+    if (!cache)
+        return;
+    
     UIElement::setStyle(element, cache);
     
     // Recalculating the text is expensive, so do it only once at the end if something changed
@@ -70,14 +73,29 @@ void Text::setStyle(const XMLElement& element, ResourceCache* cache)
     if (element.hasChildElement("font"))
     {
         XMLElement fontElem = element.getChildElement("font");
-        Font* font = cache->getResource<Font>(fontElem.getString("name"));
-        if (!font)
-            LOGERROR("Null font for Text");
-        else
+        std::string fontName = fontElem.getString("name");
+        
+        if (cache->exists(fontName))
         {
-            mFont = font;
-            mFontSize = max(fontElem.getInt("size"), 1);
-            changed = true;
+            Font* font = cache->getResource<Font>(fontName);
+            if (font)
+            {
+                mFont = font;
+                mFontSize = max(fontElem.getInt("size"), 1);
+                changed = true;
+            }
+        }
+        else if (element.hasChildElement("fallbackfont"))
+        {
+            fontElem = element.getChildElement("fallbackfont");
+            std::string fontName = fontElem.getString("name");
+            Font* font = cache->getResource<Font>(fontName);
+            if (font)
+            {
+                mFont = font;
+                mFontSize = max(fontElem.getInt("size"), 1);
+                changed = true;
+            }
         }
     }
     if (element.hasChildElement("text"))

+ 2 - 0
Engine/UI/Text.h

@@ -35,6 +35,8 @@ class Text : public UIElement
 {
     DEFINE_TYPE(Text);
     
+    using UIElement::setStyle;
+    
 public:
     //! Construct with name and initial text
     Text(const std::string& name = std::string(), const std::string& text = std::string());

+ 6 - 2
Engine/UI/UI.cpp

@@ -487,8 +487,12 @@ void UI::handleMouseMove(StringHash eventType, VariantMap& eventData)
         }
         else
         {
-            // When in non-confined mode, move cursor always to ensure accurate position, and do not clamp
-            mCursor->setPosition(eventData[P_X].getInt(), eventData[P_Y].getInt());
+            // When in non-confined mode, move cursor always to ensure accurate position
+            // Do not clamp, but hide the cursor when not in the window's client area
+            int x = eventData[P_X].getInt();
+            int y = eventData[P_Y].getInt();
+            mCursor->setPosition(x, y);
+            mCursor->setVisible((x >= 0) && (y >= 0) && (x < mRenderer->getWidth()) && (y < mRenderer->getHeight()));
         }
         
         if ((mMouseDragElement) && (mMouseButtons))

+ 19 - 39
Engine/UI/UIElement.cpp

@@ -112,9 +112,6 @@ UIElement::~UIElement()
 
 void UIElement::setStyle(const XMLElement& element, ResourceCache* cache)
 {
-    if (!cache)
-        return;
-    
     if (element.hasAttribute("name"))
         mName = element.getString("name");
     if (element.hasChildElement("position"))
@@ -567,10 +564,27 @@ void UIElement::setDragDropMode(unsigned mode)
     mDragDropMode = mode;
 }
 
+void UIElement::setStyle(XMLFile* file, const std::string& typeName, ResourceCache* cache)
+{
+    if (!file)
+        return;
+    
+    XMLElement rootElem = file->getRootElement();
+    XMLElement childElem = rootElem.getChildElement("element");
+    while (childElem)
+    {
+        if (childElem.getString("type") == typeName)
+        {
+            setStyle(childElem, cache);
+            return;
+        }
+        childElem = childElem.getNextElement("element");
+    }
+}
+
 void UIElement::setStyleAuto(XMLFile* file, ResourceCache* cache)
 {
-    XMLElement element = getStyleElement(file);
-    setStyle(element, cache);
+    setStyle(file, getTypeName(), cache);
 }
 
 void UIElement::setLayout(LayoutMode mode, int spacing, const IntRect& border)
@@ -915,23 +929,6 @@ UIElement* UIElement::getRootElement() const
     return root;
 }
 
-XMLElement UIElement::getStyleElement(XMLFile* file) const
-{
-    if (file)
-    {
-        XMLElement rootElem = file->getRootElement();
-        XMLElement childElem = rootElem.getChildElement("element");
-        while (childElem)
-        {
-            if (childElem.getString("type") == getTypeName())
-                return childElem;
-            childElem = childElem.getNextElement("element");
-        }
-    }
-    
-    return XMLElement();
-}
-
 IntVector2 UIElement::screenToElement(const IntVector2& screenPosition)
 {
     return screenPosition - getScreenPosition();
@@ -1038,23 +1035,6 @@ void UIElement::getBatchesWithOffset(IntVector2& offset, std::vector<UIBatch>& b
     }
 }
 
-XMLElement UIElement::getStyleElement(XMLFile* file, const std::string& typeName)
-{
-    if (file)
-    {
-        XMLElement rootElem = file->getRootElement();
-        XMLElement childElem = rootElem.getChildElement("element");
-        while (childElem)
-        {
-            if (childElem.getString("type") == typeName)
-                return childElem;
-            childElem = childElem.getNextElement("element");
-        }
-    }
-    
-    return XMLElement();
-}
-
 void UIElement::markDirty()
 {
     mScreenPositionDirty = true;

+ 2 - 5
Engine/UI/UIElement.h

@@ -222,6 +222,8 @@ public:
     void setFocusMode(FocusMode mode);
     //! Set drag and drop flags
     void setDragDropMode(unsigned mode);
+    //! Set style from an XML file. Find the style element by name
+    void setStyle(XMLFile* file, const std::string& typeName, ResourceCache* cache);
     //! Set style from an XML file. Find the style element automatically
     void setStyleAuto(XMLFile* file, ResourceCache* cache);
     //! Set layout
@@ -329,8 +331,6 @@ public:
     UIElement* getParent() const { return mParent; }
     //! Return root element
     UIElement* getRootElement() const;
-    //! Return first matching UI style element from an XML file. If not found, return empty
-    XMLElement getStyleElement(XMLFile* file) const;
     
     //! Convert screen coordinates to element coordinates
     IntVector2 screenToElement(const IntVector2& screenPosition);
@@ -353,9 +353,6 @@ public:
     void getBatchesWithOffset(IntVector2& offset, std::vector<UIBatch>& batches, std::vector<UIQuad>& quads, IntRect
         currentScissor);
     
-    //! Return first matching UI style element from an XML file, with freely specified type. If not found, return empty
-    static XMLElement getStyleElement(XMLFile* file, const std::string& typeName);
-    
 protected:
     //! Mark screen position as needing an update
     void markDirty();

+ 2 - 0
Engine/UI/Window.h

@@ -46,6 +46,8 @@ class Window : public BorderImage
 {
     DEFINE_TYPE(Window);
     
+    using UIElement::setStyle;
+    
 public:
     //! Construct with name
     Window(const std::string& name = std::string());

+ 13 - 14
Examples/Urho3D/Application.cpp

@@ -59,15 +59,15 @@ void Application::run()
     
     // Parse scene or script file name from the command line
     const std::vector<std::string>& arguments = getArguments();
-    std::string fullName, pathName, fileName, extension;
+    std::string fileName;
+    
     if ((arguments.size()) && (arguments[0][0] != '-'))
-        fullName = replace(arguments[0], '\\', '/');
-    if (fullName.empty())
+        fileName = replace(arguments[0], '\\', '/');
+    if (fileName.empty())
         EXCEPTION("Usage: Urho3D <scriptfile | scenefile> [options]\n\n"
             "Either a script file or a scene file can be specified. The script file should implement the function void start(), "
             "which should in turn subscribe to all necessary events, such as the application update. If a scene is loaded, it "
             "should contain script objects to implement the application logic. Refer to the readme for the command line options.");
-    splitPath(fullName, pathName, fileName, extension);
     
     // Instantiate the engine
     mEngine = new Engine();
@@ -76,16 +76,14 @@ void Application::run()
     // Check first the resource cache
     SharedPtr<File> file;
     mCache = mEngine->getResourceCache();
-    if (mCache->exists(fullName))
-        file = mCache->getFile(fullName);
-    // If not found, open using the full filename, and add the path as a resource directory
+    if (mCache->exists(fileName))
+        file = mCache->getFile(fileName);
+    // If not found, open using the full absolute filename, and add the path as a resource directory
     else
     {
-        file = new File(fullName);
-        if (!pathName.empty())
-            mCache->addResourcePath(pathName);
-        else
-            mCache->addResourcePath(getCurrentDirectory());
+        fileName = getAbsoluteFileName(fileName);
+        file = new File(fileName);
+        mCache->addResourcePath(getPath(fileName));
     }
     
     // Initialize engine & scripting
@@ -93,9 +91,10 @@ void Application::run()
     mEngine->createScriptEngine();
     
     // Script mode: execute the rest of initialization, including scene creation, in script
-    if ((extension != ".xml") && (extension != ".scn"))
+    std::string extension = getExtension(fileName);
+    if ((extension != ".xml") && (extension != ".scn") && (extension != ".bin"))
     {
-        mScriptFile = new ScriptFile(mEngine->getScriptEngine(), fileName + extension);
+        mScriptFile = new ScriptFile(mEngine->getScriptEngine(), fileName);
         mScriptFile->load(*file, mCache);
         if (!mScriptFile->execute("void start()"))
             EXCEPTION("Failed to execute the start() function");