Răsfoiți Sursa

Editor resource browser & right click menus

Chris Friesen 11 ani în urmă
părinte
comite
ffc4dffba7

+ 5 - 0
Bin/Data/Materials/Editor/TexturedUnlit.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0"?>
+<material>
+	<technique name="Techniques/DiffUnlitAlpha.xml" quality="0" loddistance="0" />
+	<texture unit="diffuse" name="Textures/LogoLarge.png" />
+</material>

BIN
Bin/Data/Models/Editor/ImagePlane.mdl


+ 2 - 0
Bin/Data/Scripts/Editor.as

@@ -13,6 +13,7 @@
 #include "Scripts/Editor/EditorSecondaryToolbar.as"
 #include "Scripts/Editor/EditorUI.as"
 #include "Scripts/Editor/EditorImport.as"
+#include "Scripts/Editor/EditorResourceBrowser.as"
 #include "Scripts/Editor/EditorSpawn.as"
 
 String configFileName;
@@ -91,6 +92,7 @@ void HandleUpdate(StringHash eventType, VariantMap& eventData)
 {
     float timeStep = eventData["TimeStep"].GetFloat();
 
+    DoResourceBrowserWork();
     UpdateView(timeStep);
     UpdateViewports(timeStep);
     UpdateStats(timeStep);

+ 6 - 0
Bin/Data/Scripts/Editor/AttributeEditor.as

@@ -120,6 +120,7 @@ LineEdit@ CreateAttributeLineEdit(UIElement@ parent, Array<Serializable@>@ seria
 {
     LineEdit@ attrEdit = LineEdit();
     parent.AddChild(attrEdit);
+    attrEdit.dragDropMode = DD_TARGET;
     attrEdit.style = "EditorAttributeEdit";
     attrEdit.SetFixedHeight(ATTR_HEIGHT - 2);
     attrEdit.vars["Index"] = index;
@@ -1063,6 +1064,11 @@ void OpenResource(StringHash eventType, VariantMap& eventData)
     if (fileName.empty)
         return;
 
+    OpenResource(fileName);
+}
+
+void OpenResource(String fileName)
+{
     Array<String>@ resourceDirs = cache.resourceDirs;
     for (uint i = 0; i < resourceDirs.length; ++i)
     {

+ 80 - 1
Bin/Data/Scripts/Editor/EditorActions.as

@@ -121,7 +121,7 @@ class ReparentNodeAction : EditAction
     {
         multiple = true;
         newParentID = newParent.id;
-        for(uint i = 0; i < nodes.length; i++)
+        for(uint i = 0; i < nodes.length; ++i)
         {
             Node@ node = nodes[i];
             nodeList.Push(node.id);
@@ -785,3 +785,82 @@ class EditMaterialAction : EditAction
         }
     }
 }
+
+class AssignMaterialAction : EditAction
+{
+    WeakHandle model;
+    Array<String> oldMaterials;
+    String newMaterialName;
+
+    void Define(StaticModel@ model_, Array<Material@> oldMaterials_, Material@ newMaterial_)
+    {
+        model = model_;
+        for (uint i =0; i < oldMaterials_.length; ++i)
+        {
+            oldMaterials.Push(oldMaterials_[i].name);
+        }
+        newMaterialName = newMaterial_.name;
+    }
+
+    void Undo()
+    {
+        StaticModel@ staticModel = model.Get();
+        if (staticModel is null)
+            return;
+
+        for(uint i=0; i<oldMaterials.length; ++i)
+        {
+            Material@ material = cache.GetResource("Material", oldMaterials[i]);
+            staticModel.materials[i] = material;
+        }
+    }
+
+    void Redo()
+    {
+        StaticModel@ staticModel = model.Get();
+        if (staticModel is null)
+            return;
+
+        Material@ material = cache.GetResource("Material", newMaterialName);
+        staticModel.material = material;
+    }
+}
+
+class AssignModelAction : EditAction
+{
+    WeakHandle staticModel;
+    String oldModel;
+    String newModel;
+
+    void Define(StaticModel@ staticModel_, Model@ oldModel_, Model@ newModel_)
+    {
+        staticModel = staticModel_;
+        oldModel = oldModel_.name;
+        newModel = newModel_.name;
+    }
+
+    void Undo()
+    {
+        StaticModel@ staticModel_ = staticModel.Get();
+        if (staticModel_ is null)
+            return;
+
+        Model@ model = cache.GetResource("Model", oldModel);
+        if (model is null)
+            return;
+        staticModel_.model = model;
+    }
+
+    void Redo()
+    {
+        StaticModel@ staticModel_ = staticModel.Get();
+        if (staticModel_ is null)
+            return;
+
+        Model@ model = cache.GetResource("Model", newModel);
+        if (model is null)
+            return;
+        staticModel_.model = model;
+    }
+
+}

+ 294 - 5
Bin/Data/Scripts/Editor/EditorHierarchyWindow.as

@@ -7,6 +7,8 @@ const int ITEM_UI_ELEMENT = 3;
 const uint NO_ITEM = M_MAX_UNSIGNED;
 const ShortStringHash SCENE_TYPE("Scene");
 const ShortStringHash NODE_TYPE("Node");
+const ShortStringHash STATICMODEL_TYPE("StaticModel");
+const ShortStringHash ANIMATEDMODEL_TYPE("AnimatedModel");
 const ShortStringHash STATICMODELGROUP_TYPE("StaticModelGroup");
 const ShortStringHash SPLINEPATH_TYPE("SplinePath");
 const ShortStringHash CONSTRAINT_TYPE("Constraint");
@@ -57,7 +59,7 @@ void CreateHierarchyWindow()
     hierarchyWindow = ui.LoadLayout(cache.GetResource("XMLFile", "UI/EditorHierarchyWindow.xml"));
     hierarchyList = hierarchyWindow.GetChild("HierarchyList");
     ui.root.AddChild(hierarchyWindow);
-    int height = Min(ui.root.height - 60, 500);
+    int height = Min(ui.root.height - 60, 460);
     hierarchyWindow.SetSize(300, height);
     hierarchyWindow.SetPosition(35, 100);
     hierarchyWindow.opacity = uiMaxOpacity;
@@ -77,6 +79,7 @@ void CreateHierarchyWindow()
     SubscribeToEvent(hierarchyWindow.GetChild("CollapseButton", true), "Released", "ExpandCollapseHierarchy");
     SubscribeToEvent(hierarchyList, "SelectionChanged", "HandleHierarchyListSelectionChange");
     SubscribeToEvent(hierarchyList, "ItemDoubleClicked", "HandleHierarchyListDoubleClick");
+    SubscribeToEvent(hierarchyList, "ItemClicked", "HandleHierarchyItemClick");
     SubscribeToEvent("DragDropTest", "HandleDragDropTest");
     SubscribeToEvent("DragDropFinish", "HandleDragDropFinish");
     SubscribeToEvent(editorScene, "NodeAdded", "HandleNodeAdded");
@@ -743,6 +746,69 @@ void HandleHierarchyListDoubleClick(StringHash eventType, VariantMap& eventData)
     }
 }
 
+void HandleHierarchyItemClick(StringHash eventType, VariantMap& eventData)
+{
+    if (eventData["Button"].GetInt() != MOUSEB_RIGHT)
+        return;
+
+    UIElement@ uiElement = eventData["Item"].GetPtr();
+    int selectionIndex = eventData["Selection"].GetInt();
+
+    Array<UIElement@> actions;
+    int type = uiElement.vars[TYPE_VAR].GetInt();
+
+    // Adds left clicked items to selection which is not normal listview behavior
+    if (type == ITEM_COMPONENT || type == ITEM_NODE)
+    {
+        if (input.keyDown[KEY_LSHIFT])
+            hierarchyList.AddSelection(selectionIndex);
+        else
+        {
+            hierarchyList.ClearSelection();
+            hierarchyList.AddSelection(selectionIndex);
+        }
+    }
+
+    if (type == ITEM_COMPONENT)
+    {
+        Component@ targetComponent = editorScene.GetComponent(uiElement.vars[COMPONENT_ID_VAR].GetUInt());
+        if (targetComponent is null)
+            return;
+
+        actions.Push(CreateContextMenuItem("Copy", "HandleHierarchyContextCopy"));
+        actions.Push(CreateContextMenuItem("Cut", "HandleHierarchyContextCut"));
+        actions.Push(CreateContextMenuItem("Delete", "HandleHierarchyContextDelete"));
+        actions.Push(CreateContextMenuItem("Paste", "HandleHierarchyContextPaste")); 
+        actions.Push(CreateContextMenuItem("Enable/disable", "HandleHierarchyContextEnableDisable"));
+
+        /* actions.Push(CreateBrowserFileActionMenu("Edit", "HandleBrowserEditResource", file)); */
+    }
+    else if (type == ITEM_NODE)
+    {
+        actions.Push(CreateContextMenuItem("Create Replicated Node", "HandleHierarchyContextCreateReplicatedNode"));
+        actions.Push(CreateContextMenuItem("Create Local Node", "HandleHierarchyContextCreateLocalNode"));
+        actions.Push(CreateContextMenuItem("Copy", "HandleHierarchyContextCopy"));
+        actions.Push(CreateContextMenuItem("Cut", "HandleHierarchyContextCut"));
+        actions.Push(CreateContextMenuItem("Delete", "HandleHierarchyContextDelete"));
+        actions.Push(CreateContextMenuItem("Paste", "HandleHierarchyContextPaste")); 
+        actions.Push(CreateContextMenuItem("Reset to default", "HandleHierarchyContextResetToDefault"));
+        actions.Push(CreateContextMenuItem("Reset position", "HandleHierarchyContextResetPosition"));
+        actions.Push(CreateContextMenuItem("Reset rotation", "HandleHierarchyContextResetRotation"));
+        actions.Push(CreateContextMenuItem("Reset scale", "HandleHierarchyContextResetScale"));
+        actions.Push(CreateContextMenuItem("Enable/disable", "HandleHierarchyContextEnableDisable"));
+        actions.Push(CreateContextMenuItem("Unparent", "HandleHierarchyContextUnparent"));
+    }
+    else if (type == ITEM_UI_ELEMENT)
+    {
+        // close ui element
+        actions.Push(CreateContextMenuItem("Close UI-Layout", "HandleHierarchyContextUIElementCloseUILayout"));
+        actions.Push(CreateContextMenuItem("Close all UI-layouts", "HandleHierarchyContextUIElementCloseAllUILayouts"));
+    }
+
+    if (actions.length > 0)
+        ActivateContextMenu(actions);
+}
+
 void HandleDragDropTest(StringHash eventType, VariantMap& eventData)
 {
     UIElement@ source = eventData["Source"].GetPtr();
@@ -761,6 +827,107 @@ void HandleDragDropFinish(StringHash eventType, VariantMap& eventData)
     if (!accept)
         return;
 
+    // resource browser
+    if (source !is null && source.GetVar(TEXT_VAR_RESOURCE_TYPE).GetInt() > 0)
+    {
+        int type = source.GetVar(TEXT_VAR_RESOURCE_TYPE).GetInt();
+
+        BrowserFile@ browserFile = GetBrowserFileFromId(source.vars[TEXT_VAR_FILE_ID].GetUInt());
+        if (browserFile is null)
+            return;
+
+        Component@ createdComponent;
+        if (itemType == ITEM_NODE)
+        {
+            Node@ targetNode = editorScene.GetNode(target.vars[NODE_ID_VAR].GetUInt());
+            if (targetNode is null)
+                return;
+
+            // editNode = targetNode;
+
+            if (type == RESOURCE_TYPE_PREFAB)
+            {
+                LoadNode(browserFile.GetFullPath(), targetNode);
+            }
+            else if(type == RESOURCE_TYPE_SCRIPTFILE)
+            {
+                // TODO: not sure what to do here.  lots of choices.
+            }
+            else if(type == RESOURCE_TYPE_MODEL)
+            {
+                CreateModelWithStaticModel(browserFile.resourceKey, targetNode);
+                return;
+            }
+            else if (type == RESOURCE_TYPE_PARTICLEEMITTER)
+            {
+                if (browserFile.extension == "xml")
+                {
+                    XMLFile@ file = cache.GetResource("XMLFile", browserFile.resourceKey);
+                    if (file is null)
+                        return;
+
+                    ParticleEmitter@ emitter = targetNode.CreateComponent("ParticleEmitter");
+                    emitter.Load(file);
+                    createdComponent = emitter;
+                }
+            }
+            else if (type == RESOURCE_TYPE_2D_PARTICLE_EFFECT)
+            {
+                if (browserFile.extension == "xml")
+                {
+                    ParticleEffect2D@ effect = cache.GetResource("ParticleEffect2D", browserFile.resourceKey);
+                    if (effect is null)
+                        return;
+
+                    ParticleEmitter2D@ emitter = targetNode.CreateComponent("ParticleEmitter2D");
+                    emitter.effect = effect;
+                    createdComponent = emitter;
+                }
+            }
+        }
+        else if (itemType == ITEM_COMPONENT)
+        {
+            Component@ targetComponent = editorScene.GetComponent(target.vars[COMPONENT_ID_VAR].GetUInt());
+
+            if (targetComponent is null)
+                return;
+
+            if (type == RESOURCE_TYPE_MATERIAL)
+            {
+                StaticModel@ model = cast<StaticModel>(targetComponent);
+                if (model is null)
+                    return;
+
+                AssignMaterial(model, browserFile.resourceKey);
+            }
+            else if (type == RESOURCE_TYPE_MODEL)
+            {
+                StaticModel@ staticModel = cast<StaticModel>(targetComponent);
+                if (staticModel is null)
+                    return;
+
+                AssignModel(staticModel, browserFile.resourceKey);
+            }
+        }
+        else
+        {
+            LineEdit@ text = cast<LineEdit>(target);
+            if (text is null)
+                return;
+            text.text = browserFile.resourceKey;
+            VariantMap data();
+            data["Element"] = text;
+            data["Text"] = text.text;
+            text.SendEvent("TextFinished", data);
+        }
+
+        if (createdComponent !is null)
+        {
+            CreateLoadedComponent(createdComponent);
+        }
+        return;
+    }
+
     if (itemType == ITEM_NODE)
     {
         Node@ targetNode = editorScene.GetNode(target.vars[NODE_ID_VAR].GetUInt());
@@ -873,7 +1040,7 @@ Array<Node@> GetMultipleSourceNodes(UIElement@ source)
             return nodeList;
         
         bool sourceIsSelected = false;
-        for (uint i = 0; i < listView_.selectedItems.length; i++)
+        for (uint i = 0; i < listView_.selectedItems.length; ++i)
         {
             if (listView_.selectedItems[i] is source)
             {
@@ -884,7 +1051,7 @@ Array<Node@> GetMultipleSourceNodes(UIElement@ source)
 
         if (sourceIsSelected)
         {
-            for (uint i = 0; i < listView_.selectedItems.length; i++)
+            for (uint i = 0; i < listView_.selectedItems.length; ++i)
             {
                 UIElement@ item_ = listView_.selectedItems[i];
                 // The source item is already added
@@ -929,6 +1096,18 @@ bool TestDragDrop(UIElement@ source, UIElement@ target, int& itemType)
                 return false;
         }
 
+        // Resource browser
+        if (sourceNode is null && targetNode !is null)
+        {
+            itemType = ITEM_NODE;
+            int type = source.GetVar(TEXT_VAR_RESOURCE_TYPE).GetInt();
+            return type == RESOURCE_TYPE_PREFAB || 
+                type == RESOURCE_TYPE_SCRIPTFILE || 
+                type == RESOURCE_TYPE_MODEL || 
+                type == RESOURCE_TYPE_PARTICLEEMITTER ||
+                type == RESOURCE_TYPE_2D_PARTICLE_EFFECT;
+        }
+
         return true;
     }
     else if (targetItemType == ITEM_UI_ELEMENT)
@@ -971,13 +1150,53 @@ bool TestDragDrop(UIElement@ source, UIElement@ target, int& itemType)
         if (sourceNode !is null && targetComponent !is null && (targetComponent.type == STATICMODELGROUP_TYPE ||
             targetComponent.type == CONSTRAINT_TYPE || targetComponent.type == SPLINEPATH_TYPE))
             return true;
-        else
-            return false;
+
+        // resource browser
+        int type = source.GetVar(TEXT_VAR_RESOURCE_TYPE).GetInt();
+        if (targetComponent.type == STATICMODEL_TYPE || targetComponent.type == ANIMATEDMODEL_TYPE)
+            return type == RESOURCE_TYPE_MATERIAL || type == RESOURCE_TYPE_MODEL;
+
+        return false;
     }
+    else if (source.vars.Contains(TEXT_VAR_RESOURCE_TYPE)) // only testing resource browser ui elements
+    {
+        int type = source.GetVar(TEXT_VAR_RESOURCE_TYPE).GetInt();
 
+        // test against resource pickers
+        LineEdit@ lineEdit = cast<LineEdit>(target);
+        if (lineEdit !is null)
+        {
+            ShortStringHash resourceType = GetResourceTypeFromPickerLineEdit(lineEdit);
+            if (resourceType == ShortStringHash("Material") && type == RESOURCE_TYPE_MATERIAL)
+                return true;
+            else if (resourceType == ShortStringHash("Model") && type == RESOURCE_TYPE_MODEL)
+                return true;
+            else if (resourceType == ShortStringHash("Animation") && type == RESOURCE_TYPE_ANIMATION)
+                return true;
+        }
+    }
     return true;
 }
 
+ShortStringHash GetResourceTypeFromPickerLineEdit(UIElement@ lineEdit)
+{
+    Array<Serializable@>@ targets = GetAttributeEditorTargets(lineEdit);
+    if (!targets.empty)
+    {
+        resourcePickIndex = lineEdit.vars["Index"].GetUInt();
+        resourcePickSubIndex = lineEdit.vars["SubIndex"].GetUInt();
+        AttributeInfo info = targets[0].attributeInfos[resourcePickIndex];
+        ShortStringHash resourceType;
+        if (info.type == VAR_RESOURCEREF)
+            return targets[0].attributes[resourcePickIndex].GetResourceRef().type;
+        else if (info.type == VAR_RESOURCEREFLIST)
+            return targets[0].attributes[resourcePickIndex].GetResourceRefList().type;
+        else if (info.type == VAR_VARIANTVECTOR)
+            return targets[0].attributes[resourcePickIndex].GetVariantVector()[resourcePickSubIndex].GetResourceRef().type;
+    }
+    return ShortStringHash();
+}
+
 void FocusNode(Node@ node)
 {
     uint index = GetListIndex(node);
@@ -1385,3 +1604,73 @@ void EndSelectionModify()
     inSelectionModify = false;
     HandleHierarchyListSelectionChange();
 }
+
+void HandleHierarchyContextCreateReplicatedNode()
+{
+    CreateNode(REPLICATED);
+}
+
+void HandleHierarchyContextCreateLocalNode()
+{
+    CreateNode(LOCAL);
+}
+
+void HandleHierarchyContextCopy()
+{
+    Copy();
+}
+
+void HandleHierarchyContextCut()
+{
+    Cut();
+}
+
+void HandleHierarchyContextDelete()
+{
+    Delete();
+}
+
+void HandleHierarchyContextPaste()
+{
+    Paste();
+}
+
+void HandleHierarchyContextResetToDefault()
+{
+    ResetToDefault();
+}
+
+void HandleHierarchyContextResetPosition()
+{
+    SceneResetPosition();
+}
+
+void HandleHierarchyContextResetRotation()
+{
+    SceneResetRotation();
+}
+
+void HandleHierarchyContextResetScale()
+{
+    SceneResetScale();
+}
+
+void HandleHierarchyContextEnableDisable()
+{
+    SceneToggleEnable();
+}
+
+void HandleHierarchyContextUnparent()
+{
+    SceneUnparent();
+}
+
+void HandleHierarchyContextUIElementCloseUILayout()
+{
+    CloseUILayout();
+}
+
+void HandleHierarchyContextUIElementCloseAllUILayouts()
+{
+    CloseAllUILayouts();
+}

+ 4 - 1
Bin/Data/Scripts/Editor/EditorMaterial.as

@@ -577,6 +577,9 @@ void PickMaterialTextureDone(StringHash eventType, VariantMap& eventData)
     String resourceName = eventData["FileName"].GetString();
     Resource@ res = GetPickedResource(resourceName);
 
+    Print("INDEX");
+    Print(resourcePickIndex);
+    
     if (res !is null && editMaterial !is null)
     {
         BeginMaterialEdit();
@@ -597,7 +600,7 @@ void EditMaterialTexture(StringHash eventType, VariantMap& eventData)
     LineEdit@ attrEdit = eventData["Element"].GetPtr();
     String textureName = attrEdit.text.Trimmed();
     uint index = attrEdit.vars["Index"].GetUInt();
-    
+
     BeginMaterialEdit();
 
     if (!textureName.empty)

+ 1409 - 0
Bin/Data/Scripts/Editor/EditorResourceBrowser.as

@@ -0,0 +1,1409 @@
+UIElement@ browserWindow;
+Window@ browserFilterWindow;
+ListView@ browserDirList;
+ListView@ browserFileList;
+LineEdit@ browserSearch;
+BrowserFile@ browserDragFile;
+Node@ browserDragNode;
+Component@ browserDragComponent;
+View3D@ resourceBrowserPreview;
+Scene@ resourcePreviewScene;
+Node@ resourcePreviewNode;
+Node@ resourcePreviewCameraNode;
+Node@ resourcePreviewLightNode;
+Light@ resourcePreviewLight;
+int browserSearchSortMode = 0;
+
+BrowserDir@ rootDir;
+Array<BrowserFile@> browserFiles;
+Dictionary browserDirs;
+Array<int> activeResourceFilters;
+
+Array<BrowserFile@> browserFilesToScan;
+const uint BROWSER_WORKER_ITEMS_PER_TICK = 10;
+const uint BROWSER_SEARCH_LIMIT = 50;
+const int BROWSER_SORT_MODE_ALPHA = 1;
+const int BROWSER_SORT_MODE_SEARCH = 2;
+
+const int RESOURCE_TYPE_UNUSABLE = -2;
+const int RESOURCE_TYPE_UNKNOWN = -1;
+const int RESOURCE_TYPE_NOTSET = 0;
+const int RESOURCE_TYPE_SCENE = 1;
+const int RESOURCE_TYPE_SCRIPTFILE = 2;
+const int RESOURCE_TYPE_MODEL = 3;
+const int RESOURCE_TYPE_MATERIAL = 4;
+const int RESOURCE_TYPE_ANIMATION = 5;
+const int RESOURCE_TYPE_IMAGE = 6;
+const int RESOURCE_TYPE_SOUND = 7;
+const int RESOURCE_TYPE_TEXTURE = 8;
+const int RESOURCE_TYPE_FONT = 9;
+const int RESOURCE_TYPE_PREFAB = 10;
+const int RESOURCE_TYPE_TECHNIQUE = 11;
+const int RESOURCE_TYPE_PARTICLEEMITTER = 12;
+const int RESOURCE_TYPE_UIELEMENT = 13;
+const int RESOURCE_TYPE_UIELEMENTS = 14;
+const int RESOURCE_TYPE_ANIMATION_SETTINGS = 15;
+const int RESOURCE_TYPE_RENDERPATH = 16;
+const int RESOURCE_TYPE_TEXTURE_ATLAS = 17;
+const int RESOURCE_TYPE_2D_PARTICLE_EFFECT = 18;
+const int RESOURCE_TYPE_TEXTURE_3D = 19;
+const int RESOURCE_TYPE_CUBEMAP = 20;
+
+const ShortStringHash XML_TYPE_SCENE("scene");
+const ShortStringHash XML_TYPE_NODE("node");
+const ShortStringHash XML_TYPE_MATERIAL("material");
+const ShortStringHash XML_TYPE_TECHNIQUE("technique");
+const ShortStringHash XML_TYPE_PARTICLEEMITTER("particleemitter");
+const ShortStringHash XML_TYPE_TEXTURE("texture");
+const ShortStringHash XML_TYPE_ELEMENT("element");
+const ShortStringHash XML_TYPE_ELEMENTS("elements");
+const ShortStringHash XML_TYPE_ANIMATION_SETTINGS("animation");
+const ShortStringHash XML_TYPE_RENDERPATH("renderpath");
+const ShortStringHash XML_TYPE_TEXTURE_ATLAS("TextureAtlas");
+const ShortStringHash XML_TYPE_2D_PARTICLE_EFFECT("particleEmitterConfig");
+const ShortStringHash XML_TYPE_TEXTURE_3D("texture3d");
+const ShortStringHash XML_TYPE_CUBEMAP("cubemap");
+
+const ShortStringHash BINARY_TYPE_SCENE("USCN");
+const ShortStringHash BINARY_TYPE_PACKAGE("UPAK");
+const ShortStringHash BINARY_TYPE_COMPRESSED_PACKAGE("ULZ4");
+const ShortStringHash BINARY_TYPE_ANGLESCRIPT("ASBC");
+const ShortStringHash BINARY_TYPE_MODEL("UMDL");
+const ShortStringHash BINARY_TYPE_SHADER("USHD");
+const ShortStringHash BINARY_TYPE_ANIMATION("UANI");
+
+const ShortStringHash EXTENSION_TYPE_TTF(".ttf");
+const ShortStringHash EXTENSION_TYPE_OGG(".ogg");
+const ShortStringHash EXTENSION_TYPE_WAV(".wav");
+const ShortStringHash EXTENSION_TYPE_DDS(".dds");
+const ShortStringHash EXTENSION_TYPE_PNG(".png");
+const ShortStringHash EXTENSION_TYPE_JPG(".jpg");
+const ShortStringHash EXTENSION_TYPE_JPEG(".jpeg");
+const ShortStringHash EXTENSION_TYPE_TGA(".tga");
+const ShortStringHash EXTENSION_TYPE_OBJ(".obj");
+const ShortStringHash EXTENSION_TYPE_FBX(".fbx");
+const ShortStringHash EXTENSION_TYPE_COLLADA(".dae");
+const ShortStringHash EXTENSION_TYPE_BLEND(".blend");
+const ShortStringHash EXTENSION_TYPE_ANGELSCRIPT(".as");
+const ShortStringHash EXTENSION_TYPE_LUASCRIPT(".lua");
+const ShortStringHash EXTENSION_TYPE_HLSL(".hlsl");
+const ShortStringHash EXTENSION_TYPE_GLSL(".glsl");
+const ShortStringHash EXTENSION_TYPE_FRAGMENTSHADER(".frag");
+const ShortStringHash EXTENSION_TYPE_VERTEXSHADER(".vert");
+const ShortStringHash EXTENSION_TYPE_HTML(".html");
+
+const ShortStringHash TEXT_VAR_FILE_ID("browser_file_id");
+const ShortStringHash TEXT_VAR_DIR_ID("browser_dir_id");
+const ShortStringHash TEXT_VAR_RESOURCE_TYPE("resource_type");
+
+const int BROWSER_FILE_SOURCE_RESOURCE_DIR = 1;
+
+uint browserDirIndex = 1;
+uint browserFileIndex = 1;
+BrowserDir@ selectedBrowserDirectory;
+BrowserFile@ selectedBrowserFile;
+Text@ browserStatusMessage;
+Text@ browserResultsMessage;
+bool ignoreRefreshBrowserResults = false;
+
+void CreateResourceBrowser()
+{
+    if (browserWindow !is null) return;
+
+    CreateResourceBrowserUI();
+    InitResourceBrowserPreview();
+    ScanResourceDirectories();
+    PopulateBrowserDirectories();
+    PopulateResourceBrowserFilesByDirectory(rootDir);
+}
+
+void ScanResourceDirectories()
+{
+    browserDirs.Clear();
+    browserFiles.Clear();
+    browserFilesToScan.Clear();
+
+    rootDir = BrowserDir("");
+    browserDirs.Set("", @rootDir);
+
+    // collect all of the items and sort them afterwards
+    for(uint i=0; i < cache.resourceDirs.length; ++i)
+        ScanResourceDir(i);
+}
+
+// used to stop ui from blocking while determining file types
+void DoResourceBrowserWork()
+{
+    if (browserFilesToScan.length == 0)
+        return;
+
+    int counter = 0;
+    bool updateBrowserUI = false;
+    BrowserFile@ scanItem = browserFilesToScan[0];
+    while(counter < BROWSER_WORKER_ITEMS_PER_TICK)
+    {
+        scanItem.DetermainResourceType();
+
+        // next
+        browserFilesToScan.Erase(0);
+        if (browserFilesToScan.length > 0)
+            @scanItem = browserFilesToScan[0];
+        else
+            break;
+        counter++;
+    }
+
+    if (browserFilesToScan.length > 0)
+        browserStatusMessage.text = "Files left to scan: " + browserFilesToScan.length;
+    else
+        browserStatusMessage.text = "Scan complete";
+
+}
+
+void CreateResourceBrowserUI()
+{
+    browserWindow = ui.LoadLayout(cache.GetResource("XMLFile", "UI/EditorResourceBrowser.xml"));
+    browserDirList = browserWindow.GetChild("DirectoryList", true);
+    browserFileList = browserWindow.GetChild("FileList", true);
+    browserSearch = browserWindow.GetChild("Search", true);
+    browserStatusMessage = browserWindow.GetChild("StatusMessage", true);
+    browserResultsMessage = browserWindow.GetChild("ResultsMessage", true);
+    // browserWindow.visible = false;
+    browserWindow.opacity = uiMaxOpacity;
+
+    browserFilterWindow = ui.LoadLayout(cache.GetResource("XMLFile", "UI/EditorResourceFilterWindow.xml"));
+    {
+        UIElement@ options = browserFilterWindow.GetChild("Options", true);
+        CheckBox@ toggleAll = browserFilterWindow.GetChild("ToggleAll", true);
+        SubscribeToEvent(toggleAll, "Toggled", "HandleResourceFilterToggleAllToggled");
+        SubscribeToEvent(browserFilterWindow.GetChild("CloseButton", true), "Released", "HideResourceFilterWindow");
+
+        UIElement@ col1 = browserFilterWindow.GetChild("FilterColumn1", true);
+        UIElement@ col2 = browserFilterWindow.GetChild("FilterColumn2", true);
+        for (int i=-2; i < 21; ++i)
+        {
+            if (i == RESOURCE_TYPE_NOTSET)
+                continue;
+
+            UIElement@ resourceTypeHolder = UIElement();
+            if (i < 10)
+                col1.AddChild(resourceTypeHolder);
+            else
+                col2.AddChild(resourceTypeHolder);
+            resourceTypeHolder.layoutMode = LM_HORIZONTAL;
+            resourceTypeHolder.layoutSpacing = 4;
+
+            Text@ label = Text();
+            label.style = "FileSelectorListText";
+            label.text = ResourceTypeName(i);
+            CheckBox@ checkbox = CheckBox();
+            checkbox.name = i;
+            checkbox.SetStyleAuto();
+            checkbox.vars[TEXT_VAR_RESOURCE_TYPE] = i;
+            checkbox.checked = true;
+            SubscribeToEvent(checkbox, "Toggled", "HandleResourceFilterToggled");

+
+            resourceTypeHolder.AddChild(checkbox);
+            resourceTypeHolder.AddChild(label);
+        }
+    }
+    HideResourceFilterWindow();
+
+    int height = Min(ui.root.height * .25, 300);
+    browserWindow.SetSize(900, height);
+    browserWindow.SetPosition(35, ui.root.height - height - 25);
+
+    CloseContextMenu();
+    ui.root.AddChild(browserWindow);
+    ui.root.AddChild(browserFilterWindow);
+
+    SubscribeToEvent(browserWindow.GetChild("CloseButton", true), "Released", "HideResourceBrowserWindow");
+    SubscribeToEvent(browserWindow.GetChild("RescanButton", true), "Released", "HandleRescanResourceBrowserClick");
+    SubscribeToEvent(browserWindow.GetChild("FilterButton", true), "Released", "ToggleResourceFilterWindow");
+    SubscribeToEvent(browserDirList, "SelectionChanged", "HandleResourceBrowserDirListSelectionChange");
+    SubscribeToEvent(browserSearch, "TextChanged", "HandleResourceBrowserSearchTextChange");
+    SubscribeToEvent(browserFileList, "ItemClicked", "HandleBrowserFileClick");
+    SubscribeToEvent(browserFileList, "SelectionChanged", "HandleResourceBrowserFileListSelectionChange");
+    SubscribeToEvent(cache, "FileChanged", "HandleFileChanged");
+}
+
+void CreateDirList(BrowserDir@ dir, UIElement@ parentUI = null)
+{
+    Text@ dirText = Text();
+    browserDirList.InsertItem(browserDirList.numItems, dirText, parentUI);
+    dirText.style = "FileSelectorListText";
+    dirText.text = dir.resourceKey.empty ? "Root" : dir.name;
+    dirText.name = dir.resourceKey;
+    dirText.vars[TEXT_VAR_DIR_ID] = dir.resourceKey;
+
+    // Sort directories alphetically
+    browserSearchSortMode = BROWSER_SORT_MODE_ALPHA;
+    dir.children.Sort();
+
+    for(uint i=0; i<dir.children.length; ++i)
+        CreateDirList(dir.children[i], dirText);
+}
+
+void CreateFileList(BrowserFile@ file)
+{
+    Text@ fileText = Text();
+    fileText.style = "FileSelectorListText";
+    fileText.layoutMode = LM_HORIZONTAL;
+    browserFileList.InsertItem(browserFileList.numItems, fileText);
+    file.browserFileListRow = fileText;
+    InitializeBrowserFileListRow(fileText, file);
+}
+
+void InitializeBrowserFileListRow(Text@ fileText, BrowserFile@ file)
+{
+    fileText.RemoveAllChildren();
+    VariantMap params = VariantMap();
+    fileText.vars[TEXT_VAR_FILE_ID] = file.id;
+    fileText.vars[TEXT_VAR_RESOURCE_TYPE] = file.resourceType;
+    if (file.resourceType > 0)
+        fileText.dragDropMode = DD_SOURCE;
+
+    {
+        Text@ text = Text();
+        fileText.AddChild(text);
+        text.style = "FileSelectorListText";
+        text.text = file.fullname;
+        text.name = file.resourceKey;
+    }
+
+    {
+        Text@ text = Text();
+        fileText.AddChild(text);
+        text.style = "FileSelectorListText";
+        text.text = file.ResourceTypeName();
+    }
+
+    if (file.resourceType == RESOURCE_TYPE_MATERIAL || 
+            file.resourceType == RESOURCE_TYPE_MODEL ||
+            file.resourceType == RESOURCE_TYPE_PARTICLEEMITTER ||
+            file.resourceType == RESOURCE_TYPE_PREFAB
+        )
+    {
+        SubscribeToEvent(fileText, "DragBegin", "HandleBrowserFileDragBegin");
+        SubscribeToEvent(fileText, "DragEnd", "HandleBrowserFileDragEnd");
+    }
+
+}
+
+void InitResourceBrowserPreview()
+{
+    resourcePreviewScene = Scene("PreviewScene");
+    resourcePreviewScene.CreateComponent("Octree");
+    PhysicsWorld@ physicsWorld = resourcePreviewScene.CreateComponent("PhysicsWorld");
+    physicsWorld.enabled = false;
+    physicsWorld.gravity = Vector3(0.0, 0.0, 0.0);
+
+    Node@ zoneNode = resourcePreviewScene.CreateChild("Zone");
+    Zone@ zone = zoneNode.CreateComponent("Zone");
+    zone.boundingBox = BoundingBox(-1000, 1000);
+    zone.ambientColor = Color(0.15, 0.15, 0.15);
+    zone.fogColor = Color(0, 0, 0);
+    zone.fogStart = 10.0;
+    zone.fogEnd = 100.0;
+
+    resourcePreviewCameraNode = resourcePreviewScene.CreateChild("PreviewCamera");
+    resourcePreviewCameraNode.position = Vector3(0, 0, -1.5);
+    Camera@ camera = resourcePreviewCameraNode.CreateComponent("Camera");
+    camera.nearClip = 0.1f;
+    camera.farClip = 100.0f;
+
+    resourcePreviewLightNode = resourcePreviewScene.CreateChild("PreviewLight");
+    resourcePreviewLightNode.direction = Vector3(0.5, -0.5, 0.5);
+    resourcePreviewLight = resourcePreviewLightNode.CreateComponent("Light");
+    resourcePreviewLight.lightType = LIGHT_DIRECTIONAL;
+    resourcePreviewLight.specularIntensity = 0.5;
+
+    resourceBrowserPreview = browserWindow.GetChild("ResourceBrowserPreview", true);
+    resourceBrowserPreview.SetFixedHeight(200);
+    resourceBrowserPreview.SetFixedWidth(266);
+    resourceBrowserPreview.SetView(resourcePreviewScene, camera);
+    resourceBrowserPreview.autoUpdate = false;
+
+    resourcePreviewNode = resourcePreviewScene.CreateChild("PreviewNodeContainer");
+
+    SubscribeToEvent(resourceBrowserPreview, "DragMove", "RotateResourceBrowserPreview");
+
+    RefreshBrowserPreview();
+}
+
+// Opens a contextual menu based on what resource item was actioned
+void HandleBrowserFileClick(StringHash eventType, VariantMap& eventData)
+{
+    if (eventData["Button"].GetInt() != MOUSEB_RIGHT)
+        return;
+
+    UIElement@ uiElement = eventData["Item"].GetPtr();
+    BrowserFile@ file = GetBrowserFileFromUIElement(uiElement);
+
+    if (file is null)
+        return;
+
+    Array<UIElement@> actions;
+    if (file.resourceType == RESOURCE_TYPE_MATERIAL)
+    {
+        actions.Push(CreateBrowserFileActionMenu("Edit", "HandleBrowserEditResource", file));
+    }
+    else if (file.resourceType == RESOURCE_TYPE_MODEL)
+    {
+        actions.Push(CreateBrowserFileActionMenu("Instance Animated Model", "HandleBrowserInstantiateAnimatedModel", file));
+        actions.Push(CreateBrowserFileActionMenu("Instance Static Model", "HandleBrowserInstantiateStaticModel", file));
+    }
+    else if (file.resourceType == RESOURCE_TYPE_PREFAB)
+    {
+        actions.Push(CreateBrowserFileActionMenu("Instance Prefab", "HandleBrowserInstantiatePrefab", file));
+        actions.Push(CreateBrowserFileActionMenu("Instance in Spawner", "HandleBrowserInstantiateInSpawnEditor", file));
+    }
+    else if (file.fileType == EXTENSION_TYPE_OBJ ||
+        file.fileType == EXTENSION_TYPE_COLLADA ||
+        file.fileType == EXTENSION_TYPE_FBX ||
+        file.fileType == EXTENSION_TYPE_BLEND)
+    {
+        actions.Push(CreateBrowserFileActionMenu("Import Model", "HandleBrowserImportModel", file));
+        actions.Push(CreateBrowserFileActionMenu("Import Scene", "HandleBrowserImportScene", file));
+    }
+    else if (file.resourceType == RESOURCE_TYPE_UIELEMENT)
+    {
+        actions.Push(CreateBrowserFileActionMenu("Open UI Layout", "HandleBrowserOpenUILayout", file));
+    }
+    else if (file.resourceType == RESOURCE_TYPE_SCENE)
+    {
+        actions.Push(CreateBrowserFileActionMenu("Load Scene", "HandleBrowserLoadScene", file));
+    }
+    else if (file.resourceType == RESOURCE_TYPE_SCRIPTFILE)
+    {
+        actions.Push(CreateBrowserFileActionMenu("Execute Script", "HandleBrowserRunScript", file));
+    }
+
+    actions.Push(CreateBrowserFileActionMenu("Open", "HandleBrowserOpenResource", file));
+
+    ActivateContextMenu(actions);
+}
+
+BrowserDir@ GetBrowserDir(String path)
+{
+    BrowserDir@ browserDir;
+    browserDirs.Get(path, @browserDir);
+    return browserDir;
+}
+
+// Makes sure the entire directory tree exists and new dir is linked to parent
+BrowserDir@ InitBrowserDir(String path)
+{
+    BrowserDir@ browserDir;
+    if (browserDirs.Get(path, @browserDir))
+        return browserDir;
+
+    Array<String> parts = path.Split('/');
+    Array<String> finishedParts;
+    if (parts.length > 0)
+    {
+        BrowserDir@ parent = rootDir;
+        for( uint i = 0; i < parts.length; ++i )
+        {
+            finishedParts.Push(parts[i]);
+            String currentPath = Join(finishedParts, "/");
+            if (!browserDirs.Get(currentPath, @browserDir))
+            {
+                browserDir = BrowserDir(currentPath);
+                browserDirs.Set(currentPath, @browserDir);
+                parent.children.Push(browserDir);
+            }
+            @parent = browserDir;
+        } 
+        return browserDir;
+    }
+    return null;
+}
+
+void ScanResourceDir(uint resourceDirIndex)
+{
+    String resourceDir = cache.resourceDirs[resourceDirIndex];
+    ScanResourceDirFiles("", resourceDirIndex);
+    Array<String> dirs = fileSystem.ScanDir(resourceDir, "*", SCAN_DIRS, true);
+    for (uint i=0; i < dirs.length; ++i)
+    {
+        String path = dirs[i];
+        if (path.EndsWith("."))
+            continue;
+
+        InitBrowserDir(path);
+        ScanResourceDirFiles(path, resourceDirIndex);
+    }
+}
+
+void ScanResourceDirFiles(String path, uint resourceDirIndex)
+{
+    String fullPath = cache.resourceDirs[resourceDirIndex] + path;
+    if (!fileSystem.DirExists(fullPath))
+        return;
+
+    BrowserDir@ dir = GetBrowserDir(path);
+
+    if (dir is null)
+        return;
+
+    // get files in directory
+    Array<String> dirFiles = fileSystem.ScanDir(fullPath, "*.*", SCAN_FILES, false);
+
+    // add new files
+    for (uint x=0; x < dirFiles.length; x++)
+    {
+        String filename = dirFiles[x];
+        BrowserFile@ browserFile = dir.AddFile(filename, resourceDirIndex, BROWSER_FILE_SOURCE_RESOURCE_DIR);
+        browserFiles.Push(browserFile);
+        browserFilesToScan.Push(browserFile);
+    }
+}
+
+void HideResourceBrowserWindow()
+{
+    browserWindow.visible = false;
+}
+
+bool ShowResourceBrowserWindow()
+{
+    browserWindow.visible = true;
+    browserWindow.BringToFront();
+    ui.focusElement = browserSearch;
+    return true;
+}
+
+void ToggleResourceFilterWindow()
+{
+    if (browserFilterWindow.visible)
+        HideResourceFilterWindow();
+    else
+        ShowResourceFilterWindow();
+}
+void HideResourceFilterWindow()
+{
+    browserFilterWindow.visible = false;
+}
+
+void ShowResourceFilterWindow()
+{
+    int x = browserWindow.position.x + browserWindow.width - browserFilterWindow.width;
+    int y = browserWindow.position.y - browserFilterWindow.height - 1;
+    browserFilterWindow.position = IntVector2(x,y);
+    browserFilterWindow.visible = true;
+    browserFilterWindow.BringToFront();
+}
+
+void PopulateBrowserDirectories()
+{
+    browserDirList.RemoveAllItems();
+    CreateDirList(rootDir);
+    browserDirList.selection = 0;
+}
+
+void PopulateResourceBrowserFilesByDirectory(BrowserDir@ dir)
+{
+    @selectedBrowserDirectory = dir;
+    browserFileList.RemoveAllItems();
+    if (dir is null) return;
+
+    Array<BrowserFile@> files;
+    for(uint x=0; x < dir.files.length; x++)
+    {
+        BrowserFile@ file = dir.files[x];
+        int find = activeResourceFilters.Find(file.resourceType);
+        if (find == -1)
+            files.Push(file);
+    }
+
+    // Sort alphetically
+    browserSearchSortMode = BROWSER_SORT_MODE_ALPHA;
+    files.Sort();
+    PopulateResourceBrowserResults(files);
+    browserResultsMessage.text = "Showing " + files.length + " files";
+}
+
+
+void PopulateResourceBrowserBySearch()
+{
+    String query = browserSearch.text;
+
+    Array<int> scores;
+    Array<BrowserFile@> scored;
+    Array<BrowserFile@> filtered;
+    {
+        BrowserFile@ file;
+        for(uint x=0; x < browserFiles.length; x++)
+        {
+            @file = browserFiles[x];
+            file.sortScore = -1;
+            if (activeResourceFilters.Find(file.resourceType) > -1)
+                continue;
+
+            int find = file.fullname.Find(query, 0, false);
+            if (find > -1)
+            {
+                int fudge = query.length - file.fullname.length;
+                int score = find * Abs(fudge*2) + Abs(fudge);
+                file.sortScore = score;
+                scored.Push(file);
+                scores.Push(score);
+            }
+        }
+    }
+
+    // cut this down for a faster sort
+    if (scored.length > BROWSER_SEARCH_LIMIT)
+    {
+        scores.Sort();
+        int scoreThreshold = scores[BROWSER_SEARCH_LIMIT];
+        BrowserFile@ file;
+        for(uint x=0;x<scored.length;x++)
+        {
+            file = scored[x];
+            if (file.sortScore <= scoreThreshold)
+                filtered.Push(file);
+        }
+    }
+    else
+        filtered = scored;
+
+    browserSearchSortMode = BROWSER_SORT_MODE_ALPHA;
+    filtered.Sort();
+    PopulateResourceBrowserResults(filtered);
+    browserResultsMessage.text = "Showing top " + filtered.length + " of " + scored.length + " results";
+}
+
+void PopulateResourceBrowserResults(Array<BrowserFile@>@ files)
+{
+    browserFileList.RemoveAllItems();
+    for(uint i=0; i < files.length; ++i)
+        CreateFileList(files[i]);
+}
+
+void RefreshBrowserResults()
+{
+    if (browserSearch.text.empty)
+    {
+        browserDirList.visible = true;
+        PopulateResourceBrowserFilesByDirectory(selectedBrowserDirectory);
+    }
+    else
+    {
+        browserDirList.visible = false;
+        PopulateResourceBrowserBySearch();
+    }
+}
+
+void HandleResourceFilterToggleAllToggled(StringHash eventType, VariantMap& eventData)
+{
+    CheckBox@ checkbox = eventData["Element"].GetPtr();
+    UIElement@ filterHolder = browserFilterWindow.GetChild("Filters", true);
+    Array<UIElement@> children = filterHolder.GetChildren(true);
+
+    ignoreRefreshBrowserResults = true;
+    for(uint i=0; i < children.length; ++i)
+    {
+        CheckBox@ filter = children[i];
+        if (filter !is null)
+            filter.checked = checkbox.checked;
+    }
+    ignoreRefreshBrowserResults = false;
+    RefreshBrowserResults();
+}
+
+void HandleResourceFilterToggled(StringHash eventType, VariantMap& eventData)
+{
+    CheckBox@ checkbox = eventData["Element"].GetPtr();
+    if (!checkbox.vars.Contains(TEXT_VAR_RESOURCE_TYPE))
+        return;
+
+    int resourceType = checkbox.GetVar(TEXT_VAR_RESOURCE_TYPE).GetInt();
+    int find = activeResourceFilters.Find(resourceType);
+
+    if (checkbox.checked && find != -1)
+        activeResourceFilters.Erase(find);
+    else if (!checkbox.checked && find == -1)
+        activeResourceFilters.Push(resourceType);
+
+    if (ignoreRefreshBrowserResults == false)
+        RefreshBrowserResults();
+}
+
+
+void HandleRescanResourceBrowserClick(StringHash eventType, VariantMap& eventData)
+{
+    ScanResourceDirectories();
+    PopulateBrowserDirectories();
+    PopulateResourceBrowserFilesByDirectory(rootDir);
+}
+
+void HandleResourceBrowserDirListSelectionChange(StringHash eventType, VariantMap& eventData)
+{
+    if (browserDirList.selection == M_MAX_UNSIGNED)
+        return;
+
+    UIElement@ uiElement = browserDirList.GetItems()[browserDirList.selection];
+    BrowserDir@ dir = GetBrowserDir(uiElement.vars[TEXT_VAR_DIR_ID].GetString());
+    if (dir is null)
+        return;
+
+    PopulateResourceBrowserFilesByDirectory(dir);
+}
+
+void HandleResourceBrowserFileListSelectionChange(StringHash eventType, VariantMap& eventData)
+{
+    if (browserFileList.selection == M_MAX_UNSIGNED)
+        return;
+
+    UIElement@ uiElement = browserFileList.GetItems()[browserFileList.selection];
+    BrowserFile@ file = GetBrowserFileFromUIElement(uiElement);
+    if (file is null)
+        return;
+
+    if (resourcePreviewNode !is null)
+        resourcePreviewNode.Remove();
+
+    resourcePreviewNode = resourcePreviewScene.CreateChild("PreviewNodeContainer");
+    CreateResourcePreview(file.GetFullPath(), resourcePreviewNode);
+
+    if (resourcePreviewNode !is null)
+    {
+        Array<BoundingBox> boxes;
+        Array<Component@> staticModels = resourcePreviewNode.GetComponents("StaticModel", true);
+        Array<Component@> animatedModels = resourcePreviewNode.GetComponents("AnimatedModel", true);
+
+        for (uint i = 0; i < staticModels.length; ++i)
+            boxes.Push(cast<StaticModel>(staticModels[i]).worldBoundingBox);
+
+        for (uint i = 0; i < animatedModels.length; ++i)
+            boxes.Push(cast<AnimatedModel>(animatedModels[i]).worldBoundingBox);
+
+        if (boxes.length > 0)
+        {
+            Vector3 camPosition = Vector3(0.0, 0.0, -1.2);
+            BoundingBox biggestBox = boxes[0];
+            for (uint i = 1; i < boxes.length; ++i)
+            {
+                if (boxes[i].size.length > biggestBox.size.length)
+                    biggestBox = boxes[i];
+            }
+            resourcePreviewCameraNode.position = biggestBox.center + camPosition * biggestBox.size.length;
+        }
+
+        resourcePreviewScene.AddChild(resourcePreviewNode);
+        RefreshBrowserPreview();
+    }
+}
+
+void HandleResourceBrowserSearchTextChange(StringHash eventType, VariantMap& eventData)
+{
+    RefreshBrowserResults();
+}
+
+BrowserFile@ GetBrowserFileFromId(uint id)
+{
+    if (id == 0)
+        return null;
+
+    BrowserFile@ file;
+    for(uint i=0; i<browserFiles.length; ++i)
+    {
+        @file = @browserFiles[i];
+        if (file.id == id) return file;
+    }
+    return null;
+}
+
+BrowserFile@ GetBrowserFileFromUIElement(UIElement@ element)
+{
+    if (element is null || !element.vars.Contains(TEXT_VAR_FILE_ID))
+        return null;
+    return GetBrowserFileFromId(element.vars[TEXT_VAR_FILE_ID].GetUInt());
+}
+
+BrowserFile@ GetBrowserFileFromPath(String path)
+{
+    for (uint i=0; i < browserFiles.length; ++i)
+    {
+        BrowserFile@ file = browserFiles[i];
+        if (path == file.GetFullPath())
+            return file;
+    }
+    return null;
+}
+
+void HandleBrowserEditResource(StringHash eventType, VariantMap& eventData)
+{
+    UIElement@ element = eventData["Element"].GetPtr();
+    BrowserFile@ file = GetBrowserFileFromUIElement(element);
+    if (file is null)
+        return;
+
+    if (file.resourceType == RESOURCE_TYPE_MATERIAL)
+    {
+        Material@ material = cache.GetResource("Material", file.resourceKey);
+        if (material !is null)
+            EditMaterial(material);
+    }
+}
+
+void HandleBrowserOpenResource(StringHash eventType, VariantMap& eventData)
+{
+    UIElement@ element = eventData["Element"].GetPtr();
+    BrowserFile@ file = GetBrowserFileFromUIElement(element);
+    if (file !is null)
+        OpenResource(file.resourceKey);
+}
+
+void HandleBrowserImportScene(StringHash eventType, VariantMap& eventData)
+{
+    UIElement@ element = eventData["Element"].GetPtr();
+    BrowserFile@ file = GetBrowserFileFromUIElement(element);
+    if (file !is null)
+        ImportScene(file.GetFullPath());
+}
+
+void HandleBrowserImportModel(StringHash eventType, VariantMap& eventData)
+{
+    UIElement@ element = eventData["Element"].GetPtr();
+    BrowserFile@ file = GetBrowserFileFromUIElement(element);
+    if (file !is null)
+        ImportModel(file.GetFullPath());
+}
+
+void HandleBrowserOpenUILayout(StringHash eventType, VariantMap& eventData)
+{
+    UIElement@ element = eventData["Element"].GetPtr();
+    BrowserFile@ file = GetBrowserFileFromUIElement(element);
+    if (file !is null)
+        OpenUILayout(file.GetFullPath());
+}
+
+void HandleBrowserInstantiateStaticModel(StringHash eventType, VariantMap& eventData)
+{
+    UIElement@ element = eventData["Element"].GetPtr();
+    BrowserFile@ file = GetBrowserFileFromUIElement(element);
+    if (file !is null)
+        CreateModelWithStaticModel(file.resourceKey, editNode);
+}
+
+void HandleBrowserInstantiateAnimatedModel(StringHash eventType, VariantMap& eventData)
+{
+    UIElement@ element = eventData["Element"].GetPtr();
+    BrowserFile@ file = GetBrowserFileFromUIElement(element);
+    if (file !is null)
+        CreateModelWithAnimatedModel(file.resourceKey, editNode);
+}
+
+void HandleBrowserInstantiatePrefab(StringHash eventType, VariantMap& eventData)
+{
+    UIElement@ element = eventData["Element"].GetPtr();
+    BrowserFile@ file = GetBrowserFileFromUIElement(element);
+    if (file !is null)
+        LoadNode(file.GetFullPath());
+}
+
+void HandleBrowserInstantiateInSpawnEditor(StringHash eventType, VariantMap& eventData)
+{
+    UIElement@ element = eventData["Element"].GetPtr();
+    BrowserFile@ file = GetBrowserFileFromUIElement(element);
+    if (file !is null)
+    {
+        spawnedObjectsNames.Resize(1);
+        spawnedObjectsNames[0] = VerifySpawnedObjectFile(file.GetPath());
+        RefreshPickedObjects();
+        ShowSpawnEditor();
+    }
+}
+
+void HandleBrowserLoadScene(StringHash eventType, VariantMap& eventData)
+{
+    UIElement@ element = eventData["Element"].GetPtr();
+    BrowserFile@ file = GetBrowserFileFromUIElement(element);
+    if (file !is null)
+        LoadScene(file.GetFullPath());
+}
+
+void HandleBrowserRunScript(StringHash eventType, VariantMap& eventData)
+{
+    UIElement@ element = eventData["Element"].GetPtr();
+    BrowserFile@ file = GetBrowserFileFromUIElement(element);
+    if (file !is null)
+        ExecuteScript(ExtractFileName(eventData));
+}
+
+void HandleBrowserFileDragBegin(StringHash eventType, VariantMap& eventData)
+{
+    UIElement@ uiElement = eventData["Element"].GetPtr();
+    @browserDragFile = GetBrowserFileFromUIElement(uiElement);
+}
+
+void HandleBrowserFileDragEnd(StringHash eventType, VariantMap& eventData)
+{
+    if (@browserDragFile is null)
+        return;
+
+    UIElement@ element = ui.GetElementAt(ui.cursor.screenPosition);
+    if (element !is null)
+        return;
+
+    if (browserDragFile.resourceType == RESOURCE_TYPE_MATERIAL)
+    {
+        StaticModel@ model = cast<StaticModel>(GetDrawableAtMousePostion());
+        if (model !is null)
+        {
+            AssignMaterial(model, browserDragFile.resourceKey);
+        }
+    }
+
+    browserDragFile = null;
+    browserDragComponent = null;
+    browserDragNode = null;
+}
+
+void HandleFileChanged(StringHash eventType, VariantMap& eventData)
+{
+    String filename = eventData["FileName"].GetString();
+    BrowserFile@ file = GetBrowserFileFromPath(filename);
+    
+    if (file is null)
+    {
+        // TODO: new file logic when watchers are supported 
+        return;
+    }
+    else
+    {
+        file.FileChanged();
+    }
+}
+
+Menu@ CreateBrowserFileActionMenu(String text, String handler, BrowserFile@ browserFile = null)
+{
+    Menu@ menu = CreateContextMenuItem(text, handler);
+    if (browserFile !is null)
+        menu.vars[TEXT_VAR_FILE_ID] = browserFile.id;
+
+    return menu;
+}
+
+int GetResourceType(String path)
+{
+    ShortStringHash fileType;
+    return GetResourceType(path, fileType);
+}
+
+int GetResourceType(String path, ShortStringHash &out fileType, bool useCache = false)
+{
+    if (GetExtensionType(path, fileType) || GetBinaryType(path, fileType, useCache) || GetXmlType(path, fileType, useCache))
+        return GetResourceType(fileType);
+
+    return RESOURCE_TYPE_UNKNOWN;
+}
+
+
+int GetResourceType(ShortStringHash fileType)
+{
+    // binary fileTypes
+    if (fileType == BINARY_TYPE_SCENE)
+        return RESOURCE_TYPE_SCENE;
+    else if (fileType == BINARY_TYPE_PACKAGE)
+        return RESOURCE_TYPE_UNUSABLE;
+    else if (fileType == BINARY_TYPE_COMPRESSED_PACKAGE)
+        return RESOURCE_TYPE_UNUSABLE;
+    else if (fileType == BINARY_TYPE_ANGLESCRIPT)
+        return RESOURCE_TYPE_SCRIPTFILE;
+    else if (fileType == BINARY_TYPE_MODEL)
+        return RESOURCE_TYPE_MODEL;
+    else if (fileType == BINARY_TYPE_SHADER)
+        return RESOURCE_TYPE_UNUSABLE;
+    else if (fileType == BINARY_TYPE_ANIMATION)
+        return RESOURCE_TYPE_ANIMATION;
+
+    // xml fileTypes
+    else if (fileType == XML_TYPE_SCENE)
+        return RESOURCE_TYPE_SCENE;
+    else if (fileType == XML_TYPE_NODE)
+        return RESOURCE_TYPE_PREFAB;
+    else if(fileType == XML_TYPE_MATERIAL)
+        return RESOURCE_TYPE_MATERIAL;
+    else if(fileType == XML_TYPE_TECHNIQUE)
+        return RESOURCE_TYPE_TECHNIQUE;
+    else if(fileType == XML_TYPE_PARTICLEEMITTER)
+        return RESOURCE_TYPE_PARTICLEEMITTER;
+    else if(fileType == XML_TYPE_TEXTURE)
+        return RESOURCE_TYPE_TEXTURE;
+    else if(fileType == XML_TYPE_ELEMENT)
+        return RESOURCE_TYPE_UIELEMENT;
+    else if(fileType == XML_TYPE_ELEMENTS)
+        return RESOURCE_TYPE_UIELEMENTS;
+    else if (fileType == XML_TYPE_ANIMATION_SETTINGS)
+        return RESOURCE_TYPE_ANIMATION_SETTINGS;
+    else if (fileType == XML_TYPE_RENDERPATH)
+        return RESOURCE_TYPE_RENDERPATH;
+    else if (fileType == XML_TYPE_TEXTURE_ATLAS)
+        return RESOURCE_TYPE_TEXTURE_ATLAS;
+    else if (fileType == XML_TYPE_2D_PARTICLE_EFFECT)
+        return RESOURCE_TYPE_2D_PARTICLE_EFFECT;
+    else if (fileType == XML_TYPE_TEXTURE_3D)
+        return RESOURCE_TYPE_TEXTURE_3D;
+    else if (fileType == XML_TYPE_CUBEMAP)
+        return RESOURCE_TYPE_CUBEMAP;
+
+    // extension fileTypes
+    else if (fileType == EXTENSION_TYPE_TTF)
+        return RESOURCE_TYPE_FONT;
+    else if (fileType == EXTENSION_TYPE_OGG)
+        return RESOURCE_TYPE_SOUND;
+    else if(fileType == EXTENSION_TYPE_WAV)
+        return RESOURCE_TYPE_SOUND;
+    else if(fileType == EXTENSION_TYPE_DDS)
+        return RESOURCE_TYPE_IMAGE;
+    else if(fileType == EXTENSION_TYPE_PNG)
+        return RESOURCE_TYPE_IMAGE;
+    else if(fileType == EXTENSION_TYPE_JPG)
+        return RESOURCE_TYPE_IMAGE;
+    else if(fileType == EXTENSION_TYPE_JPEG)
+        return RESOURCE_TYPE_IMAGE;
+    else if(fileType == EXTENSION_TYPE_TGA)
+        return RESOURCE_TYPE_IMAGE;
+    else if(fileType == EXTENSION_TYPE_OBJ)
+        return RESOURCE_TYPE_UNUSABLE;
+    else if(fileType == EXTENSION_TYPE_FBX)
+        return RESOURCE_TYPE_UNUSABLE;
+    else if(fileType == EXTENSION_TYPE_COLLADA)
+        return RESOURCE_TYPE_UNUSABLE;
+    else if(fileType == EXTENSION_TYPE_BLEND)
+        return RESOURCE_TYPE_UNUSABLE;
+    else if(fileType == EXTENSION_TYPE_ANGELSCRIPT)
+        return RESOURCE_TYPE_SCRIPTFILE;
+    else if(fileType == EXTENSION_TYPE_LUASCRIPT)
+        return RESOURCE_TYPE_SCRIPTFILE;
+    else if(fileType == EXTENSION_TYPE_HLSL)
+        return RESOURCE_TYPE_UNUSABLE;
+    else if(fileType == EXTENSION_TYPE_GLSL)
+        return RESOURCE_TYPE_UNUSABLE;
+    else if(fileType == EXTENSION_TYPE_FRAGMENTSHADER)
+        return RESOURCE_TYPE_UNUSABLE;
+    else if(fileType == EXTENSION_TYPE_VERTEXSHADER)
+        return RESOURCE_TYPE_UNUSABLE;
+    else if(fileType == EXTENSION_TYPE_HTML)
+        return RESOURCE_TYPE_UNUSABLE;
+
+    return RESOURCE_TYPE_UNKNOWN;
+}
+
+bool GetExtensionType(String path, ShortStringHash &out fileType)
+{
+    ShortStringHash type = ShortStringHash(GetExtension(path));
+    if (type == EXTENSION_TYPE_TTF)
+        fileType = EXTENSION_TYPE_TTF;
+    else if (type == EXTENSION_TYPE_OGG)
+        fileType = EXTENSION_TYPE_OGG;
+    else if(type == EXTENSION_TYPE_WAV)
+        fileType = EXTENSION_TYPE_WAV;
+    else if(type == EXTENSION_TYPE_DDS)
+        fileType = EXTENSION_TYPE_DDS;
+    else if(type == EXTENSION_TYPE_PNG)
+        fileType = EXTENSION_TYPE_PNG;
+    else if(type == EXTENSION_TYPE_JPG)
+        fileType = EXTENSION_TYPE_JPG;
+    else if(type == EXTENSION_TYPE_JPEG)
+        fileType = EXTENSION_TYPE_JPEG;
+    else if(type == EXTENSION_TYPE_TGA)
+        fileType = EXTENSION_TYPE_TGA;
+    else if(type == EXTENSION_TYPE_OBJ)
+        fileType = EXTENSION_TYPE_OBJ;
+    else if(type == EXTENSION_TYPE_FBX)
+        fileType = EXTENSION_TYPE_FBX;
+    else if(type == EXTENSION_TYPE_COLLADA)
+        fileType = EXTENSION_TYPE_COLLADA;
+    else if(type == EXTENSION_TYPE_BLEND)
+        fileType = EXTENSION_TYPE_BLEND;
+    else if(type == EXTENSION_TYPE_ANGELSCRIPT)
+        fileType = EXTENSION_TYPE_ANGELSCRIPT;
+    else if(type == EXTENSION_TYPE_LUASCRIPT)
+        fileType = EXTENSION_TYPE_LUASCRIPT;
+    else if(type == EXTENSION_TYPE_HLSL)
+        fileType = EXTENSION_TYPE_HLSL;
+    else if(type == EXTENSION_TYPE_GLSL)
+        fileType = EXTENSION_TYPE_GLSL;
+    else if(type == EXTENSION_TYPE_FRAGMENTSHADER)
+        fileType = EXTENSION_TYPE_FRAGMENTSHADER;
+    else if(type == EXTENSION_TYPE_VERTEXSHADER)
+        fileType = EXTENSION_TYPE_VERTEXSHADER;
+    else if(type == EXTENSION_TYPE_HTML)
+        fileType = EXTENSION_TYPE_HTML;
+    else
+        return false;
+
+    return true;
+}
+
+bool GetBinaryType(String path, ShortStringHash &out fileType, bool useCache = false)
+{   
+    ShortStringHash type;
+    if (useCache)
+    {
+        File@ file = cache.GetFile(path);
+        if (file is null)
+            return false;
+
+        if (file.size == 0)
+            return false;
+
+        type = ShortStringHash(file.ReadFileID());
+    }
+    else
+    {
+        File@ file = File();
+        if (!file.Open(path))
+            return false;
+
+        if (file.size == 0)
+            return false;
+
+        type = ShortStringHash(file.ReadFileID());
+    }
+
+    if (type == BINARY_TYPE_SCENE)
+        fileType = BINARY_TYPE_SCENE;
+    else if (type == BINARY_TYPE_PACKAGE)
+        fileType = BINARY_TYPE_PACKAGE;
+    else if (type == BINARY_TYPE_COMPRESSED_PACKAGE)
+        fileType = BINARY_TYPE_COMPRESSED_PACKAGE;
+    else if (type == BINARY_TYPE_ANGLESCRIPT)
+        fileType = BINARY_TYPE_ANGLESCRIPT;
+    else if (type == BINARY_TYPE_MODEL)
+        fileType = BINARY_TYPE_MODEL;
+    else if (type == BINARY_TYPE_SHADER)
+        fileType = BINARY_TYPE_SHADER;
+    else if (type == BINARY_TYPE_ANIMATION)
+        fileType = BINARY_TYPE_ANIMATION;
+    else
+        return false;
+
+    return true;
+}
+
+bool GetXmlType(String path, ShortStringHash &out fileType, bool useCache = false)
+{
+    String name;
+    if (useCache)
+    {
+        XMLFile@ xml = cache.GetResource("XMLFile", path);
+        if (xml is null)
+            return false;
+
+        name = xml.root.name;
+    }
+    else
+    {
+        File@ file = File();
+        if (!file.Open(path))
+            return false;
+
+        if (file.size == 0)
+            return false;
+
+        XMLFile@ xml = XMLFile();
+        if (xml.Load(file))
+            name = xml.root.name;
+        else 
+            return false;
+    }
+
+    bool found = false;
+    if (!name.empty)
+    {
+        found = true;
+        ShortStringHash type = ShortStringHash(name);
+        if (type == XML_TYPE_SCENE)
+            fileType = XML_TYPE_SCENE;
+        else if (type == XML_TYPE_NODE)
+            fileType = XML_TYPE_NODE;
+        else if(type == XML_TYPE_MATERIAL)
+            fileType = XML_TYPE_MATERIAL;
+        else if(type == XML_TYPE_TECHNIQUE)
+            fileType = XML_TYPE_TECHNIQUE;
+        else if(type == XML_TYPE_PARTICLEEMITTER)
+            fileType = XML_TYPE_PARTICLEEMITTER;
+        else if(type == XML_TYPE_TEXTURE)
+            fileType = XML_TYPE_TEXTURE;
+        else if(type == XML_TYPE_ELEMENT)
+            fileType = XML_TYPE_ELEMENT;
+        else if(type == XML_TYPE_ELEMENTS)
+            fileType = XML_TYPE_ELEMENTS;
+        else if (type == XML_TYPE_ANIMATION_SETTINGS)
+            fileType = XML_TYPE_ANIMATION_SETTINGS;
+        else if (type == XML_TYPE_RENDERPATH)
+            fileType = XML_TYPE_RENDERPATH;
+        else if (type == XML_TYPE_TEXTURE_ATLAS)
+            fileType = XML_TYPE_TEXTURE_ATLAS;
+        else if (type == XML_TYPE_2D_PARTICLE_EFFECT)
+            fileType = XML_TYPE_2D_PARTICLE_EFFECT;
+        else if (type == XML_TYPE_TEXTURE_3D)
+            fileType = XML_TYPE_TEXTURE_3D;
+        else if (type == XML_TYPE_CUBEMAP)
+            fileType = XML_TYPE_CUBEMAP;
+        else
+            found = false;
+    }
+    return found;
+}
+
+String ResourceTypeName(int resourceType)
+{
+    if (resourceType == RESOURCE_TYPE_UNUSABLE)
+        return "Unusable";
+    else if (resourceType == RESOURCE_TYPE_UNKNOWN)
+        return "Unknown";
+    else if (resourceType == RESOURCE_TYPE_NOTSET)
+        return "Uninitialized";
+    else if (resourceType == RESOURCE_TYPE_SCENE)
+        return "Scene";
+    else if (resourceType == RESOURCE_TYPE_SCRIPTFILE)
+        return "Script File";
+    else if (resourceType == RESOURCE_TYPE_MODEL)
+        return "Model";
+    else if (resourceType == RESOURCE_TYPE_MATERIAL)
+        return "Material";
+    else if (resourceType == RESOURCE_TYPE_ANIMATION)
+        return "Animation";
+    else if (resourceType == RESOURCE_TYPE_IMAGE)
+        return "Image";
+    else if (resourceType == RESOURCE_TYPE_SOUND)
+        return "Sound";
+    else if (resourceType == RESOURCE_TYPE_TEXTURE)
+        return "Texture";
+    else if (resourceType == RESOURCE_TYPE_FONT)
+        return "Font";
+    else if (resourceType == RESOURCE_TYPE_PREFAB)
+        return "Prefab";
+    else if (resourceType == RESOURCE_TYPE_TECHNIQUE)
+        return "Render Technique";
+    else if (resourceType == RESOURCE_TYPE_PARTICLEEMITTER)
+        return "Particle Emitter";
+    else if (resourceType == RESOURCE_TYPE_UIELEMENT)
+        return "UI Element";
+    else if (resourceType == RESOURCE_TYPE_UIELEMENTS)
+        return "UI Elements";
+    else if (resourceType == RESOURCE_TYPE_ANIMATION_SETTINGS)
+        return "Animation Settings";
+    else if (resourceType == RESOURCE_TYPE_RENDERPATH)
+        return "Render Path";
+    else if (resourceType == RESOURCE_TYPE_TEXTURE_ATLAS)
+        return "Texture Atlas";
+    else if (resourceType == RESOURCE_TYPE_2D_PARTICLE_EFFECT)
+        return "2D Particle Effect";
+    else if (resourceType == RESOURCE_TYPE_TEXTURE_3D)
+        return "Texture 3D";
+    else if (resourceType == RESOURCE_TYPE_CUBEMAP)
+        return "Cubemap";
+    else
+        return "";
+}
+
+class BrowserDir
+{
+    uint id;
+    String resourceKey;
+    String name;
+    Array<BrowserDir@> children;
+    Array<BrowserFile@> files;
+
+    BrowserDir(String path_)
+    {
+        resourceKey = path_;
+        String parent = GetParentPath(path_);
+        name = path_;
+        name.Replace(parent, "");
+        id = browserDirIndex++;
+    }
+
+    int opCmp(BrowserDir@ b)
+    {
+        return name.opCmp(b.name);
+    }
+
+    BrowserFile@ AddFile(String name, uint resourceSourceIndex, uint sourceType)
+    {
+        String path = resourceKey + "/" + name;
+        BrowserFile@ file = BrowserFile(path, resourceSourceIndex, sourceType);
+        files.Push(file);
+        return file;
+    }
+}
+
+class BrowserFile
+{
+    uint id;
+    uint resourceSourceIndex;
+    String resourceKey;
+    String name;
+    String fullname;
+    String extension;
+    ShortStringHash fileType;
+    int resourceType = 0;
+    int sourceType = 0;
+    int sortScore = 0;
+    WeakHandle browserFileListRow;
+
+    BrowserFile(String path_, uint resourceSourceIndex_, int sourceType_)
+    {
+        sourceType = sourceType_;
+        resourceSourceIndex = resourceSourceIndex_;
+        resourceKey = path_;
+        name = GetFileName(path_);
+        extension = GetExtension(path_);
+        fullname = GetFileNameAndExtension(path_);
+        id = browserFileIndex++;
+    }
+
+    int opCmp(BrowserFile@ b)
+    {
+        if (browserSearchSortMode == 1)
+            return fullname.opCmp(b.fullname);
+        else
+            return sortScore - b.sortScore;
+    }
+
+    String GetResourceSource()
+    {
+        if (sourceType == BROWSER_FILE_SOURCE_RESOURCE_DIR)
+            return cache.resourceDirs[resourceSourceIndex];
+        else
+            return "Unknown";
+    }
+
+    String GetFullPath()
+    {
+        return String(cache.resourceDirs[resourceSourceIndex] + resourceKey);
+    }
+
+    String GetPath()
+    {
+        return resourceKey;
+    }
+
+    void DetermainResourceType()
+    {
+        resourceType = GetResourceType(GetFullPath(), fileType, false);
+        Text@ browserFileListRow_ = browserFileListRow.Get();
+        if (browserFileListRow_ !is null)
+        {
+            InitializeBrowserFileListRow(browserFileListRow_, this);
+        }
+    }
+    
+    String ResourceTypeName()
+    {
+        return ::ResourceTypeName(resourceType);
+    }
+
+    void FileChanged()
+    {
+        if (!fileSystem.FileExists(GetFullPath()))
+        {
+        }
+        else
+        {
+        }
+    }
+}
+
+void CreateResourcePreview(String path, Node@ previewNode)
+{
+    int resourceType = GetResourceType(path); 
+    if (resourceType > 0)
+    {
+        File file;
+        file.Open(path);
+
+        if (resourceType == RESOURCE_TYPE_MODEL)
+        {
+            Model@ model = Model();
+            if (model.Load(file))
+            {
+                StaticModel@ staticModel = previewNode.CreateComponent("StaticModel");
+                staticModel.model = model;
+                return;
+            }
+        }
+        else if (resourceType == RESOURCE_TYPE_MATERIAL)
+        {
+            Material@ material = Material();
+            if (material.Load(file))
+            {
+                StaticModel@ staticModel = previewNode.CreateComponent("StaticModel");
+                staticModel.model = cache.GetResource("Model", "Models/Sphere.mdl");
+                staticModel.material = material;
+                return;
+            }
+        }
+        else if (resourceType == RESOURCE_TYPE_IMAGE)
+        {
+            Image@ image = Image();
+            if (image.Load(file))
+            {
+                StaticModel@ staticModel = previewNode.CreateComponent("StaticModel");
+                staticModel.model = cache.GetResource("Model", "Models/Editor/ImagePlane.mdl");
+                Material@ material =  cache.GetResource("Material", "Materials/Editor/TexturedUnlit.xml");
+                Texture2D@ texture = Texture2D();
+                texture.Load(@image, true);
+                material.textures[0] = texture;
+                staticModel.material = material;
+                return;
+            }
+        }
+        else if (resourceType == RESOURCE_TYPE_PREFAB)
+        {
+            if (GetExtension(path) == ".xml")
+            {
+                XMLFile xmlFile;
+                if(xmlFile.Load(file))
+                    if(previewNode.LoadXML(xmlFile.root, true) && (previewNode.GetComponents("StaticModel", true).length > 0 || previewNode.GetComponents("AnimatedModel", true).length > 0))
+                    {
+                        return;
+                    }
+            }
+            else if(previewNode.Load(file, true) && (previewNode.GetComponents("StaticModel", true).length > 0 || previewNode.GetComponents("AnimatedModel", true).length > 0))
+                return;
+
+            previewNode.RemoveAllChildren();
+            previewNode.RemoveAllComponents();
+        }
+    }
+
+    StaticModel@ staticModel = previewNode.CreateComponent("StaticModel");
+    staticModel.model = cache.GetResource("Model", "Models/Editor/ImagePlane.mdl");
+    Material@ material =  cache.GetResource("Material", "Materials/Editor/TexturedUnlit.xml");
+    Texture2D@ texture = Texture2D();
+    Image@ noPreviewImage = cache.GetResource("Image", "Textures/Editor/NoPreviewAvailable.png");
+    texture.Load(noPreviewImage, false);
+    material.textures[0] = texture;
+    staticModel.material = material;
+
+    return;
+}
+
+void RotateResourceBrowserPreview(StringHash eventType, VariantMap& eventData)
+{
+    int elemX = eventData["ElementX"].GetInt();
+    int elemY = eventData["ElementY"].GetInt();
+    
+    if (resourceBrowserPreview.height > 0 && resourceBrowserPreview.width > 0)
+    {
+        float yaw = ((resourceBrowserPreview.height / 2) - elemY) * (90.0 / resourceBrowserPreview.height);
+        float pitch = ((resourceBrowserPreview.width / 2) - elemX) * (90.0 / resourceBrowserPreview.width);
+
+        resourcePreviewNode.rotation = resourcePreviewNode.rotation.Slerp(Quaternion(yaw, pitch, 0), 0.1);
+        RefreshBrowserPreview();
+    }
+}
+
+void RefreshBrowserPreview()
+{
+    resourceBrowserPreview.QueueUpdate();
+}
+

+ 94 - 10
Bin/Data/Scripts/Editor/EditorScene.as

@@ -1,4 +1,4 @@
-// Urho3D editor scene handling
+/// Urho3D editor scene handling
 
 #include "Scripts/Editor/EditorHierarchyWindow.as"
 #include "Scripts/Editor/EditorInspectorWindow.as"
@@ -258,9 +258,13 @@ bool SaveSceneWithExistingName()
         return SaveScene(editorScene.fileName);
 }
 
-void CreateNode(CreateMode mode)
+Node@ CreateNode(CreateMode mode)
 {
-    Node@ newNode = editorScene.CreateChild("", mode);
+    Node@ newNode = null;
+    if (editNode !is null)
+        newNode = editNode.CreateChild("", mode);
+    else
+        newNode = editorScene.CreateChild("", mode);
     // Set the new node a certain distance from the camera
     newNode.position = GetNewNodePosition();
 
@@ -271,6 +275,8 @@ void CreateNode(CreateMode mode)
     SetSceneModified();
 
     FocusNode(newNode);
+
+    return newNode;
 }
 
 void CreateComponent(const String&in componentType)
@@ -306,22 +312,32 @@ void CreateComponent(const String&in componentType)
     HandleHierarchyListSelectionChange();
 }
 
-void LoadNode(const String&in fileName)
+void CreateLoadedComponent(Component@ component)
+{
+    if (component is null) return;
+    CreateComponentAction action;
+    action.Define(component);
+    SaveEditAction(action);
+    SetSceneModified();
+    FocusComponent(component);
+}
+
+Node@ LoadNode(const String&in fileName, Node@ parent = null)
 {
     if (fileName.empty)
-        return;
+        return null;
 
     if (!fileSystem.FileExists(fileName))
     {
         MessageBox("No such node file.\n" + fileName);
-        return;
+        return null;
     }
 
     File file(fileName, FILE_READ);
     if (!file.open)
     {
         MessageBox("Could not open file.\n" + fileName);
-        return;
+        return null;
     }
 
     ui.cursor.shape = CS_BUSY;
@@ -333,15 +349,16 @@ void LoadNode(const String&in fileName)
     Vector3 position, normal;
     GetSpawnPosition(cameraRay, newNodeDistance, position, normal, 0, true);
 
-    Node@ newNode = InstantiateNodeFromFile(file, position, Quaternion(), 1, instantiateMode);
+    Node@ newNode = InstantiateNodeFromFile(file, position, Quaternion(), 1, parent, instantiateMode);
     if (newNode !is null)
     {
         FocusNode(newNode);
         instantiateFileName = fileName;
     }
+    return newNode;
 }
 
-Node@ InstantiateNodeFromFile(File@ file, const Vector3& position, const Quaternion& rotation, float scaleMod = 1.0f, CreateMode mode = REPLICATED)
+Node@ InstantiateNodeFromFile(File@ file, const Vector3& position, const Quaternion& rotation, float scaleMod = 1.0f, Node@ parent = null, CreateMode mode = REPLICATED)
 {
     if (file is null)
         return null;
@@ -359,6 +376,9 @@ Node@ InstantiateNodeFromFile(File@ file, const Vector3& position, const Quatern
 
     suppressSceneChanges = false;
 
+    if (parent !is null)
+        newNode.parent = parent;
+        
     if (newNode !is null)
     {
         newNode.scale = newNode.scale * scaleMod;
@@ -756,7 +776,7 @@ bool SceneChangeParent(Node@ sourceNode, Array<Node@> sourceNodes, Node@ targetN
         SaveEditAction(action);
     }
 
-    for (uint i = 0; i < sourceNodes.length; i++)
+    for (uint i = 0; i < sourceNodes.length; ++i)
     {
         Node@ node = sourceNodes[i];
         node.parent = targetNode;
@@ -966,6 +986,26 @@ bool SaveParticleData(const String&in fileName)
     return false;
 }
 
+void AssignMaterial(StaticModel@ model, String materialPath)
+{
+    Material@ material = cache.GetResource("Material", materialPath);
+    if (material is null)
+        return;
+
+    Array<Material@> oldMaterials;
+    for (uint i=0; i < model.numGeometries; ++i)
+    {
+        oldMaterials.Push(model.materials[i]);
+    }
+    model.material = material;
+
+    AssignMaterialAction action;
+    action.Define(model, oldMaterials, material);
+    SaveEditAction(action);
+    SetSceneModified();
+    FocusComponent(model); 
+}
+
 void UpdateSceneMru(String filename)
 {
     while (uiRecentScenes.Find(filename) > -1)
@@ -996,4 +1036,48 @@ Drawable@ GetFirstDrawable(Node@ node)
     }
     
     return null;
+}
+
+void AssignModel(StaticModel@ assignee, String modelPath)
+{
+    Model@ model = cache.GetResource("Model", modelPath);
+    if (model is null)
+        return;
+
+    Model@ oldModel = assignee.model;
+    assignee.model = model;
+
+    AssignModelAction action;
+    action.Define(assignee, oldModel, model);
+    SaveEditAction(action);
+    SetSceneModified();
+    FocusComponent(assignee); 
 }
+
+void CreateModelWithStaticModel(String filepath, Node@ parent)
+{
+    if (parent is null)
+        return;
+
+    Model@ model = cache.GetResource("Model", filepath);
+    if (model is null)
+        return;
+
+    StaticModel@ staticModel = cast<StaticModel>(editNode.CreateComponent("StaticModel"));
+    staticModel.model = model;
+    CreateLoadedComponent(staticModel);
+}
+
+void CreateModelWithAnimatedModel(String filepath, Node@ parent)
+{
+    if (parent is null)
+        return;
+
+    Model@ model = cache.GetResource("Model", filepath);
+    if (model is null)
+        return;
+
+    AnimatedModel@ animatedModel = cast<StaticModel>(editNode.CreateComponent("AnimatedModel"));
+    animatedModel.model = model;
+    CreateLoadedComponent(animatedModel);
+}

+ 1 - 1
Bin/Data/Scripts/Editor/EditorSpawn.as

@@ -318,7 +318,7 @@ void SpawnObject()
         return;
     IntRect view = activeViewport.viewport.rect;
 
-    for (uint i = 0; i < spawnCount; i++)
+    for (uint i = 0; i < spawnCount; ++i)
     {
         Ray cameraRay = GetActiveViewportCameraRay();
 

+ 80 - 1
Bin/Data/Scripts/Editor/EditorUI.as

@@ -9,6 +9,7 @@ Window@ mruScenesPopup;
 Array<QuickMenuItem@> quickMenuItems;
 FileSelector@ uiFileSelector;
 String consoleCommandInterpreter;
+Window@ contextMenu;
 
 const ShortStringHash UI_ELEMENT_TYPE("UIElement");
 const ShortStringHash WINDOW_TYPE("Window");
@@ -21,6 +22,8 @@ const String TEMP_SCENE_NAME("_tempscene_.xml");
 const ShortStringHash CALLBACK_VAR("Callback");
 const ShortStringHash INDENT_MODIFIED_BY_ICON_VAR("IconIndented");
 
+const ShortStringHash VAR_CONTEXT_MENU_HANDLER("ContextMenuHandler");
+
 const int SHOW_POPUP_INDICATOR = -1;
 const uint MAX_QUICK_MENU_ITEMS = 10;
 
@@ -66,6 +69,7 @@ void CreateUI()
     CreateToolBar();
     CreateSecondaryToolBar();
     CreateQuickMenu();
+    CreateContextMenu();
     CreateHierarchyWindow();
     CreateAttributeInspectorWindow();
     CreateEditorSettingsDialog();
@@ -75,6 +79,7 @@ void CreateUI()
     CreateStatsBar();
     CreateConsole();
     CreateDebugHud();
+    CreateResourceBrowser();
     CreateCamera();
 
     SubscribeToEvent("ScreenMode", "ResizeUI");
@@ -237,6 +242,7 @@ void CreateQuickMenu()
     quickMenu = ui.LoadLayout(cache.GetResource("XMLFile", "UI/EditorQuickMenu.xml"));
     quickMenu.enabled = false;
     quickMenu.visible = false;
+    quickMenu.opacity = uiMaxOpacity;
     
     // Handle a dummy search in the quick menu to finalize its initial size to empty
     PerformQuickMenuSearch("");
@@ -407,6 +413,7 @@ void CreateMenuBar()
         Window@ popup = menu.popup;
         popup.AddChild(CreateMenuItem("Hierarchy", @ShowHierarchyWindow, 'H', QUAL_CTRL));
         popup.AddChild(CreateMenuItem("Attribute inspector", @ShowAttributeInspectorWindow, 'I', QUAL_CTRL));
+        popup.AddChild(CreateMenuItem("Resource browser", @ShowResourceBrowserWindow, 'B', QUAL_CTRL));
         popup.AddChild(CreateMenuItem("Material editor", @ShowMaterialEditor));
         popup.AddChild(CreateMenuItem("Spawn editor", @ShowSpawnEditor));
         popup.AddChild(CreateMenuItem("Editor settings", @ShowEditorSettingsDialog));
@@ -738,7 +745,7 @@ void AddQuickMenuItem(MENU_CALLBACK@ callback, String text)
         return;
 
     bool exists = false;
-    for (uint i=0;i<quickMenuItems.length;i++)
+    for (uint i=0;i<quickMenuItems.length;++i)
     {
         if (quickMenuItems[i].action == text)
         {
@@ -948,6 +955,12 @@ void CenterDialog(UIElement@ element)
     element.SetPosition((graphics.width - size.x) / 2, (graphics.height - size.y) / 2);
 }
 
+void CreateContextMenu()
+{
+    contextMenu = ui.LoadLayout(cache.GetResource("XMLFile", "UI/EditorContextMenu.xml"));
+    ui.root.AddChild(contextMenu);
+}
+
 void UpdateWindowTitle()
 {
     String sceneName = GetFileNameAndExtension(editorScene.fileName);
@@ -1118,6 +1131,8 @@ void HandleKeyDown(StringHash eventType, VariantMap& eventData)
             UnhideUI();
         else if (console.visible)
             console.visible = false;
+        else if (contextMenu.visible)
+            CloseContextMenu();
         else if (quickMenu.visible)
         {
             quickMenu.visible = false;
@@ -1403,3 +1418,67 @@ bool LoadMostRecentScene()
 
     return LoadScene(text.text);
 }
+
+// Set from click to false if opening menu procedurally.
+void OpenContextMenu(bool fromClick=true)
+{
+    if (contextMenu is null)
+        return;
+
+    contextMenu.enabled = true;
+    contextMenu.visible = true;
+    contextMenu.BringToFront();
+    if (fromClick)
+        contextMenuActionWaitFrame=true;
+}
+
+void CloseContextMenu()
+{
+    if (contextMenu is null)
+        return;
+
+    contextMenu.enabled = false;
+    contextMenu.visible = false;
+}
+
+void ActivateContextMenu(Array<UIElement@> actions)
+{
+    contextMenu.RemoveAllChildren();
+    for (uint i=0; i< actions.length; ++i)
+    {
+        contextMenu.AddChild(actions[i]);
+    }
+    contextMenu.SetFixedHeight(24*actions.length+6);
+    contextMenu.position = ui.cursor.screenPosition + IntVector2(10,-10);
+    OpenContextMenu();
+}
+
+Menu@ CreateContextMenuItem(String text, String handler)
+{
+    Menu@ menu = Menu();
+    menu.defaultStyle = uiStyle;
+    menu.style = AUTO_STYLE;
+    menu.SetLayout(LM_HORIZONTAL, 0, IntRect(8, 2, 8, 2));
+    Text@ menuText = Text();
+    menuText.style = "EditorMenuText";
+    menu.AddChild(menuText);
+    menuText.text = text;
+    menu.vars[VAR_CONTEXT_MENU_HANDLER] = handler;
+    SubscribeToEvent(menu, "Released", "ContextMenuEventWrapper");
+    return menu;
+}
+
+void ContextMenuEventWrapper(StringHash eventType, VariantMap& eventData)
+{
+    UIElement@ uiElement = eventData["Element"].GetPtr();
+    if (uiElement is null)
+        return;
+
+    String handler = uiElement.vars[VAR_CONTEXT_MENU_HANDLER].GetString();
+    if (!handler.empty)
+    {
+        SubscribeToEvent(uiElement, "Released", handler);
+        uiElement.SendEvent("Released", eventData);
+    }
+    CloseContextMenu();
+}

+ 78 - 3
Bin/Data/Scripts/Editor/EditorView.as

@@ -16,6 +16,7 @@ int  viewportBorderWidth = 4; // width of a viewport resize border
 IntRect viewportArea; // the area where the editor viewport is. if we ever want to have the viewport not take up the whole screen this abstracts that
 IntRect viewportUIClipBorder = IntRect(27, 60, 0, 0); // used to clip viewport borders, the borders are ugly when going behind the transparent toolbars
 bool mouseWheelCameraPosition = false;
+bool contextMenuActionWaitFrame = false;
 
 const uint VIEWPORT_BORDER_H     = 0x00000001;
 const uint VIEWPORT_BORDER_H1    = 0x00000002;
@@ -400,6 +401,7 @@ void CreateCamera()
     SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
     SubscribeToEvent("UIMouseClick", "ViewMouseClick");
     SubscribeToEvent("MouseMove", "ViewMouseMove");
+    SubscribeToEvent("UIMouseClickEnd", "ViewMouseClickEnd");
     SubscribeToEvent("BeginViewUpdate", "HandleBeginViewUpdate");
     SubscribeToEvent("EndViewUpdate", "HandleEndViewUpdate");
     SubscribeToEvent("BeginViewRender", "HandleBeginViewRender");
@@ -1077,7 +1079,7 @@ void UpdateStats(float timeStep)
 
 void UpdateViewports(float timeStep)
 {
-    for(uint i = 0; i < viewports.length; i++)
+    for(uint i = 0; i < viewports.length; ++i)
     {
         ViewportContext@ viewportContext = viewports[i];
         viewportContext.Update(timeStep);
@@ -1358,7 +1360,8 @@ void DrawNodeDebug(Node@ node, DebugRenderer@ debug, bool drawNode = true)
 void ViewMouseMove()
 {
     // setting mouse position based on mouse position
-    if (ui.focusElement !is null || input.mouseButtonDown[MOUSEB_LEFT|MOUSEB_MIDDLE|MOUSEB_RIGHT])
+    if (ui.dragElement !is null) { }
+    else if (ui.focusElement !is null || input.mouseButtonDown[MOUSEB_LEFT|MOUSEB_MIDDLE|MOUSEB_RIGHT])
         return;
 
     IntVector2 pos = ui.cursor.position;
@@ -1382,6 +1385,28 @@ Ray GetActiveViewportCameraRay()
         float(ui.cursorPosition.x - view.left) / view.width,
         float(ui.cursorPosition.y - view.top) / view.height
     );
+}
+
+void ViewMouseClickEnd()
+{
+    // checks to close open popup windows
+    IntVector2 pos = ui.cursorPosition;
+    if (contextMenu !is null && contextMenu.enabled)
+    {
+        if (contextMenuActionWaitFrame)
+            contextMenuActionWaitFrame = false;
+        else
+        {
+            if (!contextMenu.IsInside(pos, true))
+                CloseContextMenu();
+        }
+    }
+    if (quickMenu !is null && quickMenu.enabled)
+    {
+        bool enabled = quickMenu.IsInside(pos, true);
+        quickMenu.enabled = enabled;
+        quickMenu.visible = enabled;
+    }
 }
 
 void ViewRaycast(bool mouseClick)
@@ -1619,6 +1644,57 @@ Vector3 SelectedNodesCenterPoint()
         return centerPoint;
 }
 
+Vector3 GetScreenCollision(IntVector2 pos)
+{
+    Ray cameraRay = camera.GetScreenRay(float(pos.x) / activeViewport.viewport.rect.width, float(pos.y) / activeViewport.viewport.rect.height);
+    Vector3 res = cameraNode.position + cameraRay.direction * Vector3(0, 0, newNodeDistance);
+
+    bool physicsFound = false;
+    if (editorScene.physicsWorld !is null)
+    {
+        if (!runUpdate)
+            editorScene.physicsWorld.UpdateCollisions();
+
+        PhysicsRaycastResult result = editorScene.physicsWorld.RaycastSingle(cameraRay, camera.farClip);
+
+        if (result.body !is null)
+        {
+            physicsFound = true;
+            result.position;
+        }
+    }
+
+    if (editorScene.octree is null)
+        return res;
+
+    RayQueryResult result = editorScene.octree.RaycastSingle(cameraRay, RAY_TRIANGLE, camera.farClip,
+        DRAWABLE_GEOMETRY, 0x7fffffff);
+
+    if (result.drawable !is null)
+    {
+        // take the closer of the results
+        if (physicsFound && (cameraNode.position - res).length < (cameraNode.position - result.position).length)
+            return res;
+        else
+            return result.position;
+    }
+
+    return res;
+}
+
+Drawable@ GetDrawableAtMousePostion()
+{
+    IntVector2 pos = ui.cursorPosition;
+    Ray cameraRay = camera.GetScreenRay(float(pos.x) / activeViewport.viewport.rect.width, float(pos.y) / activeViewport.viewport.rect.height);
+
+    if (editorScene.octree is null)
+        return null;
+
+    RayQueryResult result = editorScene.octree.RaycastSingle(cameraRay, RAY_TRIANGLE, camera.farClip, DRAWABLE_GEOMETRY, 0x7fffffff);
+
+    return result.drawable;
+}
+
 void HandleBeginViewUpdate(StringHash eventType, VariantMap& eventData)
 {
     // Hide gizmo and grid from preview camera
@@ -1675,4 +1751,3 @@ void HandleEndViewRender(StringHash eventType, VariantMap& eventData)
         }
     }
 }
-

BIN
Bin/Data/Textures/Editor/EditorIcons.png


BIN
Bin/Data/Textures/Editor/NoPreviewAvailable.png


+ 9 - 0
Bin/Data/UI/EditorContextMenu.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<element type="Window">
+	<attribute name="Name" value="BrowserActionMenu" />
+	<attribute name="Size" value="150 67" />
+	<attribute name="Layout Mode" value="Vertical" />
+	<attribute name="Layout Spacing" value="4" />
+	<attribute name="Layout Border" value="6 6 6 6" />
+	<attribute name="Is Movable" value="false" />
+</element>

+ 4 - 0
Bin/Data/UI/EditorIcons.xml

@@ -263,6 +263,10 @@
         <attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
         <attribute name="Image Rect" value="240 64 254 78" />
     </element>
+    <element type="Filter">
+        <attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
+        <attribute name="Image Rect" value="240 80 254 98" />
+    </element>
     <element type="EditMove">
         <attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
         <attribute name="Image Rect" value="0 96 30 126" />

+ 158 - 0
Bin/Data/UI/EditorResourceBrowser.xml

@@ -0,0 +1,158 @@
+<?xml version="1.0"?>
+<element type="Window">
+	<attribute name="Name" value="Resource Browser" />
+	<attribute name="Position" value="400 200" />
+	<attribute name="Size" value="758 260" />
+	<attribute name="Layout Mode" value="Vertical" />
+	<attribute name="Layout Spacing" value="4" />
+	<attribute name="Layout Border" value="6 6 6 6" />
+	<attribute name="Resize Border" value="6 6 6 6" />
+	<attribute name="Is Movable" value="true" />
+	<attribute name="Is Resizable" value="true" />
+	<element>
+		<attribute name="Name" value="Title Bar" />
+		<attribute name="Min Size" value="184 16" />
+		<attribute name="Max Size" value="2147483647 16" />
+		<attribute name="Layout Mode" value="Horizontal" />
+		<attribute name="Layout Spacing" value="8" />
+		<element type="Text">
+			<attribute name="Top Left Color" value="0.85 0.85 0.85 1" />
+			<attribute name="Top Right Color" value="0.85 0.85 0.85 1" />
+			<attribute name="Bottom Left Color" value="0.85 0.85 0.85 1" />
+			<attribute name="Bottom Right Color" value="0.85 0.85 0.85 1" />
+			<attribute name="Text" value="Resource Browser" />
+		</element>
+		<element type="LineEdit">
+			<attribute name="Name" value="Search" />
+			<element type="Text" internal="true" style="none">
+				<attribute name="Top Left Color" value="0.9 1 0.9 1" />
+				<attribute name="Top Right Color" value="0.9 1 0.9 1" />
+				<attribute name="Bottom Left Color" value="0.9 1 0.9 1" />
+				<attribute name="Bottom Right Color" value="0.9 1 0.9 1" />
+			</element>
+			<element type="BorderImage" internal="true" style="none">
+				<attribute name="Size" value="4 15" />
+			</element>
+		</element>
+		<element type="Button">
+			<attribute name="Name" value="RescanButton" />
+			<attribute name="Min Size" value="16 16" />
+			<attribute name="Max Size" value="16 16" />
+			<attribute name="Layout Mode" value="Horizontal" />
+			<element type="BorderImage">
+				<attribute name="Image Rect" value="128 32 144 48" />
+			</element>
+		</element>
+		<element type="Button">
+			<attribute name="Name" value="FilterButton" />
+			<attribute name="Min Size" value="16 16" />
+			<attribute name="Max Size" value="16 16" />
+			<attribute name="Layout Mode" value="Horizontal" />
+			<element type="BorderImage">
+				<attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
+				<attribute name="Image Rect" value="240 80 254 94" />
+			</element>
+		</element>
+		<element type="Button" style="CloseButton">
+			<attribute name="Name" value="CloseButton" />
+		</element>
+	</element>
+	<element style="Panel">
+		<attribute name="Layout Mode" value="Horizontal" />
+		<attribute name="Layout Spacing" value="10" />
+		<attribute name="Layout Border" value="0 4 0 4" />
+		<element type="ListView" style="HierarchyListView">
+			<attribute name="Name" value="DirectoryList" />
+			<attribute name="Max Size" value="400 2147483647" />
+			<attribute name="Highlight Mode" value="Always" />
+			<element type="ScrollBar" internal="true" style="none">
+				<attribute name="Size" value="230 16" />
+				<element type="Button" internal="true" style="none" />
+				<element type="Slider" internal="true" style="none">
+					<attribute name="Position" value="16 0" />
+					<attribute name="Size" value="198 16" />
+					<element type="BorderImage" internal="true" style="none" />
+				</element>
+				<element type="Button" internal="true" style="none">
+					<attribute name="Position" value="214 0" />
+				</element>
+			</element>
+			<element type="ScrollBar" internal="true" style="none">
+				<attribute name="Size" value="16 200" />
+				<element type="Button" internal="true" style="none" />
+				<element type="Slider" internal="true" style="none">
+					<attribute name="Position" value="0 16" />
+					<attribute name="Size" value="16 168" />
+					<element type="BorderImage" internal="true" style="none" />
+				</element>
+				<element type="Button" internal="true" style="none">
+					<attribute name="Position" value="0 184" />
+				</element>
+			</element>
+			<element type="BorderImage" internal="true" style="none">
+				<element type="HierarchyContainer" internal="true" style="HierarchyContainer" />
+			</element>
+			<element internal="true" style="none" />
+		</element>
+		<element type="ListView" style="PanelView">
+			<attribute name="Name" value="FileList" />
+			<attribute name="Min Size" value="0 200" />
+			<attribute name="Max Size" value="1200 1000" />
+			<attribute name="Indent Spacing" value="0" />
+			<element type="ScrollBar" internal="true" style="none">
+				<attribute name="Size" value="230 16" />
+				<element type="Button" internal="true" style="none" />
+				<element type="Slider" internal="true" style="none">
+					<attribute name="Position" value="16 0" />
+					<attribute name="Size" value="198 16" />
+					<element type="BorderImage" internal="true" style="none" />
+				</element>
+				<element type="Button" internal="true" style="none">
+					<attribute name="Position" value="214 0" />
+				</element>
+			</element>
+			<element type="ScrollBar" internal="true" style="none">
+				<attribute name="Size" value="16 200" />
+				<element type="Button" internal="true" style="none" />
+				<element type="Slider" internal="true" style="none">
+					<attribute name="Position" value="0 16" />
+					<attribute name="Size" value="16 168" />
+					<element type="BorderImage" internal="true" style="none" />
+				</element>
+				<element type="Button" internal="true" style="none">
+					<attribute name="Position" value="0 184" />
+				</element>
+			</element>
+			<element type="BorderImage" internal="true" style="none">
+				<element internal="true" style="none" />
+			</element>
+		</element>
+		<element>
+			<attribute name="Min Size" value="266 200" />
+			<attribute name="Max Size" value="266 200" />
+			<element type="View3D" style="View3D">
+				<attribute name="Name" value="ResourceBrowserPreview" />
+			</element>
+		</element>
+	</element>
+	<element>
+		<attribute name="Name" value="StatusBar" />
+		<attribute name="Min Size" value="0 16" />
+		<attribute name="Max Size" value="2147483647 16" />
+		<element type="Text">
+			<attribute name="Name" value="ResultsMessage" />
+			<attribute name="Top Left Color" value="0.85 0.85 0.85 1" />
+			<attribute name="Top Right Color" value="0.85 0.85 0.85 1" />
+			<attribute name="Bottom Left Color" value="0.85 0.85 0.85 1" />
+			<attribute name="Bottom Right Color" value="0.85 0.85 0.85 1" />
+		</element>
+		<element type="Text">
+			<attribute name="Name" value="StatusMessage" />
+			<attribute name="Horiz Alignment" value="Right" />
+			<attribute name="Top Left Color" value="0.85 0.85 0.85 1" />
+			<attribute name="Top Right Color" value="0.85 0.85 0.85 1" />
+			<attribute name="Bottom Left Color" value="0.85 0.85 0.85 1" />
+			<attribute name="Bottom Right Color" value="0.85 0.85 0.85 1" />
+		</element>
+	</element>
+</element>

+ 62 - 0
Bin/Data/UI/EditorResourceFilterWindow.xml

@@ -0,0 +1,62 @@
+<?xml version="1.0"?>
+<element type="Window">
+	<attribute name="Name" value="BrowserFilterMenu" />
+	<attribute name="Size" value="400 80" />
+	<attribute name="Layout Mode" value="Vertical" />
+	<attribute name="Layout Spacing" value="4" />
+	<attribute name="Layout Border" value="6 6 6 6" />
+	<attribute name="Is Movable" value="true" />
+	<attribute name="Is Resizable" value="true" />
+	<element>
+		<attribute name="Name" value="Title Bar" />
+		<attribute name="Min Size" value="152 16" />
+		<attribute name="Max Size" value="2147483647 16" />
+		<attribute name="Layout Mode" value="Horizontal" />
+		<attribute name="Layout Spacing" value="8" />
+		<element type="Text">
+			<attribute name="Top Left Color" value="0.85 0.85 0.85 1" />
+			<attribute name="Top Right Color" value="0.85 0.85 0.85 1" />
+			<attribute name="Bottom Left Color" value="0.85 0.85 0.85 1" />
+			<attribute name="Bottom Right Color" value="0.85 0.85 0.85 1" />
+			<attribute name="Text" value="Resource Filters" />
+		</element>
+		<element type="Button" style="CloseButton">
+			<attribute name="Name" value="CloseButton" />
+		</element>
+	</element>
+	<element style="Panel">
+		<attribute name="Name" value="Options" />
+		<attribute name="Layout Mode" value="Horizontal" />
+		<attribute name="Layout Spacing" value="10" />
+		<attribute name="Layout Border" value="0 4 0 4" />
+		<element>
+			<attribute name="Layout Mode" value="Horizontal" />
+			<attribute name="Layout Spacing" value="4" />
+			<element type="CheckBox">
+				<attribute name="Name" value="ToggleAll" />
+				<attribute name="Is Checked" value="true" />
+			</element>
+			<element type="Text">
+				<attribute name="Top Left Color" value="0.85 0.85 0.85 1" />
+				<attribute name="Top Right Color" value="0.85 0.85 0.85 1" />
+				<attribute name="Bottom Left Color" value="0.85 0.85 0.85 1" />
+				<attribute name="Bottom Right Color" value="0.85 0.85 0.85 1" />
+				<attribute name="Text" value="Toggle All" />
+			</element>
+		</element>
+	</element>
+	<element style="Panel">
+		<attribute name="Name" value="Filters" />
+		<attribute name="Layout Mode" value="Horizontal" />
+		<attribute name="Layout Spacing" value="10" />
+		<attribute name="Layout Border" value="0 4 0 4" />
+		<element style="Panel">
+			<attribute name="Name" value="FilterColumn1" />
+			<attribute name="Layout Border" value="6 6 6 6" />
+		</element>
+		<element style="Panel">
+			<attribute name="Name" value="FilterColumn2" />
+			<attribute name="Layout Border" value="6 6 6 6" />
+		</element>
+	</element>
+</element>

+ 4 - 0
Docs/GettingStarted.dox

@@ -570,6 +570,7 @@ Ctrl+E          - Enable/disable node hierarchy or component
 Ctrl+U          - Unparent scene node
 Ctrl+H          - Open the scene hierarchy window
 Ctrl+I          - Open the attribute inspector window
+Ctrl+B          - Open the resource browser window
 Ctrl+P          - Toggle scene update on/off
 Ctrl+W          - Cycle through solid, wireframe and point rendering
 Ctrl+Z          - Undo
@@ -618,6 +619,9 @@ In addition to whole scenes, single scene nodes including all their components a
 
 Primitive geometries (boxes, spheres, cylinders) can be instantiated from the Create menu. Note that these are just ordinary model files in the Bin/Data/Models directory; their Blender format source files are in the SourceAssets directory.
 
+Additionally a resource browser is available to access the contents of resource directories. Whenever the editor is opened or a scene is loaded the resource browser scans for resources. If at anytime the resources change the reload button can be pressed at the top-right of the resource browser and the resource browser will rescan. Two methods are provided to find resources. A tree view of the folders and a search bar. The search box utilizes a simple string substring of the filename. Resources can also be filtered by type by opening the filter panel which is toggled by the small filter icon.  There are lots of contextual options for each resource type such as dragging a resource into the hierarchy view, a LineEdit, or in the viewport.  A right click context menu on a resource in the browser will give additional options per resource type.
+
+
 \section EditorInstructions_Importing Importing
 
 The editor can import models or scenes from all the formats that the Open Asset Import Library supports, see http://assimp.sourceforge.net/main_features_formats.html