Browse Source

Merge branch 'master' of https://github.com/AtomicGameEngine/AtomicGameEngine into RED-EDITOR-375

rsredsq 10 years ago
parent
commit
40ce6fec0c
87 changed files with 5676 additions and 2806 deletions
  1. 2 0
      AUTHORS.md
  2. 1 1
      Build/Scripts/BuildWindows.js
  3. 1 1
      Build/Scripts/Windows/GenerateVS2015.bat
  4. BIN
      Resources/EditorData/AtomicEditor/resources/default_skin/checkbox_mark_nonuniform.png
  5. 12 0
      Resources/EditorData/AtomicEditor/resources/default_skin/skin.tb.txt
  6. 1 3
      Script/AtomicEditor/editor/Editor.ts
  7. 15 2
      Script/AtomicEditor/editor/EditorEvents.ts
  8. 5 1
      Script/AtomicEditor/ui/EditorStrings.ts
  9. 31 12
      Script/AtomicEditor/ui/Shortcuts.ts
  10. 123 184
      Script/AtomicEditor/ui/frames/HierarchyFrame.ts
  11. 22 55
      Script/AtomicEditor/ui/frames/ProjectFrame.ts
  12. 9 3
      Script/AtomicEditor/ui/frames/ResourceFrame.ts
  13. 895 0
      Script/AtomicEditor/ui/frames/inspector/AttributeInfoEdit.ts
  14. 104 0
      Script/AtomicEditor/ui/frames/inspector/ComponentAttributeUI.ts
  15. 0 620
      Script/AtomicEditor/ui/frames/inspector/ComponentInspector.ts
  16. 2 16
      Script/AtomicEditor/ui/frames/inspector/CreateComponentButton.ts
  17. 0 538
      Script/AtomicEditor/ui/frames/inspector/DataBinding.ts
  18. 91 58
      Script/AtomicEditor/ui/frames/inspector/InspectorFrame.ts
  19. 0 450
      Script/AtomicEditor/ui/frames/inspector/NodeInspector.ts
  20. 38 0
      Script/AtomicEditor/ui/frames/inspector/SelectionEditTypes.ts
  21. 728 0
      Script/AtomicEditor/ui/frames/inspector/SelectionInspector.ts
  22. 144 0
      Script/AtomicEditor/ui/frames/inspector/SelectionPrefabWidget.ts
  23. 193 0
      Script/AtomicEditor/ui/frames/inspector/SelectionSection.ts
  24. 48 0
      Script/AtomicEditor/ui/frames/inspector/SelectionSectionCoreUI.ts
  25. 26 0
      Script/AtomicEditor/ui/frames/inspector/SelectionSectionUI.ts
  26. 150 0
      Script/AtomicEditor/ui/frames/inspector/SerializableEditType.ts
  27. 2 7
      Script/AtomicEditor/ui/frames/menus/HierarchyFrameMenu.ts
  28. 11 3
      Script/AtomicEditor/ui/frames/menus/MainFrameMenu.ts
  29. 1 1
      Script/Packages/Atomic/IO.json
  30. 3 1
      Script/Packages/Editor/Editor.json
  31. 69 9
      Script/TypeScript/AtomicWork.d.ts
  32. 12 3
      Script/tsconfig.json
  33. 1 1
      Source/Atomic/Container/Vector.h
  34. 7 0
      Source/Atomic/Core/StringUtils.cpp
  35. 2 0
      Source/Atomic/Core/StringUtils.h
  36. 127 0
      Source/Atomic/IO/BufferQueue.cpp
  37. 72 0
      Source/Atomic/IO/BufferQueue.h
  38. 3 0
      Source/Atomic/Scene/PrefabComponent.cpp
  39. 27 1
      Source/Atomic/UI/UI.cpp
  40. 8 0
      Source/Atomic/UI/UI.h
  41. 3 1
      Source/Atomic/UI/UIEditField.cpp
  42. 8 0
      Source/Atomic/UI/UIEvents.h
  43. 4 1
      Source/Atomic/UI/UIInlineSelect.cpp
  44. 1 1
      Source/Atomic/UI/UIInput.cpp
  45. 409 53
      Source/Atomic/UI/UIListView.cpp
  46. 22 1
      Source/Atomic/UI/UIListView.h
  47. 50 0
      Source/Atomic/UI/UISelectList.cpp
  48. 10 0
      Source/Atomic/UI/UISelectList.h
  49. 27 2
      Source/Atomic/UI/UIWidget.cpp
  50. 2 1
      Source/Atomic/Web/Web.h
  51. 135 120
      Source/Atomic/Web/WebSocket.cpp
  52. 28 3
      Source/Atomic/Web/WebSocket.h
  53. 2 7
      Source/AtomicEditor/EditorMode/AEEditorEvents.h
  54. 1 1
      Source/AtomicEditor/Editors/ResourceEditor.h
  55. 30 25
      Source/AtomicEditor/Editors/SceneEditor3D/Gizmo3D.cpp
  56. 5 2
      Source/AtomicEditor/Editors/SceneEditor3D/Gizmo3D.h
  57. 189 64
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditHistory.cpp
  58. 25 15
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditHistory.h
  59. 429 0
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditOp.cpp
  60. 109 0
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditOp.h
  61. 0 109
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditOps.cpp
  62. 0 118
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditOps.h
  63. 142 95
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditor3D.cpp
  64. 18 9
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditor3D.h
  65. 82 14
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditor3DEvents.h
  66. 383 0
      Source/AtomicEditor/Editors/SceneEditor3D/SceneSelection.cpp
  67. 72 0
      Source/AtomicEditor/Editors/SceneEditor3D/SceneSelection.h
  68. 24 73
      Source/AtomicEditor/Editors/SceneEditor3D/SceneView3D.cpp
  69. 6 8
      Source/AtomicEditor/Editors/SceneEditor3D/SceneView3D.h
  70. 168 40
      Source/AtomicJS/Javascript/JSAPI.cpp
  71. 4 1
      Source/AtomicJS/Javascript/JSAPI.h
  72. 69 12
      Source/AtomicJS/Javascript/JSIO.cpp
  73. 28 26
      Source/AtomicJS/Javascript/JSSceneSerializable.cpp
  74. 3 1
      Source/ThirdParty/TurboBadger/tb_editfield.cpp
  75. 19 14
      Source/ThirdParty/TurboBadger/tb_inline_select.cpp
  76. 3 1
      Source/ThirdParty/TurboBadger/tb_scroll_container.cpp
  77. 75 6
      Source/ThirdParty/TurboBadger/tb_select.cpp
  78. 11 0
      Source/ThirdParty/TurboBadger/tb_select.h
  79. 1 0
      Source/ToolCore/Assets/AssetDatabase.cpp
  80. 11 0
      Source/ToolCore/Assets/MaterialImporter.cpp
  81. 2 0
      Source/ToolCore/Assets/MaterialImporter.h
  82. 5 1
      Source/ToolCore/JSBind/JSBFunction.h
  83. 3 0
      Source/ToolCore/JSBind/JSBHeaderVisitor.h
  84. 6 1
      Source/ToolCore/JSBind/JSBTypeScript.cpp
  85. 51 7
      Source/ToolCore/JSBind/JavaScript/JSClassWriter.cpp
  86. 2 0
      Source/ToolCore/JSBind/JavaScript/JSClassWriter.h
  87. 13 3
      Source/ToolCore/JSBind/JavaScript/JSFunctionWriter.cpp

+ 2 - 0
AUTHORS.md

@@ -19,6 +19,8 @@
 
 - Jay Sistar (https://github.com/type1j)
 
+- Matt Benic (https://github.com/mattbenic)
+
 ### Contribution Copyright and Licensing
 
 Atomic Game Engine contribution copyrights are held by their authors.  Each author retains the copyright to their contribution and agrees to irrevocably license the contribution under the Atomic Game Engine Contribution License `CONTRIBUTION_LICENSE.md`.  Please see `CONTRIBUTING.md` for more details.

+ 1 - 1
Build/Scripts/BuildWindows.js

@@ -89,7 +89,7 @@ namespace('build', function() {
 
     var cmds = [];
 
-    cmds.push(atomicRoot + "Build/Scripts/Windows/GenerateVS2015.bat");
+    cmds.push(atomicRoot + "Build/Scripts/Windows/GenerateVS2015.bat " + atomicRoot);
 
     jake.exec(cmds, function() {
 

+ 1 - 1
Build/Scripts/Windows/GenerateVS2015.bat

@@ -1,3 +1,3 @@
 call "%VS140COMNTOOLS%..\..\VC\bin\amd64\vcvars64.bat"
-cmake ..\\AtomicGameEngine -DATOMIC_DEV_BUILD=1 -G "Visual Studio 14 2015 Win64"
+cmake %1 -DATOMIC_DEV_BUILD=1 -G "Visual Studio 14 2015 Win64"
 msbuild Atomic.sln /m /p:Configuration=Debug /p:Platform=x64 /t:AtomicTool

BIN
Resources/EditorData/AtomicEditor/resources/default_skin/checkbox_mark_nonuniform.png


+ 12 - 0
Resources/EditorData/AtomicEditor/resources/default_skin/skin.tb.txt

@@ -400,6 +400,18 @@ elements
 		expand 7
 		type Image
 
+	TBGreyCheckBoxNonUniform
+		clone TBCheckBox
+		children
+			element TBGreyCheckBoxNonUniform.selected
+				state selected
+
+	TBGreyCheckBoxNonUniform.selected
+		bitmap checkbox_mark_nonuniform.png
+		expand 7
+		type Image
+
+
 	TBRadioButton
 		bitmap radio.png
 		cut 19

+ 1 - 3
Script/AtomicEditor/editor/Editor.ts

@@ -134,9 +134,7 @@ class Editor extends Atomic.ScriptObject {
 
     handleProjectUnloaded(event) {
 
-        this.sendEvent(EditorEvents.ActiveSceneChange, { scene: null });
-
-
+        this.sendEvent(EditorEvents.ActiveSceneEditorChange, { sceneEditor: null });
 
     }
 

+ 15 - 2
Script/AtomicEditor/editor/EditorEvents.ts

@@ -24,8 +24,14 @@ export interface PlayerLogEvent {
 }
 
 
-export const ActiveSceneChange = "EditorActiveSceneChange";
-export const ActiveNodeChange = "EditorActiveNodeChange";
+export const ActiveSceneEditorChange = "EditorActiveSceneEditorChange";
+export interface ActiveSceneEditorChangeEvent {
+
+  sceneEditor: Editor.SceneEditor3D;
+
+}
+
+
 export const SceneClosed = "EditorSceneClosed";
 export interface SceneClosedEvent {
 
@@ -76,3 +82,10 @@ export interface EditResourceEvent {
   path: string;
 
 }
+
+export const SceneEditStateChange = "SceneEditStateChange";
+export interface SceneEditStateChangeEvent {
+
+  serializable: Atomic.Serializable;
+
+}

+ 5 - 1
Script/AtomicEditor/ui/EditorStrings.ts

@@ -23,7 +23,8 @@ export enum StringID {
     ShortcutPlay,
     ShortcutPlayDebug,
     ShortcutBuild,
-    ShortcutBuildSettings
+    ShortcutBuildSettings,
+    ShortcutFrameSelected
 }
 
 export class EditorString {
@@ -64,8 +65,11 @@ export class EditorString {
         lookup[StringID.ShortcutCopy] = shortcutKey + "C";
         lookup[StringID.ShortcutPaste] = shortcutKey + "V";
         lookup[StringID.ShortcutSelectAll] = shortcutKey + "A";
+
         lookup[StringID.ShortcutFind] = shortcutKey + "F";
 
+        lookup[StringID.ShortcutFrameSelected] = "F";
+
         lookup[StringID.ShortcutBeautify] = shortcutKey + "I";
 
         lookup[StringID.ShortcutSaveFile] = shortcutKey + "S";

+ 31 - 12
Script/AtomicEditor/ui/Shortcuts.ts

@@ -84,17 +84,22 @@ class Shortcuts extends Atomic.ScriptObject {
         this.invokeResourceFrameShortcut("paste");
     }
 
+    invokeFrameSelected() {
+        this.invokeResourceFrameShortcut("frameselected");
+    }
+
+
     invokeSelectAll() {
         this.invokeResourceFrameShortcut("selectall");
     }
 
-    invokeGizmoEditModeChanged(mode:Editor.EditMode) {
+    invokeGizmoEditModeChanged(mode: Editor.EditMode) {
 
         this.sendEvent("GizmoEditModeChanged", { mode: mode });
 
     }
 
-    invokeGizmoAxisModeChanged(mode:Editor.AxisMode, toggle:boolean = false) {
+    invokeGizmoAxisModeChanged(mode: Editor.AxisMode, toggle: boolean = false) {
 
         this.sendEvent("GizmoAxisModeChanged", { mode: mode, toggle: toggle });
 
@@ -115,22 +120,26 @@ class Shortcuts extends Atomic.ScriptObject {
 
             // TODO: Make these customizable
 
-            if (ev.key == Atomic.KEY_W) {
-                this.invokeGizmoEditModeChanged(Editor.EDIT_MOVE);
-            } else if (ev.key == Atomic.KEY_E) {
-              this.invokeGizmoEditModeChanged(Editor.EDIT_ROTATE);
-            } else if (ev.key == Atomic.KEY_R) {
-                this.invokeGizmoEditModeChanged(Editor.EDIT_SCALE);
-            } else if (ev.key == Atomic.KEY_X) {
-                this.invokeGizmoAxisModeChanged(Editor.AXIS_WORLD, true);
+            if (!Atomic.ui.focusedWidget && !this.cmdKeyDown()) {
+
+                if (ev.key == Atomic.KEY_W) {
+                    this.invokeGizmoEditModeChanged(Editor.EDIT_MOVE);
+                } else if (ev.key == Atomic.KEY_E) {
+                    this.invokeGizmoEditModeChanged(Editor.EDIT_ROTATE);
+                } else if (ev.key == Atomic.KEY_R) {
+                    this.invokeGizmoEditModeChanged(Editor.EDIT_SCALE);
+                } else if (ev.key == Atomic.KEY_X) {
+                    this.invokeGizmoAxisModeChanged(Editor.AXIS_WORLD, true);
+                } else if (ev.key == Atomic.KEY_F) {
+                    this.invokeFrameSelected();
+                }
             }
 
         }
 
     }
 
-    // global shortcut handler
-    handleUIShortcut(ev: Atomic.UIShortcutEvent) {
+    cmdKeyDown(): boolean {
 
         var cmdKey;
         if (Atomic.platform == "MacOSX") {
@@ -139,6 +148,16 @@ class Shortcuts extends Atomic.ScriptObject {
             cmdKey = (Atomic.input.getKeyDown(Atomic.KEY_LCTRL) || Atomic.input.getKeyDown(Atomic.KEY_RCTRL));
         }
 
+        return cmdKey;
+
+
+    }
+
+    // global shortcut handler
+    handleUIShortcut(ev: Atomic.UIShortcutEvent) {
+
+        var cmdKey = this.cmdKeyDown();
+
         if (cmdKey) {
 
             if (ev.key == Atomic.KEY_S) {

+ 123 - 184
Script/AtomicEditor/ui/frames/HierarchyFrame.ts

@@ -15,6 +15,7 @@ var IconTemporary = "ComponentBitmap";
 class HierarchyFrame extends Atomic.UIWidget {
 
     scene: Atomic.Scene = null;
+    sceneEditor: Editor.SceneEditor3D;
     hierList: Atomic.UIListView;
     menu: HierarchyFrameMenu;
     nodeIDToItemID = {};
@@ -35,21 +36,16 @@ class HierarchyFrame extends Atomic.UIWidget {
         hierarchycontainer = this.getWidget("hierarchycontainer");
 
         var hierList = this.hierList = new Atomic.UIListView();
-
+        hierList.multiSelect = true;
         hierList.rootList.id = "hierList_";
 
+        hierList.subscribeToEvent("UIListViewSelectionChanged", (event: Atomic.UIListViewSelectionChangedEvent) => this.handleHierListSelectionChangedEvent(event));
+
         hierarchycontainer.addChild(hierList);
 
         this.subscribeToEvent(this, "WidgetEvent", (data) => this.handleWidgetEvent(data));
 
-        this.subscribeToEvent(EditorEvents.ActiveNodeChange, (data) => {
-
-            if (data.node)
-                this.hierList.selectItemByID(data.node.id.toString());
-
-        });
-
-        this.subscribeToEvent(EditorEvents.ActiveSceneChange, (data) => this.handleActiveSceneChanged(data));
+        this.subscribeToEvent(EditorEvents.ActiveSceneEditorChange, (data) => this.handleActiveSceneEditorChanged(data));
 
         // handle dropping on hierarchy, moving node, dropping prefabs, etc
         this.subscribeToEvent(this.hierList.rootList, "DragEnded", (data) => this.handleDragEnded(data));
@@ -122,24 +118,20 @@ class HierarchyFrame extends Atomic.UIWidget {
 
     }
 
-    handleNodeAdded(ev: Atomic.NodeAddedEvent) {
+    handleSceneEditNodeAdded(ev: Editor.SceneEditNodeAddedEvent) {
 
         var node = ev.node;
 
         if (this.filterNode(node))
             return;
 
-        if (!node.parent || node.scene != this.scene)
-            return;
-
         var parentID = this.nodeIDToItemID[node.parent.id];
-
         var childItemID = this.recursiveAddNode(parentID, node);
 
         this.nodeIDToItemID[node.id] = childItemID;
     }
 
-    handleNodeRemoved(ev: Atomic.NodeRemovedEvent) {
+    handleSceneEditNodeRemoved(ev: Editor.SceneEditNodeRemovedEvent) {
 
         // on close
         if (!this.scene)
@@ -151,187 +143,54 @@ class HierarchyFrame extends Atomic.UIWidget {
             return;
 
         delete this.nodeIDToItemID[node.id];
-
-        if (!node.parent || node.scene != this.scene)
-            return;
-
         this.hierList.deleteItemByID(node.id.toString());
 
-        var selectedId = Number(this.hierList.rootList.selectedItemID);
-        var selectedNode = this.scene.getNode(selectedId);
-        if (selectedNode == node) {
-
-            this.sendEvent(EditorEvents.ActiveNodeChange, { node: ev.parent ? ev.parent : this.scene });
-
-        }
-
     }
 
-    handleActiveSceneChanged(data) {
+    handleActiveSceneEditorChanged(event: EditorEvents.ActiveSceneEditorChangeEvent) {
 
         if (this.scene)
             this.unsubscribeFromEvents(this.scene);
 
-        // clear selected node
-        this.sendEvent(EditorEvents.ActiveNodeChange, { node: null });
+        this.sceneEditor = null;
+        this.scene = null;
 
-        this.scene = <Atomic.Scene>data.scene;
+        if (!event.sceneEditor)
+            return;
+
+        this.sceneEditor = event.sceneEditor;
+        this.scene = event.sceneEditor.scene;
 
         this.populate();
 
         if (this.scene) {
 
-            this.subscribeToEvent(this.scene, "NodeAdded", (ev: Atomic.NodeAddedEvent) => this.handleNodeAdded(ev));
-            this.subscribeToEvent(this.scene, "NodeRemoved", (ev: Atomic.NodeRemovedEvent) => this.handleNodeRemoved(ev));
+            this.subscribeToEvent(this.scene, "SceneNodeSelected", (event: Editor.SceneNodeSelectedEvent) => this.handleSceneNodeSelected(event));
+            this.subscribeToEvent(this.scene, "SceneEditNodeAdded", (ev: Editor.SceneEditNodeAddedEvent) => this.handleSceneEditNodeAdded(ev));
+            this.subscribeToEvent(this.scene, "SceneEditNodeRemoved", (ev: Editor.SceneEditNodeRemovedEvent) => this.handleSceneEditNodeRemoved(ev));
             this.subscribeToEvent(this.scene, "NodeNameChanged", (ev: Atomic.NodeNameChangedEvent) => {
 
                 this.hierList.setItemText(ev.node.id.toString(), ev.node.name);
 
             });
 
-        }
-
-    }
-
-    handleWidgetEvent(data: Atomic.UIWidgetEvent): boolean {
-
-        if (data.type == Atomic.UI_EVENT_TYPE_KEY_UP) {
-
-            if (data.key == Atomic.KEY_DOWN || data.key == Atomic.KEY_UP || data.key == Atomic.KEY_LEFT || data.key == Atomic.KEY_RIGHT) {
-                var selectedId = Number(this.hierList.selectedItemID);
-                var node = this.scene.getNode(selectedId);
-
-                if (node) {
-
-                    this.sendEvent("EditorActiveNodeChange", { node: node });
-
-                }
-            }
-            if (data.key == Atomic.KEY_RIGHT) {
-                var selectedId = Number(this.hierList.selectedItemID);
-                var itemNodeId = this.nodeIDToItemID[selectedId];
+            this.subscribeToEvent(this.scene, "SceneEditNodeReparent", (ev) => {
 
-                if (!this.hierList.getExpanded(itemNodeId) && this.hierList.getExpandable(itemNodeId)) {
-                    this.hierList.setExpanded(itemNodeId, true);
-                    this.hierList.rootList.invalidateList();
+                if (!ev.added) {
+                    delete this.nodeIDToItemID[ev.node.id];
+                    this.hierList.deleteItemByID(ev.node.id.toString());
                 } else {
-                    this.hierList.rootList.selectNextItem();
-                }
-
-            } else if (data.key == Atomic.KEY_LEFT) {
-                var selectedId = Number(this.hierList.selectedItemID);
-                var itemNodeId = this.nodeIDToItemID[selectedId];
-
-                if (this.hierList.getExpanded(itemNodeId)) {
-                    this.hierList.setExpanded(itemNodeId, false);
-                    this.hierList.rootList.invalidateList();
-                } else {
-                    var node = this.scene.getNode(selectedId);
-                    var parentNode = node.getParent();
-                    if (parentNode) {
-                        this.hierList.selectItemByID(parentNode.id.toString());
-                    }
-                }
-
-            }
-
-            // node deletion
-            if (data.key == Atomic.KEY_DELETE || data.key == Atomic.KEY_BACKSPACE) {
-
-                var selectedId = Number(this.hierList.rootList.selectedItemID);
-
-                var node = this.scene.getNode(selectedId);
-                if (node) {
-                    this.scene.sendEvent("SceneEditNodeAddedRemoved", { scene:this.scene, node:node, added:false});
-                    node.remove();
-
-                }
-
-            }
-
-        } else if (data.type == Atomic.UI_EVENT_TYPE_POINTER_DOWN) {
-
-            if (data.target == this.hierList.rootList) {
-
-                var node = this.scene.getNode(Number(data.refid));
-
-                if (node) {
-
-                    // set the widget's drag object
-                    var dragObject = new Atomic.UIDragObject(node, node.name.length ? "Node: " + node.name : "Node: (Anonymous)");
-                    this.hierList.rootList.dragObject = dragObject;
-
-                }
-
-            }
-
-        } else if (data.type == Atomic.UI_EVENT_TYPE_CLICK) {
-
-            if (this.menu.handleNodeContextMenu(data.target, data.refid)) {
-                return true;
-            }
-
-            var id = data.target.id;
-
-            if (id == "create popup") {
-
-                var selectedId = Number(this.hierList.rootList.selectedItemID);
-                var node = this.scene.getNode(selectedId);
-                if (this.menu.handlePopupMenu(data.target, data.refid, node))
-                    return true;
-
-            }
-
-            // create
-            if (id == "menu create") {
-                if (!ToolCore.toolSystem.project) return;
-                var src = MenuItemSources.getMenuItemSource("hierarchy create items");
-                var menu = new Atomic.UIMenuWindow(data.target, "create popup");
-                menu.show(src);
-                return true;
-
-            }
-
-
-            if (id == "hierList_") {
-
-                var list = <Atomic.UISelectList>data.target;
-
-                var selectedId = Number(list.selectedItemID);
-                var node = this.scene.getNode(selectedId);
-
-                if (node) {
-
-                    this.sendEvent("EditorActiveNodeChange", { node: node });
+                  var parentID = this.nodeIDToItemID[ev.node.parent.id];
+                  var childItemID = this.recursiveAddNode(parentID, ev.node);
+                  this.nodeIDToItemID[ev.node.id] = childItemID;
 
                 }
 
-                return false;
-
-            }
-        } else if (data.type == Atomic.UI_EVENT_TYPE_RIGHT_POINTER_UP) {
-
-            var id = data.target.id;
-            var db = ToolCore.getAssetDatabase();
-            var node: Atomic.Node;
-
-            if (id == "hierList_")
-                node = this.scene.getNode(Number(this.hierList.hoverItemID));
-            else
-                node = this.scene.getNode(Number(id));
-
-            if (node) {
-
-                this.menu.createNodeContextMenu(this, node, data.x, data.y);
-
-            }
-
-
+            });
 
 
         }
 
-        return false;
     }
 
     filterNode(node: Atomic.Node): boolean {
@@ -397,7 +256,7 @@ class HierarchyFrame extends Atomic.UIWidget {
 
         }
 
-        this.hierList.rootList.value = 0;
+        this.hierList.rootList.value = -1;
         this.hierList.setExpanded(parentID, true);
 
     }
@@ -414,38 +273,118 @@ class HierarchyFrame extends Atomic.UIWidget {
 
             var dragNode = <Atomic.Node>ev.dragObject.object;
 
-            if (dragNode.scene != this.scene) {
-                return;
+            this.sceneEditor.reparentNode(dragNode, dropNode);
+
+        } else if (typeName == "Asset") {
+
+            var asset = <ToolCore.Asset>ev.dragObject.object;
+            var newNode = asset.instantiateNode(dropNode, asset.name);
+            if (newNode) {
+                this.sceneEditor.registerNode(newNode);
+                // getting a click event after this (I think) which
+                // is causing the dropNode to be selected
+                this.sceneEditor.selection.addNode(newNode, true);
             }
+        }
+    }
 
-            // can't drop on self
-            if (dragNode == dropNode) {
-                return;
+    handleSceneNodeSelected(ev: Editor.SceneNodeSelectedEvent) {
+
+        this.hierList.selectItemByID(ev.node.id.toString(), ev.selected);
+
+    }
+
+    handleHierListSelectionChangedEvent(event: Atomic.UIListViewSelectionChangedEvent) {
+
+        if (!this.scene)
+            return;
+
+        var node = this.scene.getNode(Number(event.refid));
+
+        if (node) {
+
+            if (event.selected)
+                this.sceneEditor.selection.addNode(node);
+            else
+                this.sceneEditor.selection.removeNode(node);
+
+        }
+    }
+
+    handleWidgetEvent(data: Atomic.UIWidgetEvent): boolean {
+
+        if (data.type == Atomic.UI_EVENT_TYPE_KEY_UP) {
+
+            // node deletion
+            if (data.key == Atomic.KEY_DELETE || data.key == Atomic.KEY_BACKSPACE) {
+                this.sceneEditor.selection.delete();
             }
 
-            // check if dropping on child of ourselves
-            var parent = dropNode.parent;
+        } else if (data.type == Atomic.UI_EVENT_TYPE_POINTER_DOWN) {
 
-            while (parent) {
+            if (data.target == this.hierList.rootList) {
+
+                var node = this.scene.getNode(Number(data.refid));
+
+                if (node) {
+
+                    // set the widget's drag object
+                    var dragObject = new Atomic.UIDragObject(node, node.name.length ? "Node: " + node.name : "Node: (Anonymous)");
+                    this.hierList.rootList.dragObject = dragObject;
 
-                if (parent == dragNode) {
-                    return;
                 }
 
-                parent = parent.parent;
+            }
+
+        } else if (data.type == Atomic.UI_EVENT_TYPE_CLICK) {
 
+            if (this.menu.handleNodeContextMenu(data.target, data.refid)) {
+                return true;
             }
 
-            // move it
-            dropNode.addChild(dragNode);
+            var id = data.target.id;
 
-        } else if (typeName == "Asset") {
+            if (id == "create popup") {
 
-            var asset = <ToolCore.Asset>ev.dragObject.object;
-            asset.instantiateNode(dropNode, asset.name);
+                var selectedId = Number(this.hierList.rootList.selectedItemID);
+                var node = this.scene.getNode(selectedId);
+                if (!node)
+                    node = this.scene;
+                if (this.menu.handlePopupMenu(data.target, data.refid, node))
+                    return true;
+
+            }
+
+            // create
+            if (id == "menu create") {
+                if (!ToolCore.toolSystem.project) return;
+                var src = MenuItemSources.getMenuItemSource("hierarchy create items");
+                var menu = new Atomic.UIMenuWindow(data.target, "create popup");
+                menu.show(src);
+                return true;
+
+            }
+
+
+        } else if (data.type == Atomic.UI_EVENT_TYPE_RIGHT_POINTER_UP) {
+
+            var id = data.target.id;
+            var node: Atomic.Node;
+
+            if (id == "hierList_")
+                node = this.scene.getNode(Number(this.hierList.hoverItemID));
+            else
+                node = this.scene.getNode(Number(id));
+
+            if (node) {
+
+                this.menu.createNodeContextMenu(this, node, data.x, data.y);
+
+            }
 
         }
 
+        return false;
     }
 
 }

+ 22 - 55
Script/AtomicEditor/ui/frames/ProjectFrame.ts

@@ -51,6 +51,8 @@ class ProjectFrame extends ScriptWidget {
         this.subscribeToEvent("ResourceRemoved", (ev: ToolCore.ResourceRemovedEvent) => this.handleResourceRemoved(ev));
         this.subscribeToEvent("AssetRenamed", (ev: ToolCore.AssetRenamedEvent) => this.handleAssetRenamed(ev));
 
+        folderList.subscribeToEvent("UIListViewSelectionChanged", (event: Atomic.UIListViewSelectionChangedEvent) => this.handleFolderListSelectionChangedEvent(event));
+
         // this.subscribeToEvent(EditorEvents.ResourceFolderCreated, (ev: EditorEvents.ResourceFolderCreatedEvent) => this.handleResourceFolderCreated(ev));
 
         // this uses FileWatcher which doesn't catch subfolder creation
@@ -70,8 +72,7 @@ class ProjectFrame extends ScriptWidget {
 
             if (widget.id == ev.asset.guid) {
 
-                if (widget['assetButton'])
-                {
+                if (widget['assetButton']) {
                     widget['assetButton'].text = ev.asset.name + ev.asset.extension;
                     widget['assetButton'].dragObject = new Atomic.UIDragObject(ev.asset, ev.asset.name);
                 }
@@ -136,56 +137,6 @@ class ProjectFrame extends ScriptWidget {
 
     handleWidgetEvent(data: Atomic.UIWidgetEvent): boolean {
 
-        if (data.type == Atomic.UI_EVENT_TYPE_KEY_UP) {
-
-          if (data.key == Atomic.KEY_RIGHT) {
-              var selectedId = this.folderList.selectedItemID;
-              var itemAssetId = this.assetGUIDToItemID[selectedId];
-
-              if (selectedId != "0") {
-                  if (!this.folderList.getExpanded(itemAssetId) && this.folderList.getExpandable(itemAssetId)) {
-                      this.folderList.setExpanded(itemAssetId, true);
-                      this.folderList.rootList.invalidateList();
-                  } else {
-                      this.folderList.rootList.selectNextItem();
-                  }
-              }
-
-
-          } else if (data.key == Atomic.KEY_LEFT) {
-              var selectedId = this.folderList.selectedItemID;
-              var itemAssetId = this.assetGUIDToItemID[selectedId];
-              if (selectedId != "0") {
-                  if (this.folderList.getExpanded(itemAssetId)) {
-                      this.folderList.setExpanded(itemAssetId, false);
-                      this.folderList.rootList.invalidateList();
-                  } else {
-                      var db = ToolCore.getAssetDatabase();
-
-                      var asset = db.getAssetByGUID(selectedId);
-
-                      var parentAsset = asset.parent;
-                      if (parentAsset) {
-                         this.folderList.selectItemByID(parentAsset.guid.toString());
-                      }
-                  }
-              }
-          }
-
-            if (data.key == Atomic.KEY_DOWN || data.key == Atomic.KEY_UP || data.key == Atomic.KEY_LEFT || data.key == Atomic.KEY_RIGHT) {
-
-                var selectedId = this.folderList.selectedItemID;
-
-                if (selectedId != "0") {
-                    var db = ToolCore.getAssetDatabase();
-
-                    var asset = db.getAssetByGUID(selectedId);
-
-                    if (asset.isFolder)
-                        this.refreshContent(asset);
-                }
-            }
-        }
         if (data.type == Atomic.UI_EVENT_TYPE_RIGHT_POINTER_UP) {
 
             var id = data.target.id;
@@ -295,6 +246,22 @@ class ProjectFrame extends ScriptWidget {
 
     }
 
+    handleFolderListSelectionChangedEvent(event: Atomic.UIListViewSelectionChangedEvent) {
+
+        var selectedId = this.folderList.selectedItemID;
+
+        if (selectedId != "0") {
+            var db = ToolCore.getAssetDatabase();
+
+            var asset = db.getAssetByGUID(selectedId);
+            if (!asset)
+                return;
+
+            if (asset.isFolder)
+                this.refreshContent(asset);
+        }
+    }
+
     handleDragEnded(data: Atomic.DragEndedEvent) {
 
         var asset: ToolCore.Asset;
@@ -306,10 +273,10 @@ class ProjectFrame extends ScriptWidget {
             if (data.target.id == "contentcontainerscroll" || container.isAncestorOf(data.target)) {
 
                 if (data.target["asset"])
-                  asset = <ToolCore.Asset> data.target["asset"];
+                    asset = <ToolCore.Asset>data.target["asset"];
 
                 if (!asset || !asset.isFolder)
-                  asset = this.currentFolder;
+                    asset = this.currentFolder;
             }
 
         }
@@ -358,7 +325,7 @@ class ProjectFrame extends ScriptWidget {
 
         } else if (dragObject.object && dragObject.object.typeName == "Asset") {
 
-            var dragAsset = <ToolCore.Asset> dragObject.object;
+            var dragAsset = <ToolCore.Asset>dragObject.object;
 
             // get the folder we dragged on
             var destPath = Atomic.addTrailingSlash(asset.path);

+ 9 - 3
Script/AtomicEditor/ui/frames/ResourceFrame.ts

@@ -76,8 +76,7 @@ class ResourceFrame extends ScriptWidget {
 
             var sceneEditor3D = new Editor.SceneEditor3D(path, this.tabcontainer);
             editor = sceneEditor3D;
-
-            this.sendEvent(EditorEvents.ActiveSceneChange, { scene: sceneEditor3D.scene });
+            this.sendEvent(EditorEvents.ActiveSceneEditorChange, { sceneEditor: sceneEditor3D });
 
         }
 
@@ -149,6 +148,13 @@ class ResourceFrame extends ScriptWidget {
 
         var closedIndex = editors.indexOf(editor.fullPath);
 
+        if (editor.typeName == "SceneEditor3D") {
+
+            this.sendEvent(EditorEvents.ActiveSceneEditorChange, { sceneEditor: (<Editor.SceneEditor3D> null) });
+
+        }
+
+
         // remove from lookup
         delete this.editors[editor.fullPath];
 
@@ -193,7 +199,7 @@ class ResourceFrame extends ScriptWidget {
 
                     if (w.editor.typeName == "SceneEditor3D") {
 
-                        this.sendEvent(EditorEvents.ActiveSceneChange, { scene: (<Editor.SceneEditor3D> w.editor).scene });
+                        this.sendEvent(EditorEvents.ActiveSceneEditorChange, { sceneEditor: (<Editor.SceneEditor3D> w.editor) });
 
                     }
 

+ 895 - 0
Script/AtomicEditor/ui/frames/inspector/AttributeInfoEdit.ts

@@ -0,0 +1,895 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// LICENSE: Atomic Game Engine Editor and Tools EULA
+// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
+// license information: https://github.com/AtomicGameEngine/AtomicGameEngine
+//
+
+import EditorUI = require("ui/EditorUI");
+import InspectorUtils = require("./InspectorUtils");
+import SerializableEditType = require("./SerializableEditType");
+
+class AttributeInfoEdit extends Atomic.UILayout {
+
+    attrInfo: Atomic.AttributeInfo;
+    editType: SerializableEditType;
+
+    editWidget: Atomic.UIWidget;
+
+    nameOverride: string;
+
+    constructor() {
+
+        super();
+
+    }
+
+    initialize(editType: SerializableEditType, attrInfo: Atomic.AttributeInfo): boolean {
+
+        this.editType = editType;
+        this.attrInfo = attrInfo;
+
+        this.createLayout();
+
+        return true;
+
+    }
+
+    handleWidgetEvent(ev: Atomic.UIWidgetEvent): boolean {
+
+        return false;
+
+    }
+
+    createLayout() {
+
+        this.createEditWidget();
+
+        this.editWidget.subscribeToEvent(this.editWidget, "WidgetEvent", (data) => this.handleWidgetEvent(data));
+
+        var attr = this.attrInfo;
+        var attrNameLP = AttributeInfoEdit.attrNameLP;
+
+        this.layoutDistribution = Atomic.UI_LAYOUT_DISTRIBUTION_GRAVITY;
+
+        var name = new Atomic.UITextField();
+        name.textAlign = Atomic.UI_TEXT_ALIGN_LEFT;
+        name.skinBg = "InspectorTextAttrName";
+        name.layoutParams = attrNameLP;
+
+        if (attr.type == Atomic.VAR_VECTOR3 || attr.type == Atomic.VAR_COLOR ||
+            attr.type == Atomic.VAR_QUATERNION) {
+            this.axis = Atomic.UI_AXIS_Y;
+            this.layoutPosition = Atomic.UI_LAYOUT_POSITION_LEFT_TOP;
+            this.skinBg = "InspectorVectorAttrLayout";
+        }
+
+        var bname = attr.name;
+
+        if (bname == "Is Enabled")
+            bname = "Enabled";
+
+        if (this.nameOverride)
+            name.text = this.nameOverride;
+        else
+            name.text = bname;
+
+        name.fontDescription = AttributeInfoEdit.fontDesc;
+
+        this.addChild(name);
+
+        this.addChild(this.editWidget);
+
+    }
+
+    createEditWidget() {
+
+    }
+
+    refresh() {
+
+
+    }
+
+    static createAttrEdit(editType: SerializableEditType, attrInfo: Atomic.AttributeInfo): AttributeInfoEdit {
+
+        var type: typeof AttributeInfoEdit;
+        var customTypes = AttributeInfoEdit.customAttrEditTypes[editType.typeName];
+        if (customTypes) {
+
+            type = customTypes[attrInfo.name];
+
+        }
+
+        if (!type) {
+
+            type = AttributeInfoEdit.standardAttrEditTypes[attrInfo.type];
+
+        }
+
+        if (!type)
+            return null;
+
+        var attrEdit = new type();
+        if (!attrEdit.initialize(editType, attrInfo))
+            return null;
+
+        return attrEdit;
+
+    }
+
+    // atttribute name layout param
+    static attrNameLP: Atomic.UILayoutParams;
+    static fontDesc: Atomic.UIFontDescription;
+
+    static standardAttrEditTypes: { [variantType: number /*Atomic.VariantType*/]: typeof AttributeInfoEdit } = {};
+
+    static customAttrEditTypes: { [typeName: string]: { [name: string]: typeof AttributeInfoEdit } } = {};
+
+    static registerCustomAttr(typeName: string, attrName: string, edit: typeof AttributeInfoEdit) {
+
+        if (!AttributeInfoEdit.customAttrEditTypes[typeName]) {
+            AttributeInfoEdit.customAttrEditTypes[typeName] = {};
+        }
+
+        AttributeInfoEdit.customAttrEditTypes[typeName][attrName] = edit;
+
+    }
+
+    private static Ctor = (() => {
+
+        var attrNameLP = AttributeInfoEdit.attrNameLP = new Atomic.UILayoutParams();
+        attrNameLP.width = 100;
+
+        var fd = AttributeInfoEdit.fontDesc = new Atomic.UIFontDescription();
+        fd.id = "Vera";
+        fd.size = 11;
+
+    })();
+
+}
+
+class BoolAttributeEdit extends AttributeInfoEdit {
+
+    createEditWidget() {
+
+        var box = new Atomic.UICheckBox();
+        this.editWidget = box;
+    }
+
+    refresh() {
+
+        var uniform = this.editType.getUniformValue(this.attrInfo);
+
+        if (uniform) {
+            var object = this.editType.getFirstObject();
+            this.editWidget.skinBg = "TBGreyCheckBox";
+            if (object) {
+                var value = object.getAttribute(this.attrInfo.name);
+                this.editWidget.value = (value ? 1 : 0);
+            }
+
+        } else {
+
+            this.editWidget.skinBg = "TBGreyCheckBoxNonUniform";
+            this.editWidget.value = 1;
+
+        }
+
+    }
+
+    handleWidgetEvent(ev: Atomic.UIWidgetEvent): boolean {
+
+        if (ev.type == Atomic.UI_EVENT_TYPE_CHANGED) {
+
+            this.editType.onAttributeInfoEdited(this.attrInfo, this.editWidget.value ? true : false);
+            this.refresh();
+
+            return true;
+        }
+
+        return false;
+
+    }
+
+}
+
+class StringAttributeEdit extends AttributeInfoEdit {
+
+    createEditWidget() {
+
+        var field = new Atomic.UIEditField();
+        field.textAlign = Atomic.UI_TEXT_ALIGN_LEFT;
+        field.skinBg = "TBAttrEditorField";;
+        field.fontDescription = AttributeInfoEdit.fontDesc;
+        var lp = new Atomic.UILayoutParams();
+        lp.width = 140;
+        field.layoutParams = lp;
+
+        field.subscribeToEvent(field, "UIWidgetEditComplete", (ev) => this.handleUIWidgetEditCompleteEvent(ev));
+
+        this.editWidget = field;
+    }
+
+    refresh() {
+
+        var uniform = this.editType.getUniformValue(this.attrInfo);
+
+        if (uniform) {
+            var object = this.editType.getFirstObject();
+            if (object) {
+                var value = object.getAttribute(this.attrInfo.name);
+                this.editWidget.text = value;
+            }
+
+        } else {
+
+            this.editWidget.text = "--";
+
+        }
+
+    }
+
+    handleUIWidgetEditCompleteEvent(ev) {
+
+        this.editType.onAttributeInfoEdited(this.attrInfo, this.editWidget.text);
+        this.refresh();
+
+    }
+
+
+    handleWidgetEvent(ev: Atomic.UIWidgetEvent): boolean {
+
+        if (ev.type == Atomic.UI_EVENT_TYPE_CHANGED) {
+
+            return true;
+        }
+
+        return false;
+
+    }
+
+}
+
+class IntAttributeEdit extends AttributeInfoEdit {
+
+    enumSource: Atomic.UISelectItemSource;
+
+    createEditWidget() {
+
+        var attrInfo = this.attrInfo;
+
+        if (attrInfo.enumNames.length) {
+
+            var enumSource = this.enumSource = new Atomic.UISelectItemSource();
+
+            for (var i in attrInfo.enumNames) {
+
+                enumSource.addItem(new Atomic.UISelectItem(attrInfo.enumNames[i], (Number(i) + 1).toString()));
+
+            }
+
+            var button = new Atomic.UIButton();
+            button.fontDescription = AttributeInfoEdit.fontDesc;
+            button.text = "Enum Value!";
+            var lp = new Atomic.UILayoutParams();
+            lp.width = 140;
+            button.layoutParams = lp;
+
+            this.editWidget = button;
+
+        } else {
+
+
+            var field = new Atomic.UIEditField();
+            field.textAlign = Atomic.UI_TEXT_ALIGN_CENTER;
+            field.skinBg = "TBAttrEditorField";;
+            field.fontDescription = AttributeInfoEdit.fontDesc;
+            var lp = new Atomic.UILayoutParams();
+            lp.width = 140;
+            field.layoutParams = lp;
+
+            field.subscribeToEvent(field, "UIWidgetEditComplete", (ev) => this.handleUIWidgetEditCompleteEvent(ev));
+
+            this.editWidget = field;
+        }
+    }
+
+    refresh() {
+
+        var uniform = this.editType.getUniformValue(this.attrInfo);
+
+        if (uniform) {
+            var object = this.editType.getFirstObject();
+            if (object) {
+                var value = object.getAttribute(this.attrInfo.name);
+
+                var widget = this.editWidget;
+                var attrInfo = this.attrInfo;
+
+                if (attrInfo.enumNames.length) {
+                    widget.text = attrInfo.enumNames[value];
+                }
+                else {
+                    widget.text = value.toString();
+                }
+            }
+
+        } else {
+
+            this.editWidget.text = "--";
+
+        }
+
+    }
+
+    handleUIWidgetEditCompleteEvent(ev) {
+
+        // non-enum
+        this.editType.onAttributeInfoEdited(this.attrInfo, Number(this.editWidget.text));
+        this.refresh();
+
+    }
+
+
+    handleWidgetEvent(ev: Atomic.UIWidgetEvent): boolean {
+
+        if (ev.type == Atomic.UI_EVENT_TYPE_CHANGED) {
+
+            return true;
+        }
+
+        if (ev.type == Atomic.UI_EVENT_TYPE_CLICK) {
+
+            var id = this.attrInfo.name + " enum popup";
+
+            if (ev.target.id == id) {
+
+                this.editType.onAttributeInfoEdited(this.attrInfo, Number(ev.refid) - 1);
+                this.refresh();
+
+            }
+
+            else if (this.editWidget == ev.target && this.attrInfo.enumNames.length) {
+
+
+                if (this.enumSource) {
+                    var menu = new Atomic.UIMenuWindow(ev.target, id);
+                    menu.show(this.enumSource);
+                }
+
+                return true;
+
+            }
+
+        }
+
+        return false;
+
+    }
+
+}
+
+class FloatAttributeEdit extends AttributeInfoEdit {
+
+    createEditWidget() {
+
+        var attrInfo = this.attrInfo;
+
+        var field = new Atomic.UIEditField();
+        field.textAlign = Atomic.UI_TEXT_ALIGN_CENTER;
+        field.skinBg = "TBAttrEditorField";;
+        field.fontDescription = AttributeInfoEdit.fontDesc;
+        var lp = new Atomic.UILayoutParams();
+        lp.width = 140;
+        field.layoutParams = lp;
+
+        field.subscribeToEvent(field, "UIWidgetEditComplete", (ev) => this.handleUIWidgetEditCompleteEvent(ev));
+
+        this.editWidget = field;
+
+    }
+
+    refresh() {
+
+        var uniform = this.editType.getUniformValue(this.attrInfo);
+
+        if (uniform) {
+            var object = this.editType.getFirstObject();
+            if (object) {
+
+                var widget = this.editWidget;
+                var attrInfo = this.attrInfo;
+                var value = object.getAttribute(attrInfo.name);
+
+                if (value == undefined) {
+
+                  console.log("WARNING: Undefined value for object: ", this.editType.typeName + "." + attrInfo.name);
+                  widget.text = "???";
+
+                } else {
+                  widget.text = parseFloat(value.toFixed(5)).toString();
+                }
+
+            }
+
+        } else {
+
+            this.editWidget.text = "--";
+
+        }
+
+    }
+
+    handleUIWidgetEditCompleteEvent(ev) {
+
+        this.editType.onAttributeInfoEdited(this.attrInfo, Number(this.editWidget.text));
+        this.refresh();
+
+    }
+
+
+    handleWidgetEvent(ev: Atomic.UIWidgetEvent): boolean {
+
+        if (ev.type == Atomic.UI_EVENT_TYPE_CHANGED) {
+
+            return true;
+        }
+
+        return false;
+
+    }
+
+}
+
+class NumberArrayAttributeEdit extends AttributeInfoEdit {
+
+    selects: Atomic.UIInlineSelect[] = [];
+
+    private numElements: number;
+
+    constructor(numElements: number) {
+
+        super();
+
+        this.numElements = numElements;
+
+    }
+
+    createEditWidget() {
+
+        var attrInfo = this.attrInfo;
+
+        var layout = new Atomic.UILayout();
+        layout.spacing = 0;
+
+        var lp = new Atomic.UILayoutParams();
+        lp.width = this.numElements != 4 ? 100 : 70;
+
+        for (var i = 0; i < this.numElements; i++) {
+
+            var select = new Atomic.UIInlineSelect();
+            this.selects.push(select);
+
+            select.id = String(i + 1);
+            select.fontDescription = AttributeInfoEdit.fontDesc;
+            select.skinBg = "InspectorVectorAttrName";
+            select.setLimits(-10000000, 10000000);
+            if (this.numElements != 4) {
+                var editlp = new Atomic.UILayoutParams();
+                editlp.minWidth = 60;
+                select.editFieldLayoutParams = editlp;
+            }
+            select.layoutParams = lp;
+            layout.addChild(select);
+
+            select["_edit"] = select.getWidget("edit");
+            select["_dec"] = select.getWidget("dec");
+            select["_inc"] = select.getWidget("inc");
+
+            select.subscribeToEvent(select, "WidgetEvent", (ev) => this.handleWidgetEvent(ev));
+            select.subscribeToEvent(select, "UIWidgetEditComplete", (ev) => this.handleUIWidgetEditCompleteEvent(ev));
+        }
+
+        this.editWidget = layout;
+
+    }
+
+    refresh() {
+
+        for (var i in this.selects) {
+
+            var select = this.selects[i];
+            if (select["_edit"].focus || select["_dec"].captured || select["_inc"].captured)
+                continue;
+
+            var uniform = this.editType.getUniformValue(this.attrInfo, i);
+
+            if (uniform) {
+
+                var object = this.editType.getFirstObject();
+
+                if (object) {
+
+                    var value = object.getAttribute(this.attrInfo.name);
+                    select.value = parseFloat(value[i].toFixed(5));
+
+                }
+
+            } else {
+
+                select["_edit"].text = "--";
+
+            }
+
+        }
+
+
+    }
+
+    handleUIWidgetEditCompleteEvent(ev: Atomic.UIWidgetEditCompleteEvent) {
+
+        var index = Number(ev.widget.id) - 1;
+        this.editType.onAttributeInfoEdited(this.attrInfo, ev.widget.value, index);
+        this.refresh();
+
+    }
+
+    handleWidgetEvent(ev: Atomic.UIWidgetEvent): boolean {
+
+        if (ev.type == Atomic.UI_EVENT_TYPE_CHANGED) {
+
+            var captured = false;
+            for (var i in this.selects) {
+                var select = this.selects[i];
+                if (select["_dec"].captured || select["_inc"].captured) {
+
+                    captured = true;
+                    break;
+
+                }
+            }
+
+            if (captured) {
+
+                var index = Number(ev.target.id) - 1;
+                this.editType.onAttributeInfoEdited(this.attrInfo, ev.target.value, index, false);
+
+            }
+
+            return true;
+        }
+
+        return false;
+
+    }
+
+}
+
+class Vector2AttributeEdit extends NumberArrayAttributeEdit {
+
+    constructor() {
+
+        super(2);
+
+    }
+
+}
+
+
+class Vector3AttributeEdit extends NumberArrayAttributeEdit {
+
+    constructor() {
+
+        super(3);
+
+    }
+
+}
+
+class QuaternionAttributeEdit extends NumberArrayAttributeEdit {
+
+    constructor() {
+
+        super(3);
+
+    }
+
+}
+
+class ColorAttributeEdit extends NumberArrayAttributeEdit {
+
+    constructor() {
+
+        super(4);
+
+    }
+
+}
+
+class ResourceRefAttributeEdit extends AttributeInfoEdit {
+
+    refListIndex: number;
+    editField: Atomic.UIEditField;
+
+    constructor(refListIndex: number = -1) {
+
+        super();
+
+        this.refListIndex = refListIndex;
+
+    }
+
+    onResourceChanged(resource: Atomic.Resource) {
+
+        var parent = this.parent;
+
+        while (parent) {
+
+            if (parent.typeName == "UISection") {
+                break;
+            }
+
+            parent = parent.parent;
+
+        }
+
+        if (parent) {
+
+          parent.sendEvent("AttributeEditResourceChanged", { attrInfoEdit: this, resource: resource});
+
+        }
+
+    }
+
+    initialize(editType: SerializableEditType, attrInfo: Atomic.AttributeInfo): boolean {
+
+        if (!attrInfo.resourceTypeName)
+            return false;
+
+        if (this.refListIndex >= 0)
+            this.nameOverride = attrInfo.resourceTypeName + " " + this.refListIndex;
+
+        var importerName = ToolCore.assetDatabase.getResourceImporterName(attrInfo.resourceTypeName);
+
+        if (!importerName)
+            return false;
+
+        return super.initialize(editType, attrInfo);
+    }
+
+    refresh() {
+
+        var uniform = this.editType.getUniformValue(this.attrInfo, this.refListIndex);
+
+        if (uniform) {
+
+            var object = this.editType.getFirstObject();
+
+            if (object) {
+
+                // for cached resources, use the asset name, otherwise use the resource path name
+                var resource: Atomic.Resource;
+                if (this.refListIndex != -1) {
+                    resource = object.getAttribute(this.attrInfo.name).resources[this.refListIndex];
+                } else {
+                    resource = <Atomic.Resource>object.getAttribute(this.attrInfo.name);
+                }
+
+                var text = "";
+
+                if (resource) {
+                    text = resource.name;
+                    var asset = ToolCore.assetDatabase.getAssetByCachePath(resource.name);
+                    if (asset)
+                        text = asset.name;
+                }
+                this.editField.text = text;
+            }
+
+
+        } else {
+            this.editField.text = "--";
+        }
+
+    }
+
+    createEditWidget() {
+
+        var layout = new Atomic.UILayout();
+        var o = InspectorUtils.createAttrEditFieldWithSelectButton("", layout);
+        this.editField = o.editField;
+
+        layout.layoutSize = Atomic.UI_LAYOUT_SIZE_AVAILABLE;
+        layout.gravity = Atomic.UI_GRAVITY_LEFT_RIGHT;
+        layout.layoutDistribution = Atomic.UI_LAYOUT_DISTRIBUTION_GRAVITY;
+
+        var lp = new Atomic.UILayoutParams();
+        lp.width = 140;
+        o.editField.layoutParams = lp;
+        o.editField.readOnly = true;
+
+        this.editWidget = layout;
+
+        var selectButton = o.selectButton;
+
+        var resourceTypeName = this.attrInfo.resourceTypeName;
+        var importerName = ToolCore.assetDatabase.getResourceImporterName(resourceTypeName);
+
+        selectButton.onClick = () => {
+
+            EditorUI.getModelOps().showResourceSelection("Select " + resourceTypeName + " Resource", importerName, function(asset: ToolCore.Asset) {
+
+                var resource = asset.getResource(resourceTypeName);
+                this.editType.onAttributeInfoEdited(this.attrInfo, resource, this.refListIndex);
+                this.onResourceChanged(resource);
+                this.refresh();
+
+            }.bind(this));
+
+        }
+
+        // handle dropping of component on field
+        this.editField.subscribeToEvent(this.editField, "DragEnded", (ev: Atomic.DragEndedEvent) => {
+
+            if (ev.target == o.editField) {
+
+                var dragObject = ev.dragObject;
+
+                var importer;
+
+                if (dragObject.object && dragObject.object.typeName == "Asset") {
+
+                    var asset = <ToolCore.Asset>dragObject.object;
+
+                    if (asset.importerTypeName == importerName) {
+                        importer = asset.importer;
+                    }
+
+                }
+
+                if (importer) {
+
+                    var resource = asset.getResource(resourceTypeName);
+
+                    this.editType.onAttributeInfoEdited(this.attrInfo, resource, this.refListIndex);
+                    this.onResourceChanged(resource);
+                    this.refresh();
+
+
+                }
+            }
+
+        });
+
+    }
+
+}
+
+class ResourceRefListAttributeEdit extends AttributeInfoEdit {
+
+    layout: Atomic.UILayout;
+    refEdits: ResourceRefAttributeEdit[] = [];
+
+    initialize(editType: SerializableEditType, attrInfo: Atomic.AttributeInfo): boolean {
+
+        return super.initialize(editType, attrInfo);
+
+    }
+
+    createRefEdit(index: number) {
+
+        var refEdit = new ResourceRefAttributeEdit(index);
+
+        refEdit.initialize(this.editType, this.attrInfo);
+
+        this.layout.addChild(refEdit);
+
+        this.refEdits.push(refEdit);
+
+    }
+
+    createEditWidget() {
+
+        this.spacing = 0;
+
+        var layout = this.layout = new Atomic.UILayout();
+
+        layout.axis = Atomic.UI_AXIS_Y;
+        layout.spacing = 2;
+        layout.layoutSize = Atomic.UI_LAYOUT_SIZE_AVAILABLE;
+        layout.gravity = Atomic.UI_GRAVITY_LEFT_RIGHT;
+        layout.layoutDistribution = Atomic.UI_LAYOUT_DISTRIBUTION_GRAVITY;
+
+        var lp = new Atomic.UILayoutParams();
+        lp.width = 304;
+
+        layout.layoutParams = lp;
+
+
+        this.editWidget = layout;
+
+    }
+
+    createLayout() {
+
+        this.createEditWidget();
+
+        this.editWidget.subscribeToEvent(this.editWidget, "WidgetEvent", (data) => this.handleWidgetEvent(data));
+
+        this.addChild(this.editWidget);
+
+    }
+
+    refresh() {
+
+        var editType = this.editType;
+
+        var object = this.editType.getFirstObject();
+
+        if (!object) {
+            this.visibility = Atomic.UI_WIDGET_VISIBILITY_GONE;
+            return;
+        }
+
+        this.visibility = Atomic.UI_WIDGET_VISIBILITY_VISIBLE;
+
+        var maxLength = -1;
+        var i;
+        for (i in editType.objects) {
+
+            object = editType.objects[i];
+            var value = object.getAttribute(this.attrInfo.name);
+            if (value.resources.length > maxLength) {
+
+                maxLength = value.resources.length;
+
+            }
+
+        }
+
+        if (maxLength == -1) {
+            this.visibility = Atomic.UI_WIDGET_VISIBILITY_GONE;
+            return;
+        }
+
+        for (i = this.refEdits.length; i < maxLength; i++) {
+
+            this.createRefEdit(i);
+
+        }
+
+        for (i = 0; i < this.refEdits.length; i++) {
+
+            var refEdit = this.refEdits[i];
+
+            if (i < maxLength) {
+                refEdit.visibility = Atomic.UI_WIDGET_VISIBILITY_VISIBLE;
+                refEdit.refresh();
+            }
+            else {
+                refEdit.visibility = Atomic.UI_WIDGET_VISIBILITY_GONE;
+            }
+
+        }
+
+    }
+}
+
+
+
+AttributeInfoEdit.standardAttrEditTypes[Atomic.VAR_BOOL] = BoolAttributeEdit;
+AttributeInfoEdit.standardAttrEditTypes[Atomic.VAR_INT] = IntAttributeEdit;
+AttributeInfoEdit.standardAttrEditTypes[Atomic.VAR_FLOAT] = FloatAttributeEdit;
+AttributeInfoEdit.standardAttrEditTypes[Atomic.VAR_STRING] = StringAttributeEdit;
+
+AttributeInfoEdit.standardAttrEditTypes[Atomic.VAR_VECTOR2] = Vector2AttributeEdit;
+AttributeInfoEdit.standardAttrEditTypes[Atomic.VAR_VECTOR3] = Vector3AttributeEdit;
+AttributeInfoEdit.standardAttrEditTypes[Atomic.VAR_QUATERNION] = QuaternionAttributeEdit;
+
+AttributeInfoEdit.standardAttrEditTypes[Atomic.VAR_COLOR] = ColorAttributeEdit;
+
+AttributeInfoEdit.standardAttrEditTypes[Atomic.VAR_RESOURCEREF] = ResourceRefAttributeEdit;
+AttributeInfoEdit.standardAttrEditTypes[Atomic.VAR_RESOURCEREFLIST] = ResourceRefListAttributeEdit;
+
+export = AttributeInfoEdit;

+ 104 - 0
Script/AtomicEditor/ui/frames/inspector/ComponentAttributeUI.ts

@@ -0,0 +1,104 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// LICENSE: Atomic Game Engine Editor and Tools EULA
+// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
+// license information: https://github.com/AtomicGameEngine/AtomicGameEngine
+//
+
+import InspectorUtils = require("./InspectorUtils");
+import AttributeInfoEdit = require("./AttributeInfoEdit");
+
+class LightCascadeAttributeEdit extends AttributeInfoEdit {
+
+    splitFields: Atomic.UIEditField[] = [];
+
+    createEditWidget() {
+
+        var panel = new Atomic.UILayout();
+        panel.axis = Atomic.UI_AXIS_Y;
+        panel.layoutSize = Atomic.UI_LAYOUT_SIZE_PREFERRED;
+        panel.layoutPosition = Atomic.UI_LAYOUT_POSITION_LEFT_TOP;
+
+        var lp = new Atomic.UILayoutParams();
+        lp.width = 180;
+        panel.layoutParams = lp;
+
+        this.editWidget = panel;
+
+        var _this = this;
+
+        function createHandler(index, field) {
+
+            return function(ev: Atomic.UIWidgetEditCompleteEvent) {
+
+                for (var i in _this.editType.objects) {
+
+                    var o = <Atomic.Light>_this.editType.objects[i];
+                    o.setShadowCascadeParameter(this.index, Number(this.field.text));
+
+                }
+
+                o.scene.sendEvent("SceneEditEnd");
+                _this.refresh();
+
+            }.bind({ index: index, field: field });
+
+        }
+
+        var flp = new Atomic.UILayoutParams();
+        flp.width = 60;
+
+        for (var i = 0; i < 4; i++) {
+            var field = InspectorUtils.createAttrEditField("Split " + i, panel);
+            field.layoutParams = flp;
+            field.subscribeToEvent(field, "UIWidgetEditComplete", createHandler(i, field));
+            this.splitFields.push(field);
+        }
+
+    }
+
+    refresh() {
+
+        // Vector 4 internally
+        for (var i = 0; i < 4; i++) {
+
+            var uniform = this.editType.getUniformValue(this.attrInfo, i);
+
+            var field = this.splitFields[i];
+
+            if (uniform) {
+
+                var object = this.editType.getFirstObject();
+
+                if (object) {
+                    var value = object.getAttribute(this.attrInfo.name);
+                    field.text = parseFloat(value[i].toFixed(5)).toString();
+                }
+                else {
+
+                    field.text = "???";
+                }
+            }
+            else {
+                field.text = "--";
+            }
+
+        }
+
+
+    }
+
+    handleWidgetEvent(ev: Atomic.UIWidgetEvent): boolean {
+
+        if (ev.type == Atomic.UI_EVENT_TYPE_CHANGED) {
+
+            return true;
+        }
+
+        return false;
+
+    }
+
+}
+
+AttributeInfoEdit.registerCustomAttr("Light", "CSM Splits", LightCascadeAttributeEdit);

+ 0 - 620
Script/AtomicEditor/ui/frames/inspector/ComponentInspector.ts

@@ -1,620 +0,0 @@
-//
-// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
-// LICENSE: Atomic Game Engine Editor and Tools EULA
-// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
-// license information: https://github.com/AtomicGameEngine/AtomicGameEngine
-//
-
-import ScriptWidget = require("ui/ScriptWidget");
-import DataBinding = require("./DataBinding");
-import InspectorUtils = require("./InspectorUtils");
-import EditorUI = require("ui/EditorUI");
-import CSComponentClassSelector = require("./CSComponentClassSelector");
-
-class ComponentInspector extends Atomic.UISection {
-
-    constructor() {
-
-        super();
-
-        this.subscribeToEvent("WidgetEvent", (data) => this.handleWidgetEvent(data));
-    }
-
-    handleWidgetEvent(ev: Atomic.UIWidgetEvent) {
-
-        var handled = false;
-
-        for (var i = 0; i < this.bindings.length; i++) {
-
-            if (this.bindings[i].handleWidgetEvent(ev)) {
-
-                handled = true;
-
-            }
-
-        }
-
-        // return if handled
-        return handled;
-
-    }
-
-    addAttr(attr: Atomic.AttributeInfo, before: Atomic.UIWidget = null, after: Atomic.UIWidget = null): Atomic.UILayout {
-
-        if (attr.mode & Atomic.AM_NOEDIT)
-            return null;
-
-        var binding = DataBinding.createBinding(this.component, attr);
-
-        if (!binding)
-            return null;
-
-        var attrLayout = new Atomic.UILayout();
-
-        attrLayout.layoutDistribution = Atomic.UI_LAYOUT_DISTRIBUTION_GRAVITY;
-
-        var name = new Atomic.UITextField();
-        name.textAlign = Atomic.UI_TEXT_ALIGN_LEFT;
-        name.skinBg = "InspectorTextAttrName";
-        name.layoutParams = this.atlp;
-
-        if (attr.type == Atomic.VAR_VECTOR3 || attr.type == Atomic.VAR_COLOR ||
-            attr.type == Atomic.VAR_QUATERNION) {
-            attrLayout.axis = Atomic.UI_AXIS_Y;
-            attrLayout.layoutPosition = Atomic.UI_LAYOUT_POSITION_LEFT_TOP;
-            attrLayout.skinBg = "InspectorVectorAttrLayout";
-        }
-
-        var bname = attr.name;
-
-        if (bname == "Is Enabled")
-            bname = "Enabled";
-
-        name.text = bname;
-        name.fontDescription = this.fd;
-
-        attrLayout.addChild(name);
-
-        attrLayout.addChild(binding.widget);
-
-        if (before)
-            this.attrsVerticalLayout.addChildBefore(attrLayout, before);
-        else if (after)
-            this.attrsVerticalLayout.addChildAfter(attrLayout, after);
-        else
-            this.attrsVerticalLayout.addChild(attrLayout);
-
-
-        this.bindings.push(binding);
-
-        return attrLayout;
-    }
-
-    inspect(component: Atomic.Component) {
-
-        this.component = component;
-
-        this.text = component.typeName;
-
-        component.scene.sendEvent("SceneEditSerializable", { serializable: component, operation: 0});
-        this.subscribeToEvent(component, "SceneEditSerializableUndoRedo", (data) => this.handleSceneEditSerializableUndoRedoEvent(data));
-
-        // For JSComponents append the filename
-        if (component.typeName == "JSComponent") {
-
-            var jsc = <Atomic.JSComponent>component;
-
-            if (jsc.componentFile) {
-                var pathInfo = Atomic.splitPath(jsc.componentFile.name);
-                this.text = "JS - " + pathInfo.fileName;
-            }
-
-        }
-
-        // For CSComponents append the classname
-        if (component.typeName == "CSComponent") {
-
-            var csc = <AtomicNET.CSComponent>component;
-
-            if (csc.componentClassName) {
-                this.text = "CS - " + csc.componentClassName;
-            }
-        }
-
-        // don't expand by default
-        this.value = 0;
-
-        var fd = this.fd = new Atomic.UIFontDescription();
-        fd.id = "Vera";
-        fd.size = 11;
-
-        var nlp = this.nlp = new Atomic.UILayoutParams();
-        nlp.width = 304;
-
-        // atttribute name layout param
-        var atlp = this.atlp = new Atomic.UILayoutParams();
-        atlp.width = 100;
-
-
-        var attrsVerticalLayout = this.attrsVerticalLayout = new Atomic.UILayout(Atomic.UI_AXIS_Y);
-        attrsVerticalLayout.spacing = 3;
-        attrsVerticalLayout.layoutPosition = Atomic.UI_LAYOUT_POSITION_LEFT_TOP;
-        attrsVerticalLayout.layoutSize = Atomic.UI_LAYOUT_SIZE_AVAILABLE;
-
-        this.contentRoot.addChild(attrsVerticalLayout);
-
-        var attrs = component.getAttributes();
-
-        for (var i in attrs) {
-            this.addAttr(attrs[i]);
-        }
-
-        // custom component UI
-        if (component.typeName == "PrefabComponent") {
-            this.addPrefabUI(attrsVerticalLayout);
-        }
-
-        if (component.typeName == "Light") {
-            this.addLightCascadeParametersUI(attrsVerticalLayout);
-        }
-
-        if (component.typeName == "CollisionShape") {
-            this.addCollisionShapeUI(attrsVerticalLayout);
-        }
-
-
-        if (component.typeName == "JSComponent") {
-            // auto expand JSComponents
-            this.value = 1;
-        }
-
-        if (component.typeName == "CSComponent") {
-            // auto expand CSComponents
-            this.value = 1;
-            this.addCSComponentUI(attrsVerticalLayout);
-
-            var csc = <AtomicNET.CSComponent>this.component;
-            var currentClassName = csc.componentClassName;
-
-            this.subscribeToEvent(component, "CSComponentClassChanged", (ev: AtomicNET.CSComponentClassChangedEvent) => {
-
-                if (currentClassName != ev.classname) {
-                    //console.log("CSComponent Class Name Changed ", currentClassName, " ", ev.classname);
-                    this.text = "CS - " + ev.classname;
-                    currentClassName = ev.classname;
-                    this.updateDataBindings();
-                }
-
-            });
-        }
-
-
-        if (component.typeName == "TileMap2D") {
-            this.addTilemap2DUI(attrsVerticalLayout);
-        }
-
-
-        if (component.typeName == "StaticModel" || component.typeName == "AnimatedModel" || component.typeName == "Skybox") {
-            this.addModelUI(attrsVerticalLayout, component.typeName);
-        }
-
-        if (component.typeName == "StaticSprite2D" || component.typeName == "AnimatedSprite2D") {
-            this.addSpriteUI(attrsVerticalLayout, component.typeName);
-        }
-
-
-        var deleteButton = this.deleteButton = new Atomic.UIButton();
-        deleteButton.text = "Delete Component";
-        deleteButton.fontDescription = fd;
-
-        deleteButton.onClick = () => {
-
-            var node = this.component.node;
-            this.component.remove();
-
-            // refresh entire inspector, fix this...
-            this.sendEvent("EditorActiveNodeChange", { node: node });
-            
-            return true;
-
-        }
-
-        attrsVerticalLayout.addChild(deleteButton);
-
-        for (var i in this.bindings) {
-            this.bindings[i].setWidgetValueFromObject();
-            this.bindings[i].objectLocked = false;
-        }
-
-    }
-
-    updateDataBindings() {
-
-        var newBindings: Array<DataBinding> = new Array();
-        var foundAttr: Array<Atomic.AttributeInfo> = new Array();
-
-        var attrs = this.component.getAttributes();
-
-        // run through current attr bindings looking for ones to preserve
-        for (var i in this.bindings) {
-
-            var binding = this.bindings[i];
-
-            for (var j in attrs) {
-
-                var attr = <Atomic.AttributeInfo>attrs[j];
-
-                if (attr.name == binding.attrInfo.name && attr.type == binding.attrInfo.type) {
-
-                    newBindings.push(binding);
-                    foundAttr.push(attr);
-                    break;
-
-                }
-
-            }
-
-        }
-
-        // remove bindings that no longer exist
-        for (var i in this.bindings) {
-
-            var binding = this.bindings[i];
-
-            if (newBindings.indexOf(binding) == -1) {
-
-                binding.widget.parent.remove();
-
-            }
-
-        }
-
-        // set new bindings, additional bindings may be added below
-        this.bindings = newBindings;
-
-        // add new attr
-        var curAttrLayout:Atomic.UILayout = null;
-        for (var i in attrs) {
-
-            var attr = attrs[i];
-
-            if (foundAttr.indexOf(attr) == -1) {
-
-                if (!curAttrLayout)
-                  curAttrLayout = this.addAttr(attr, this.deleteButton);
-                else
-                  curAttrLayout = this.addAttr(attr, null, curAttrLayout);
-
-            }
-
-        }
-
-        for (var i in this.bindings) {
-            this.bindings[i].setWidgetValueFromObject();
-            this.bindings[i].objectLocked = false;
-        }
-
-    }
-
-    handleSceneEditSerializableUndoRedoEvent(ev) {
-
-      for (var i in this.bindings) {
-          this.bindings[i].objectLocked = true;
-          this.bindings[i].setWidgetValueFromObject();
-          this.bindings[i].objectLocked = false;
-      }
-
-    }
-
-    // Move these to a mixing class
-
-    addPrefabUI(layout: Atomic.UILayout) {
-
-    }
-
-    acceptAssetDrag(importerTypeName: string, ev: Atomic.DragEndedEvent): ToolCore.AssetImporter {
-
-        var dragObject = ev.dragObject;
-
-        if (dragObject.object && dragObject.object.typeName == "Asset") {
-
-            var asset = <ToolCore.Asset>dragObject.object;
-
-            if (asset.importerTypeName == importerTypeName) {
-                return asset.importer;
-            }
-
-        }
-
-        return null;
-
-    }
-
-    createMaterialClosure(layout: Atomic.UILayout, staticModel: Atomic.StaticModel, index: number) {
-
-        var o = InspectorUtils.createAttrEditFieldWithSelectButton("Material " + index, layout);
-        var materialField = o.editField;
-        materialField.readOnly = true;
-
-        var select = o.selectButton;
-
-        select.onClick = () => {
-
-            EditorUI.getModelOps().showResourceSelection("Select Material", "MaterialImporter", function(asset: ToolCore.Asset) {
-
-                staticModel.setMaterialIndex(index, <Atomic.Material>Atomic.cache.getResource("Material", asset.path));
-
-                if (staticModel.getMaterial())
-                    materialField.text = staticModel.getMaterial().name;
-                else
-                    materialField.text = "";
-
-            });
-
-        }
-
-        var material = staticModel.getMaterial();
-
-        if (material) {
-
-            materialField.text = material.name;
-
-        }
-
-        // handle dropping of material on field
-        materialField.subscribeToEvent(materialField, "DragEnded", (ev: Atomic.DragEndedEvent) => {
-
-            if (ev.target == materialField) {
-
-                var importer = this.acceptAssetDrag("MaterialImporter", ev);
-
-                if (importer) {
-
-                    var materialImporter = <ToolCore.MaterialImporter>importer;
-                    var asset = materialImporter.asset;
-
-                    var material = <Atomic.Material>Atomic.cache.getResource("Material", asset.path);
-
-                    if (material) {
-
-                        staticModel.setMaterialIndex(index, material);
-                        ev.target.text = material.name;
-
-                    }
-                }
-            }
-
-        });
-
-
-    }
-
-    addModelUI(layout: Atomic.UILayout, typeName: string) {
-
-        var staticModel = <Atomic.StaticModel>this.component;
-
-        var numGeometries = staticModel.numGeometries;
-        if (typeName == "Skybox") {
-            numGeometries = 1;
-        }
-
-        for (var x = 0; x < numGeometries; x++) {
-
-            this.createMaterialClosure(layout, staticModel, x);
-
-        }
-
-
-    }
-
-
-    addSpriteUI(layout: Atomic.UILayout, typeName: string) {
-
-        var spriteComponent = <Atomic.StaticSprite2D>this.component;
-
-        var o = InspectorUtils.createAttrEditFieldWithSelectButton("Sprite", layout);
-        var field = o.editField;
-        field.readOnly = true;
-        field.text = spriteComponent.sprite ? spriteComponent.sprite.name : "";
-
-        var select = o.selectButton;
-
-        select.onClick = () => {
-
-            // this should allow selecting of sprite sheets as well
-            EditorUI.getModelOps().showResourceSelection("Select Sprite", "TextureImporter", function(asset: ToolCore.Asset) {
-
-                spriteComponent.sprite = <Atomic.Sprite2D>Atomic.cache.getResource("Sprite2D", asset.path);
-                if (spriteComponent.sprite)
-                    field.text = spriteComponent.sprite.name;
-
-            });
-
-        }
-
-        // handle dropping of component on field
-        field.subscribeToEvent(field, "DragEnded", (ev: Atomic.DragEndedEvent) => {
-
-            if (ev.target == field) {
-
-                var importer = this.acceptAssetDrag("TextureImporter", ev);
-
-                if (importer) {
-
-                    spriteComponent.sprite = <Atomic.Sprite2D>Atomic.cache.getResource("Sprite2D", importer.asset.path);
-                    if (spriteComponent.sprite)
-                        field.text = spriteComponent.sprite.name;
-
-                }
-            }
-
-        });
-
-
-    }
-
-    addTilemap2DUI(layout: Atomic.UILayout) {
-
-        var tilemap = <Atomic.TileMap2D>this.component;
-
-        var o = InspectorUtils.createAttrEditFieldWithSelectButton("TMX File", layout);
-        var field = o.editField;
-        field.readOnly = true;
-        field.text = tilemap.tmxFile ? tilemap.tmxFile.name : "";
-
-        var select = o.selectButton;
-
-        select.onClick = () => {
-
-            // this should allow selecting of sprite sheets as well
-            EditorUI.getModelOps().showResourceSelection("Select TMX File", "TMXImporter", function(asset: ToolCore.Asset) {
-
-                tilemap.tmxFile = <Atomic.TmxFile2D>Atomic.cache.getResource("TmxFile2D", asset.path);
-                if (tilemap.tmxFile)
-                    field.text = tilemap.tmxFile.name;
-            });
-
-        }
-
-        // handle dropping of component on field
-        field.subscribeToEvent(field, "DragEnded", (ev: Atomic.DragEndedEvent) => {
-
-            if (ev.target == field) {
-
-                var importer = this.acceptAssetDrag("TextureImporter", ev);
-
-                if (importer) {
-
-                    tilemap.tmxFile = <Atomic.TmxFile2D>Atomic.cache.getResource("TmxFile2D", importer.asset.path);
-                    if (tilemap.tmxFile)
-                        field.text = tilemap.tmxFile.name;
-
-                }
-            }
-
-        });
-
-    }
-
-    addLightCascadeParametersUI(layout: Atomic.UILayout) {
-
-        var light = <Atomic.Light>this.component;
-
-        var cascadeInfo = light.getShadowCascade();
-
-        var container = InspectorUtils.createContainer();
-        container.gravity = Atomic.UI_GRAVITY_ALL;
-        layout.addChild(container);
-
-        var panel = new Atomic.UILayout();
-        panel.axis = Atomic.UI_AXIS_Y;
-        panel.layoutSize = Atomic.UI_LAYOUT_SIZE_PREFERRED;
-        panel.layoutPosition = Atomic.UI_LAYOUT_POSITION_LEFT_TOP;
-        container.addChild(panel);
-
-        var label = InspectorUtils.createAttrName("CSM Splits:");
-        panel.addChild(label);
-
-        function createHandler(index, light, field) {
-
-            return function(data: Atomic.UIWidgetEvent) {
-
-                if (data.type == Atomic.UI_EVENT_TYPE_CHANGED) {
-
-                    this.light.setShadowCascadeParameter(this.index, Number(this.field.text));
-
-                }
-
-            }.bind({ index: index, light: light, field: field });
-
-        }
-
-        var field = InspectorUtils.createAttrEditField("Split 0", panel);
-        field.text = cascadeInfo[0].toString();
-        field.subscribeToEvent(field, "WidgetEvent", createHandler(0, light, field));
-
-        field = InspectorUtils.createAttrEditField("Split 1", panel);
-        field.text = cascadeInfo[1].toString();
-        field.subscribeToEvent(field, "WidgetEvent", createHandler(1, light, field));
-
-        field = InspectorUtils.createAttrEditField("Split 2", panel);
-        field.text = cascadeInfo[2].toString();
-        field.subscribeToEvent(field, "WidgetEvent", createHandler(2, light, field));
-
-        field = InspectorUtils.createAttrEditField("Split 3", panel);
-        field.text = cascadeInfo[3].toString();
-        field.subscribeToEvent(field, "WidgetEvent", createHandler(3, light, field));
-
-    }
-
-
-    addCollisionShapeUI(layout: Atomic.UILayout) {
-
-        var shape = <Atomic.CollisionShape>this.component;
-
-        var button = new Atomic.UIButton();
-        button.fontDescription = InspectorUtils.attrFontDesc;
-        button.gravity = Atomic.UI_GRAVITY_RIGHT;
-        button.text = "Set from Model";
-
-        button.onClick = () => {
-
-            var model = <Atomic.Drawable>shape.node.getComponent("StaticModel");
-            if (model) {
-                var box = model.boundingBox;
-                shape.setBox([box[3] - box[0], box[4] - box[1], box[5] - box[2]]);
-            }
-
-        };
-
-        layout.addChild(button);
-
-    }
-
-    addCSComponentUI(layout: Atomic.UILayout) {
-
-        for (var i in this.bindings) {
-
-            var binding = this.bindings[i];
-
-            // replace the Class widget with a editfield + select button
-            if (binding.attrInfo.name == "Class") {
-
-                var parent = binding.widget.parent;
-                var text = binding.widget.text;
-                binding.widget.parent.removeChild(binding.widget);
-                var o = InspectorUtils.createAttrEditFieldWithSelectButton("", parent);
-                o.editField.text = text;
-                binding.widget = o.editField;
-
-                o.selectButton.onClick = () => {
-
-                    var cscomponent = <AtomicNET.CSComponent>this.component;
-                    if (cscomponent.assemblyFile) {
-                        var selector = new CSComponentClassSelector(o.editField, cscomponent);
-                    }
-                }
-
-                break;
-            }
-        }
-    }
-
-    component: Atomic.Component;
-    bindings: Array<DataBinding> = new Array();
-
-    fd: Atomic.UIFontDescription;
-
-    nlp: Atomic.UILayoutParams;
-
-    // atttribute name layout param
-    atlp: Atomic.UILayoutParams;
-
-    attrsVerticalLayout: Atomic.UILayout;
-
-    deleteButton: Atomic.UIButton;
-
-
-}
-
-export = ComponentInspector;

+ 2 - 16
Script/AtomicEditor/ui/frames/inspector/CreateComponentButton.ts

@@ -5,8 +5,6 @@
 // license information: https://github.com/AtomicGameEngine/AtomicGameEngine
 //
 
-import ComponentInspector = require("./ComponentInspector");
-
 var audioCreateSource = new Atomic.UIMenuItemSource();
 
 audioCreateSource.addItem(new Atomic.UIMenuItem("SoundListener", "SoundListener"));
@@ -99,12 +97,10 @@ for (var sub in sources) {
 
 class CreateComponentButton extends Atomic.UIButton {
 
-    constructor(node: Atomic.Node) {
+    constructor() {
 
         super();
 
-        this.node = node;
-
         this.fd.id = "Vera";
         this.fd.size = 11;
 
@@ -129,16 +125,7 @@ class CreateComponentButton extends Atomic.UIButton {
 
         if (ev.target && ev.target.id == "create component popup") {
 
-            var c = this.node.createComponent(ev.refid);
-
-            if (c) {
-
-              var ci = new ComponentInspector();
-              ci.inspect(c);
-
-              this.parent.addChildRelative(ci, Atomic.UI_WIDGET_Z_REL_BEFORE, this);
-
-            }
+            this.sendEvent("SelectionCreateComponent", { componentTypeName : ev.refid});
 
             return true;
 
@@ -146,7 +133,6 @@ class CreateComponentButton extends Atomic.UIButton {
 
     }
 
-    node: Atomic.Node;
     fd: Atomic.UIFontDescription = new Atomic.UIFontDescription();
 
 }

+ 0 - 538
Script/AtomicEditor/ui/frames/inspector/DataBinding.ts

@@ -1,538 +0,0 @@
-//
-// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
-// LICENSE: Atomic Game Engine Editor and Tools EULA
-// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
-// license information: https://github.com/AtomicGameEngine/AtomicGameEngine
-//
-
-import InspectorUtils = require("./InspectorUtils");
-import EditorUI = require("ui/EditorUI");
-
-class DataBinding {
-
-    constructor(object: Atomic.Serializable, attrInfo: Atomic.AttributeInfo, widget: Atomic.UIWidget) {
-
-        this.object = object;
-        this.attrInfo = attrInfo;
-        this.widget = widget;
-
-    }
-
-    static createBinding(object: Atomic.Serializable, attrInfo: Atomic.AttributeInfo): DataBinding {
-
-        var widget: Atomic.UIWidget = null;
-
-        var fd = new Atomic.UIFontDescription();
-        fd.id = "Vera";
-        fd.size = 11;
-
-        var editWidgets: Array<Atomic.UIWidget> = [];
-
-        var enumSource = null;
-
-        if (attrInfo.type == Atomic.VAR_BOOL) {
-
-            var box = new Atomic.UICheckBox();
-            box.skinBg = "TBGreyCheckBox";
-            widget = box;
-        }
-        else if (attrInfo.type == Atomic.VAR_INT) {
-
-            if (attrInfo.enumNames.length) {
-
-                enumSource = new Atomic.UISelectItemSource();
-
-                for (var i in attrInfo.enumNames) {
-
-                    enumSource.addItem(new Atomic.UISelectItem(attrInfo.enumNames[i], (Number(i) + 1).toString()));
-
-                }
-
-                var button = new Atomic.UIButton();
-                button.fontDescription = fd;
-                button.text = "Enum Value!";
-                var lp = new Atomic.UILayoutParams();
-                lp.width = 140;
-                button.layoutParams = lp;
-
-                widget = button;
-
-            } else {
-
-                var field = new Atomic.UIEditField();
-                field.textAlign = Atomic.UI_TEXT_ALIGN_CENTER;
-                field.fontDescription = fd;
-                field.skinBg = "TBAttrEditorField";
-
-                var lp = new Atomic.UILayoutParams();
-                lp.width = 140;
-                field.layoutParams = lp;
-                editWidgets.push(field);
-                widget = field;
-            }
-
-        } else if (attrInfo.type == Atomic.VAR_FLOAT) {
-
-            var field = new Atomic.UIEditField();
-            field.textAlign = Atomic.UI_TEXT_ALIGN_CENTER;
-            field.fontDescription = fd;
-            field.skinBg = "TBAttrEditorField";
-
-            var lp = new Atomic.UILayoutParams();
-            lp.width = 140;
-            field.layoutParams = lp;
-            editWidgets.push(field);
-
-            widget = field;
-
-        }
-        else if (attrInfo.type == Atomic.VAR_STRING) {
-
-            var field = new Atomic.UIEditField();
-            field.textAlign = Atomic.UI_TEXT_ALIGN_LEFT;
-            field.skinBg = "TBAttrEditorField";;
-            field.fontDescription = fd;
-            var lp = new Atomic.UILayoutParams();
-            lp.width = 140;
-            field.layoutParams = lp;
-            editWidgets.push(field);
-            widget = field;
-        }
-        else if (attrInfo.type == Atomic.VAR_VECTOR3 || attrInfo.type == Atomic.VAR_QUATERNION) {
-            var layout = new Atomic.UILayout();
-            widget = layout;
-            layout.spacing = 0;
-
-            var lp = new Atomic.UILayoutParams();
-            lp.width = 100;
-
-            for (var i: any = 0; i < 3; i++) {
-                var select = new Atomic.UIInlineSelect();
-                editWidgets.push(select);
-                select.id = String(i + 1);
-                select.fontDescription = fd;
-                select.skinBg = "InspectorVectorAttrName";
-                select.setLimits(-10000000, 10000000);
-                var editlp = new Atomic.UILayoutParams();
-                editlp.minWidth = 60;
-                select.editFieldLayoutParams = editlp;
-                select.layoutParams = lp;
-                layout.addChild(select);
-            }
-
-        } else if (attrInfo.type == Atomic.VAR_COLOR) {
-            var layout = new Atomic.UILayout();
-            widget = layout;
-            layout.spacing = 0;
-
-            var lp = new Atomic.UILayoutParams();
-            lp.width = 70;
-
-            for (var i: any = 0; i < 4; i++) {
-
-                var select = new Atomic.UIInlineSelect();
-                editWidgets.push(select);
-                select.id = String(i + 1);
-                select.fontDescription = fd;
-                select.setLimits(-10000000, 10000000);
-                select.layoutParams = lp;
-                layout.addChild(select);
-            }
-
-        } else if (attrInfo.type == Atomic.VAR_VECTOR2) {
-            var layout = new Atomic.UILayout();
-            widget = layout;
-            layout.spacing = 0;
-
-            var lp = new Atomic.UILayoutParams();
-            lp.width = 100;
-
-            for (var i: any = 0; i < 2; i++) {
-                var select = new Atomic.UIInlineSelect();
-                editWidgets.push(select);
-                select.id = String(i + 1);
-                select.fontDescription = fd;
-                select.skinBg = "InspectorVectorAttrName";
-                select.setLimits(-10000000, 10000000);
-                var editlp = new Atomic.UILayoutParams();
-                editlp.minWidth = 60;
-                select.editFieldLayoutParams = editlp;
-                select.layoutParams = lp;
-                layout.addChild(select);
-            }
-
-        } else if (attrInfo.type == Atomic.VAR_RESOURCEREF && attrInfo.resourceTypeName) {
-
-            var importerName = ToolCore.assetDatabase.getResourceImporterName(attrInfo.resourceTypeName);
-
-            if (importerName) {
-
-                var parent = new Atomic.UILayout();
-                var o = InspectorUtils.createAttrEditFieldWithSelectButton("", parent);
-
-                parent.layoutSize = Atomic.UI_LAYOUT_SIZE_AVAILABLE;
-                parent.gravity = Atomic.UI_GRAVITY_LEFT_RIGHT;
-                parent.layoutDistribution = Atomic.UI_LAYOUT_DISTRIBUTION_GRAVITY;
-
-
-                var lp = new Atomic.UILayoutParams();
-                lp.width = 140;
-                o.editField.layoutParams = lp;
-                o.editField.readOnly = true;
-
-                // stuff editfield in so can be reference
-                parent["editField"] = o.editField;
-
-                var selectButton = o.selectButton;
-
-                selectButton.onClick = () => {
-
-                    EditorUI.getModelOps().showResourceSelection("Select " + attrInfo.resourceTypeName + " Resource", importerName, function(asset: ToolCore.Asset) {
-
-                        var resource = asset.getResource(attrInfo.resourceTypeName);
-
-                        object.setAttribute(attrInfo.name, resource);
-
-                        if (resource) {
-
-                            // use the asset name instead of the cache name
-                            if (asset.importer.requiresCacheFile())
-                                o.editField.text = asset.name;
-                            else
-                                o.editField.text = resource.name;
-                        }
-                        else
-                            o.editField.text = "";
-
-
-                    });
-
-                }
-
-                // handle dropping of component on field
-                o.editField.subscribeToEvent(o.editField, "DragEnded", (ev: Atomic.DragEndedEvent) => {
-
-                    if (ev.target == o.editField) {
-
-                        var dragObject = ev.dragObject;
-
-                        var importer;
-
-                        if (dragObject.object && dragObject.object.typeName == "Asset") {
-
-                            var asset = <ToolCore.Asset>dragObject.object;
-
-                            if (asset.importerTypeName == importerName) {
-                                importer = asset.importer;
-                            }
-
-                        }
-
-                        if (importer) {
-
-                            var resource = asset.getResource(attrInfo.resourceTypeName);
-                            object.setAttribute(attrInfo.name, resource);
-                            if (resource) {
-                                // use the asset name instead of the cache name
-                                if (asset.importer.requiresCacheFile())
-                                    o.editField.text = asset.name;
-                                else
-                                    o.editField.text = resource.name;
-                            }
-                            else
-                                o.editField.text = "";
-
-                        }
-                    }
-
-                });
-
-                widget = parent;
-            }
-
-        }
-
-        if (widget) {
-
-            var binding = new DataBinding(object, attrInfo, widget);
-            binding.enumSource = enumSource;
-
-            for (var i in editWidgets) {
-                editWidgets[i].subscribeToEvent(editWidgets[i], "UIWidgetEditComplete", (ev) => binding.handleUIWidgetEditCompleteEvent(ev));
-            }
-
-            return binding;
-
-        }
-
-        return null;
-
-    }
-
-    setWidgetValueFromObject() {
-        if (this.widgetLocked)
-            return;
-
-        this.widgetLocked = true;
-
-        var attrInfo = this.attrInfo;
-        var object = this.object;
-        var widget = this.widget;
-
-        if (attrInfo.type == Atomic.VAR_BOOL) {
-            var value = object.getAttribute(attrInfo.name);
-            widget.value = (value ? 1 : 0);
-        }
-        else if (attrInfo.type == Atomic.VAR_VECTOR3) {
-
-            var value = object.getAttribute(attrInfo.name);
-
-            for (var i = 0; i < 3; i++) {
-
-                var select = widget.getWidget((i + 1).toString());
-                if (select)
-                    select.value = value[i];
-            }
-
-        }
-        else if (attrInfo.type == Atomic.VAR_VECTOR2) {
-
-            var value = object.getAttribute(attrInfo.name);
-
-            for (var i = 0; i < 2; i++) {
-
-                var select = widget.getWidget((i + 1).toString());
-                if (select)
-                    select.value = value[i];
-            }
-
-        }
-        else if (attrInfo.type == Atomic.VAR_QUATERNION) {
-
-            var value = object.getAttribute(attrInfo.name);
-
-            for (var i = 0; i < 3; i++) {
-
-                var select = widget.getWidget((i + 1).toString());
-                if (select)
-                    select.value = value[i];
-            }
-
-        }
-        else if (attrInfo.type == Atomic.VAR_STRING) {
-            var value = object.getAttribute(attrInfo.name);
-            widget.text = value;
-        }
-        else if (attrInfo.type == Atomic.VAR_FLOAT) {
-            var value = object.getAttribute(attrInfo.name);
-            widget.text = parseFloat(value.toFixed(5)).toString();
-        }
-        else if (attrInfo.type == Atomic.VAR_INT) {
-            var value = object.getAttribute(attrInfo.name);
-
-            if (attrInfo.enumNames.length) {
-                widget.text = attrInfo.enumNames[value];
-            }
-            else {
-                widget.text = value.toString();
-            }
-        }
-        else if (attrInfo.type == Atomic.VAR_COLOR) {
-
-            var value = object.getAttribute(attrInfo.name);
-
-            for (var i = 0; i < 4; i++) {
-
-                var select = widget.getWidget((i + 1).toString());
-                if (select)
-                    select.value = value[i];
-            }
-
-        } else if (attrInfo.type == Atomic.VAR_RESOURCEREF && attrInfo.resourceTypeName) {
-
-            // for cached resources, use the asset name, otherwise use the resource path name
-            var resource = <Atomic.Resource>object.getAttribute(attrInfo.name);
-            var text = "";
-            if (resource) {
-                text = resource.name;
-                var asset = ToolCore.assetDatabase.getAssetByCachePath(resource.name);
-                if (asset)
-                    text = asset.name;
-            }
-
-            widget["editField"].text = text;
-
-        }
-
-        this.widgetLocked = false;
-
-    }
-
-    setObjectValueFromWidget(srcWidget: Atomic.UIWidget) {
-
-        if (this.objectLocked)
-            return;
-
-        this.objectLocked = true;
-
-        var widget = this.widget;
-        var object = this.object;
-        var attrInfo = this.attrInfo;
-        var type = attrInfo.type;
-
-        if (type == Atomic.VAR_BOOL) {
-
-            object.setAttribute(attrInfo.name, widget.value ? true : false);
-
-        } else if (type == Atomic.VAR_INT) {
-
-            object.setAttribute(attrInfo.name, Number(widget.text));
-
-        }
-        else if (type == Atomic.VAR_FLOAT) {
-
-            object.setAttribute(attrInfo.name, Number(widget.text));
-
-        }
-        else if (type == Atomic.VAR_STRING) {
-
-            object.setAttribute(attrInfo.name, widget.text);
-
-        } else if (type == Atomic.VAR_VECTOR3 && srcWidget) {
-
-            var value = object.getAttribute(attrInfo.name);
-
-            if (srcWidget.id == "1")
-                value[0] = srcWidget.value;
-            else if (srcWidget.id == "2")
-                value[1] = srcWidget.value;
-            else if (srcWidget.id == "3")
-                value[2] = srcWidget.value;
-
-            object.setAttribute(attrInfo.name, value);
-
-        } else if (type == Atomic.VAR_VECTOR2 && srcWidget) {
-
-            var value = object.getAttribute(attrInfo.name);
-
-            if (srcWidget.id == "1")
-                value[0] = srcWidget.value;
-            else if (srcWidget.id == "2")
-                value[1] = srcWidget.value;
-
-            object.setAttribute(attrInfo.name, value);
-
-        }
-        else if (type == Atomic.VAR_QUATERNION && srcWidget) {
-
-            var value = object.getAttribute(attrInfo.name);
-
-            if (srcWidget.id == "1")
-                value[0] = srcWidget.value;
-            else if (srcWidget.id == "2")
-                value[1] = srcWidget.value;
-            else if (srcWidget.id == "3")
-                value[2] = srcWidget.value;
-
-            object.setAttribute(attrInfo.name, value);
-
-        } else if (type == Atomic.VAR_COLOR && srcWidget) {
-
-            var value = object.getAttribute(attrInfo.name);
-
-            if (srcWidget.id == "1")
-                value[0] = srcWidget.value;
-            else if (srcWidget.id == "2")
-                value[1] = srcWidget.value;
-            else if (srcWidget.id == "3")
-                value[2] = srcWidget.value;
-            else if (srcWidget.id == "4")
-                value[3] = srcWidget.value;
-
-            object.setAttribute(attrInfo.name, value);
-        }
-
-        this.objectLocked = false;
-
-    }
-
-    handleUIWidgetEditCompleteEvent(ev) {
-
-      // TODO: once new base class stuff is in, should be able to check for type
-      var scene = this.object["scene"];
-
-      if (!scene)
-          return;
-
-      scene.sendEvent("SceneEditSerializable", { serializable: this.object, operation: 1 });
-
-    }
-
-    handleWidgetEvent(ev: Atomic.UIWidgetEvent): boolean {
-
-        if (this.objectLocked)
-            return false;
-
-        if (ev.type == Atomic.UI_EVENT_TYPE_CLICK) {
-
-            var id = this.attrInfo.name + " enum popup";
-
-            if (ev.target.id == id) {
-
-                this.object.setAttribute(this.attrInfo.name, Number(ev.refid) - 1);
-                this.setWidgetValueFromObject();
-                // TODO: once new base class stuff is in, should be able to check for type
-                if (this.object["scene"])
-                    this.object["scene"].sendEvent("SceneEditSerializable", { serializable: this.object, operation: 1 });
-
-
-            }
-
-            else if (this.widget == ev.target && this.attrInfo.enumNames.length) {
-
-
-                if (this.enumSource) {
-                    var menu = new Atomic.UIMenuWindow(ev.target, id);
-                    menu.show(this.enumSource);
-                }
-
-                return true;
-
-            }
-
-
-        }
-
-        if (ev.type == Atomic.UI_EVENT_TYPE_CHANGED) {
-
-            if (this.widget == ev.target || this.widget.isAncestorOf(ev.target)) {
-
-                this.setObjectValueFromWidget(ev.target);
-
-                // UIEditField and UIInline select changes are handled by edit complete event
-                // Otherwise, we would get multiple edit snapshots
-                if (ev.target.getTypeName() != "UIEditField" && ev.target.getTypeName() != "UIInlineSelect") {
-
-                    // TODO: once new base class stuff is in, should be able to check for type
-                    if (this.object["scene"])
-                        this.object["scene"].sendEvent("SceneEditSerializable", { serializable: this.object, operation: 1 });
-                }
-
-                return true;
-            }
-        }
-
-        return false;
-
-    }
-
-    object: Atomic.Serializable;
-    objectLocked: boolean = true;
-    widgetLocked: boolean = false;
-    attrInfo: Atomic.AttributeInfo;
-    widget: Atomic.UIWidget;
-    enumSource: Atomic.UISelectItemSource;
-
-}
-
-export = DataBinding;

+ 91 - 58
Script/AtomicEditor/ui/frames/inspector/InspectorFrame.ts

@@ -7,19 +7,26 @@
 
 import EditorEvents = require("editor/EditorEvents");
 import ScriptWidget = require("ui/ScriptWidget");
-import DataBinding = require("./DataBinding");
 
 // inspectors
 
 import MaterialInspector = require("./MaterialInspector");
 import ModelInspector = require("./ModelInspector");
-import NodeInspector = require("./NodeInspector");
 import AssemblyInspector = require("./AssemblyInspector");
 import PrefabInspector = require("./PrefabInspector");
 
+import SelectionInspector = require("./SelectionInspector");
+// make sure these are hooked in
+import "./SelectionEditTypes";
+import "./SelectionSectionCoreUI";
+
+
 class InspectorFrame extends ScriptWidget {
 
-    nodeInspector: NodeInspector;
+    scene: Atomic.Scene = null;
+    sceneEditor: Editor.SceneEditor3D;
+
+    selectionInspector: SelectionInspector;
 
     constructor() {
 
@@ -32,65 +39,119 @@ class InspectorFrame extends ScriptWidget {
         var container = this.getWidget("inspectorcontainer");
 
         this.subscribeToEvent(EditorEvents.EditResource, (data) => this.handleEditResource(data));
-        this.subscribeToEvent(EditorEvents.ActiveNodeChange, (data) => this.handleActiveNodeChange(data));
         this.subscribeToEvent("ProjectUnloaded", (data) => this.handleProjectUnloaded(data));
-        this.subscribeToEvent("NodeRemoved", (ev: Atomic.NodeRemovedEvent) => this.handleNodeRemoved(ev));
 
+        this.subscribeToEvent(EditorEvents.ActiveSceneEditorChange, (data) => this.handleActiveSceneEditorChanged(data));
 
     }
 
-    handleProjectUnloaded(data) {
+    handleActiveSceneEditorChanged(event: EditorEvents.ActiveSceneEditorChangeEvent) {
+
+        if (this.sceneEditor == event.sceneEditor) {
+            return;
+        }
+
+        if (this.scene)
+            this.unsubscribeFromEvents(this.scene);
+
+        this.closeSelectionInspector();
+
+        this.sceneEditor = null;
+        this.scene = null;
+
+        if (!event.sceneEditor)
+          return;
+
+        this.sceneEditor = event.sceneEditor;
+        this.scene = event.sceneEditor.scene;
+
+        if (this.scene) {
+
+            this.subscribeToEvent(this.scene, "SceneNodeSelected", (event: Editor.SceneNodeSelectedEvent) => this.handleSceneNodeSelected(event));
+
+            // add the current selection
+            var selection = this.sceneEditor.selection;
+
+            for (var i = 0; i < selection.getSelectedNodeCount(); i++) {
+
+                this.handleSceneNodeSelected( { node: selection.getSelectedNode(i),  scene: this.scene, selected: true, quiet: true} );
+
+            }
+
+        }
+
+    }
+
+    closeSelectionInspector() {
+
+        if (!this.selectionInspector)
+            return;
 
-        this.closeNodeInspector();
         var container = this.getWidget("inspectorcontainer");
         container.deleteAllChildren();
+
+        this.selectionInspector = null;
+
     }
 
+    handleSceneNodeSelected(ev: Editor.SceneNodeSelectedEvent) {
 
-    handleEditResource(ev: EditorEvents.EditResourceEvent) {
+        var selection = this.sceneEditor.selection;
 
-        var path = ev.path;
+        if (this.selectionInspector) {
 
-        var db = ToolCore.getAssetDatabase();
-        var asset = db.getAssetByPath(path);
+            if (ev.selected) {
+                this.selectionInspector.addNode(ev.node);
+            } else {
+                this.selectionInspector.removeNode(ev.node);
+            }
 
-        if (asset) {
+        } else if (ev.selected) {
 
-            this.inspectAsset(asset);
+            var container = this.getWidget("inspectorcontainer");
+            container.deleteAllChildren();
+
+            var inspector = this.selectionInspector = new SelectionInspector(this.sceneEditor);
+            inspector.addNode(ev.node);
+            container.addChild(inspector);
 
         }
 
+        // close last, so state is saved
+        if (!selection.selectedNodeCount) {
+            this.closeSelectionInspector();
+        }
+
     }
 
-    handleActiveNodeChange(data) {
 
-        var node = <Atomic.Node> data.node;
+    handleProjectUnloaded(data) {
 
-        if (!node) {
+        this.closeSelectionInspector();
+        var container = this.getWidget("inspectorcontainer");
+        container.deleteAllChildren();
+    }
 
-            this.closeNodeInspector();
-            return;
-        }
 
-        this.inspectNode(node);
+    handleEditResource(ev: EditorEvents.EditResourceEvent) {
+
+        var path = ev.path;
 
-    }
+        var db = ToolCore.getAssetDatabase();
+        var asset = db.getAssetByPath(path);
 
-    closeNodeInspector() {
+        if (asset) {
+
+            this.inspectAsset(asset);
 
-      if (this.nodeInspector) {
-          this.nodeInspector.saveState();
-          var container = this.getWidget("inspectorcontainer");
-          container.deleteAllChildren();
-          this.nodeInspector = null;
-      }
+        }
 
     }
 
 
     inspectAsset(asset: ToolCore.Asset) {
 
-        this.sendEvent(EditorEvents.ActiveNodeChange, {node:null});
+        this.closeSelectionInspector();
 
         var container = this.getWidget("inspectorcontainer");
         container.deleteAllChildren();
@@ -108,7 +169,7 @@ class InspectorFrame extends ScriptWidget {
 
             var cache = Atomic.getResourceCache();
 
-            var material = <Atomic.Material> cache.getResource("Material", asset.path);
+            var material = <Atomic.Material>cache.getResource("Material", asset.path);
 
             if (!material) {
                 return;
@@ -139,34 +200,6 @@ class InspectorFrame extends ScriptWidget {
 
     }
 
-    handleNodeRemoved(ev: Atomic.NodeRemovedEvent) {
-
-        if (this.nodeInspector && this.nodeInspector.node != ev.node)
-            return;
-
-        this.closeNodeInspector();
-
-    }
-
-
-    inspectNode(node: Atomic.Node) {
-
-        if (!node) return;
-
-        this.closeNodeInspector();
-
-        var container = this.getWidget("inspectorcontainer");
-        container.deleteAllChildren();
-
-        var inspector = new NodeInspector();
-        container.addChild(inspector);
-
-        inspector.inspect(node);
-
-        this.nodeInspector = inspector;
-
-    }
-
 }
 
 export = InspectorFrame;

+ 0 - 450
Script/AtomicEditor/ui/frames/inspector/NodeInspector.ts

@@ -1,450 +0,0 @@
-//
-// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
-// LICENSE: Atomic Game Engine Editor and Tools EULA
-// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
-// license information: https://github.com/AtomicGameEngine/AtomicGameEngine
-//
-
-import ScriptWidget = require("ui/ScriptWidget");
-import ComponentInspector = require("./ComponentInspector");
-import DataBinding = require("./DataBinding");
-import CreateComponentButton = require("./CreateComponentButton");
-
-interface ComponentState {
-
-    expanded: boolean;
-
-}
-
-interface NodeState {
-
-    expanded: boolean;
-    componentStates: { [id: number]: ComponentState };
-
-}
-
-class NodeInspector extends ScriptWidget {
-
-    constructor() {
-
-        super();
-
-        this.subscribeToEvent(this, "WidgetEvent", (ev) => this.handleWidgetEvent(ev));
-        this.subscribeToEvent("GizmoMoved", (ev) => this.handleGizmoModed(ev));
-        this.subscribeToEvent("Update", (ev) => this.handleUpdate(ev));
-
-    }
-
-    handleUpdate(ev) {
-
-        // to keep from spamming UI update we have a little delta on the gizmo updates
-        if (!this.node) {
-            this.gizmoMoved = false;
-            return;
-        }
-
-        if (this.gizmoMoved) {
-
-            if (this.updateDelta > 1.0) {
-
-                this.updateDelta = 0.0;
-
-            }
-            else {
-
-                this.updateDelta -= ev.timeStep;
-
-            }
-
-            if (this.updateDelta <= 0) {
-
-                for (var i in this.bindings) {
-
-                    this.bindings[i].setWidgetValueFromObject();
-
-                }
-
-                this.gizmoMoved = false;
-                this.updateDelta = 0;
-
-            }
-
-
-        }
-
-    }
-
-
-    handleGizmoModed(ev) {
-
-        if (!this.node) return;
-
-        this.gizmoMoved = true;
-
-        this.updateDelta += .3;
-
-    }
-
-    handleWidgetEvent(ev: Atomic.UIWidgetEvent) {
-
-        var handled = false;
-
-        for (var i = 0; i < this.bindings.length; i++) {
-
-            if (this.bindings[i].handleWidgetEvent(ev)) {
-
-                handled = true;
-
-            }
-
-        }
-
-        // return handled
-        return handled;
-
-    }
-
-    getPrefabComponent(node: Atomic.Node): Atomic.PrefabComponent {
-
-        if (node.getComponent("PrefabComponent"))
-            return <Atomic.PrefabComponent>node.getComponent("PrefabComponent");
-
-        if (node.parent)
-            return this.getPrefabComponent(node.parent);
-
-        return null;
-
-    }
-
-    detectPrefab(node: Atomic.Node): boolean {
-
-        if (node.getComponent("PrefabComponent"))
-            return true;
-
-        if (node.parent)
-            return this.detectPrefab(node.parent);
-
-        return false;
-
-    }
-
-    inspect(node: Atomic.Node) {
-
-        this.bindings = new Array();
-
-        this.node = node;
-
-        node.scene.sendEvent("SceneEditSerializable", { serializable: node, operation: 0 });
-        this.subscribeToEvent(node, "SceneEditSerializableUndoRedo", (data) => this.handleSceneEditSerializableUndoRedoEvent(data));
-
-        this.isPrefab = this.detectPrefab(node);
-
-        var fd = new Atomic.UIFontDescription();
-        fd.id = "Vera";
-        fd.size = 11;
-
-        var nlp = new Atomic.UILayoutParams();
-        nlp.width = 304;
-
-        var nodeLayout = this.nodeLayout = new Atomic.UILayout();
-        nodeLayout.spacing = 4;
-
-        nodeLayout.layoutDistribution = Atomic.UI_LAYOUT_DISTRIBUTION_GRAVITY;
-        nodeLayout.layoutPosition = Atomic.UI_LAYOUT_POSITION_LEFT_TOP;
-        nodeLayout.layoutParams = nlp;
-        nodeLayout.axis = Atomic.UI_AXIS_Y;
-
-        // node attr layout
-
-        var nodeSection = new Atomic.UISection();
-        nodeSection.id = "node_section";
-        nodeSection.text = "Node";
-        nodeSection.value = 1;
-        nodeLayout.addChild(nodeSection);
-
-        var attrsVerticalLayout = new Atomic.UILayout(Atomic.UI_AXIS_Y);
-        attrsVerticalLayout.spacing = 3;
-        attrsVerticalLayout.layoutPosition = Atomic.UI_LAYOUT_POSITION_LEFT_TOP;
-        attrsVerticalLayout.layoutSize = Atomic.UI_LAYOUT_SIZE_AVAILABLE;
-
-        nodeSection.contentRoot.addChild(attrsVerticalLayout);
-
-        var attrs = node.getAttributes();
-
-        for (var i in attrs) {
-
-            var attr = <Atomic.AttributeInfo>attrs[i];
-
-            if (attr.mode & Atomic.AM_NOEDIT)
-                continue;
-
-            var binding = DataBinding.createBinding(node, attr);
-
-            if (!binding)
-                continue;
-
-            var attrLayout = new Atomic.UILayout();
-
-            attrLayout.layoutDistribution = Atomic.UI_LAYOUT_DISTRIBUTION_GRAVITY;
-
-            var name = new Atomic.UITextField();
-            name.textAlign = Atomic.UI_TEXT_ALIGN_LEFT;
-            name.skinBg = "InspectorTextAttrName";
-
-            if (attr.type == Atomic.VAR_VECTOR3 || attr.type == Atomic.VAR_COLOR ||
-                attr.type == Atomic.VAR_QUATERNION) {
-                attrLayout.axis = Atomic.UI_AXIS_Y;
-                attrLayout.layoutPosition = Atomic.UI_LAYOUT_POSITION_LEFT_TOP;
-                attrLayout.skinBg = "InspectorVectorAttrLayout";
-            }
-
-
-            var bname = attr.name;
-
-            if (bname == "Is Enabled")
-                bname = "Enabled";
-
-            name.text = bname;
-            name.fontDescription = fd;
-
-            attrLayout.addChild(name);
-
-            attrLayout.addChild(binding.widget);
-
-            attrsVerticalLayout.addChild(attrLayout);
-
-            this.bindings.push(binding);
-
-        }
-
-        // PREFAB
-
-        if (this.isPrefab) {
-
-            var name = new Atomic.UITextField();
-            name.textAlign = Atomic.UI_TEXT_ALIGN_LEFT;
-            name.skinBg = "InspectorTextAttrName";
-            name.text = "Prefab"
-            name.fontDescription = fd;
-
-            var prefabLayout = new Atomic.UILayout();
-            prefabLayout.layoutDistribution = Atomic.UI_LAYOUT_DISTRIBUTION_GRAVITY;
-
-            var saveButton = new Atomic.UIButton();
-            saveButton.text = "Save";
-            saveButton.fontDescription = fd;
-
-            saveButton.onClick = () => {
-
-                var prefabComponent = this.getPrefabComponent(this.node);
-
-                if (prefabComponent) {
-
-                    prefabComponent.savePrefab();
-
-                    this.sendEvent("EditorActiveNodeChange", { node: this.node });
-
-                    return true;
-
-                }
-
-            }
-
-            var undoButton = new Atomic.UIButton();
-            undoButton.text = "Undo";
-            undoButton.fontDescription = fd;
-
-            undoButton.onClick = () => {
-
-                var prefabComponent = this.getPrefabComponent(this.node);
-
-                if (prefabComponent) {
-
-                    prefabComponent.undoPrefab();
-
-                    this.sendEvent("EditorActiveNodeChange", { node: this.node });
-
-                    return true;
-
-                }
-
-            }
-
-            var breakButton = new Atomic.UIButton();
-            breakButton.text = "Break";
-            breakButton.fontDescription = fd;
-
-            breakButton.onClick = () => {
-
-                var prefabComponent = this.getPrefabComponent(this.node);
-
-                if (prefabComponent) {
-
-                    prefabComponent.breakPrefab();
-
-                    this.sendEvent("EditorActiveNodeChange", { node: this.node });
-
-                    return true;
-
-                }
-
-            }
-
-
-            prefabLayout.addChild(name);
-            prefabLayout.addChild(saveButton);
-            prefabLayout.addChild(undoButton);
-            prefabLayout.addChild(breakButton);
-
-            attrsVerticalLayout.addChild(prefabLayout);
-
-        }
-
-        // COMPONENTS
-
-        var components = node.getComponents();
-
-        for (var i in components) {
-
-            var component = components[i];
-
-            //if (component.isTemporary())
-            //  continue;
-
-            var ci = new ComponentInspector();
-            ci.id = "component_section_" + component.id;
-
-            ci.inspect(component);
-
-            nodeLayout.addChild(ci);
-
-        }
-
-        this.addChild(nodeLayout);
-
-        var button = new CreateComponentButton(node);
-
-        nodeLayout.addChild(button);
-
-        for (var i in this.bindings) {
-            this.bindings[i].setWidgetValueFromObject();
-            this.bindings[i].objectLocked = false;
-        }
-
-        this.loadState();
-
-    }
-
-    handleSceneEditSerializableUndoRedoEvent(ev) {
-
-        for (var i in this.bindings) {
-            this.bindings[i].objectLocked = true;
-            this.bindings[i].setWidgetValueFromObject();
-            this.bindings[i].objectLocked = false;
-        }
-
-    }
-
-    saveState() {
-
-        var node = this.node;
-
-        if (!node.scene)
-            return;
-
-        var nodeStates = NodeInspector.nodeStates[node.scene.id];
-
-        if (!nodeStates)
-            return;
-
-        var state = nodeStates[node.id];
-
-        if (!state) {
-
-            state = nodeStates[node.id] = { expanded: true, componentStates: {} };
-
-        }
-
-        var section: Atomic.UISection = <Atomic.UISection>this.nodeLayout.getWidget("node_section");
-
-        state.expanded = section.value ? true : false;
-
-        var components = node.getComponents();
-
-        for (var i in components) {
-
-            var component = components[i];
-            var cstate = state.componentStates[component.id];
-
-            if (!cstate) {
-                cstate = state.componentStates[component.id] = { expanded: false };
-            }
-
-            section = <Atomic.UISection>this.nodeLayout.getWidget("component_section_" + component.id);
-
-            if (section)
-                cstate.expanded = section.value ? true : false;
-
-        }
-
-    }
-
-    loadState() {
-
-        var node = this.node;
-
-        // lookup in node states via scene id
-        var nodeStates = NodeInspector.nodeStates[node.scene.id];
-
-        if (!nodeStates) {
-            nodeStates = NodeInspector.nodeStates[node.scene.id] = {};
-        }
-
-        // lookup by node id
-        var state = nodeStates[node.id];
-
-        if (!state) {
-
-            // we don't have a state, so save default state
-            this.saveState();
-
-        } else {
-
-            var section: Atomic.UISection = <Atomic.UISection>this.nodeLayout.getWidget("node_section");
-
-            section.value = state.expanded ? 1 : 0;
-
-            var components = node.getComponents();
-
-            for (var i in components) {
-
-                var component = components[i];
-
-                var cstate = state.componentStates[component.id];
-                section = <Atomic.UISection>this.nodeLayout.getWidget("component_section_" + component.id);
-
-                if (cstate && section) {
-
-                    section.value = cstate.expanded ? 1 : 0;
-
-                }
-
-            }
-
-        }
-
-    }
-
-
-    isPrefab: boolean;
-    node: Atomic.Node;
-    nodeLayout: Atomic.UILayout;
-    bindings: Array<DataBinding>;
-    gizmoMoved = false;
-    updateDelta = 0;
-
-    static nodeStates: { [sceneID: number]: { [nodeId: number]: NodeState } } = {};
-
-}
-
-export = NodeInspector;

+ 38 - 0
Script/AtomicEditor/ui/frames/inspector/SelectionEditTypes.ts

@@ -0,0 +1,38 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// LICENSE: Atomic Game Engine Editor and Tools EULA
+// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
+// license information: https://github.com/AtomicGameEngine/AtomicGameEngine
+//
+
+import SerializableEditType = require("./SerializableEditType");
+import SelectionInspector = require("./SelectionInspector");
+
+class JSComponentEditType extends SerializableEditType {
+
+    compareTypes(otherType: SerializableEditType, multiSelect: boolean = false): boolean {
+
+        if (this.typeName != otherType.typeName) {
+            return false;
+        }
+
+        if (!multiSelect)
+            return false;
+
+        var jsc1 = <Atomic.JSComponent>(otherType.objects[0]);
+        var jsc2 = <Atomic.JSComponent>(this.objects[0]);
+
+        return jsc1.componentFile == jsc2.componentFile;
+
+    }
+
+    private static Ctor = (() => {
+
+        SelectionInspector.registerEditType("JSComponent", JSComponentEditType);
+
+    })();
+
+
+}
+
+export = JSComponentEditType;

+ 728 - 0
Script/AtomicEditor/ui/frames/inspector/SelectionInspector.ts

@@ -0,0 +1,728 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// LICENSE: Atomic Game Engine Editor and Tools EULA
+// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
+// license information: https://github.com/AtomicGameEngine/AtomicGameEngine
+//
+
+import EditorUI = require("../../EditorUI");
+import CreateComponentButton = require("./CreateComponentButton");
+import ScriptWidget = require("ui/ScriptWidget");
+import EditorEvents = require("editor/EditorEvents");
+import SerializableEditType = require("./SerializableEditType");
+import SelectionSection = require("./SelectionSection");
+import SelectionPrefabWidget = require("./SelectionPrefabWidget");
+import AttributeInfoEdit = require("./AttributeInfoEdit");
+
+class NodeSection extends SelectionSection {
+
+    prefabWidget: SelectionPrefabWidget;
+
+    transformEdits: AttributeInfoEdit[] = [];
+
+    updateDelta: number = 0.0;
+
+    constructor(editType: SerializableEditType) {
+
+        super(editType);
+
+        this.prefabWidget = new SelectionPrefabWidget();
+        this.attrLayout.addChild(this.prefabWidget);
+
+        this.transformEdits.push(this.attrEdits["Position"]);
+        this.transformEdits.push(this.attrEdits["Rotation"]);
+        this.transformEdits.push(this.attrEdits["Scale"]);
+
+        this.subscribeToEvent("Update", (ev) => this.handleUpdate(ev));
+
+    }
+
+    handleUpdate(ev) {
+
+        this.updateDelta -= ev.timeStep;
+
+        if (this.updateDelta < 0.0) {
+
+            this.updateDelta = 0.1;
+
+            Atomic.ui.blockChangedEvents = true;
+
+            for (var i in this.transformEdits) {
+                this.transformEdits[i].refresh();
+            }
+
+            Atomic.ui.blockChangedEvents = false;
+
+        }
+
+
+    }
+
+
+}
+
+class ComponentSection extends SelectionSection {
+
+    constructor(editType: SerializableEditType, inspector: SelectionInspector) {
+
+        super(editType);
+
+        var deleteButton = new Atomic.UIButton();
+        deleteButton.text = "Delete Component";
+        deleteButton.fontDescription = SelectionSection.fontDesc;
+
+        deleteButton.onClick = () => {
+
+            inspector.onComponentDelete(editType);
+            return true;
+
+        }
+
+        this.attrLayout.addChild(deleteButton);;
+
+    }
+
+}
+
+class SceneSection extends SelectionSection {
+
+    constructor(editType: SerializableEditType) {
+
+        super(editType);
+
+    }
+
+}
+
+interface AttributeEditResourceChangedEvent {
+
+    attrInfoEdit: AttributeInfoEdit;
+    resource: Atomic.Resource;
+
+}
+
+class JSComponentSection extends ComponentSection {
+
+    constructor(editType: SerializableEditType, inspector: SelectionInspector) {
+
+        super(editType, inspector);
+
+        this.hasDynamicAttr = true;
+
+        this.subscribeToEvent(this, "AttributeEditResourceChanged", (ev) => this.handleAttributeEditResourceChanged(ev));
+
+    }
+
+    private handleAttributeEditResourceChanged(ev: AttributeEditResourceChangedEvent) {
+
+        var jsc = <Atomic.JSComponent>this.editType.getFirstObject();
+
+        if (!jsc)
+            return;
+
+        var attrInfos = jsc.getAttributes();
+        this.updateDynamicAttrInfos(attrInfos);
+
+    }
+
+}
+
+
+// Node Inspector + Component Inspectors
+
+class SelectionInspector extends ScriptWidget {
+
+    constructor(sceneEditor: Editor.SceneEditor3D) {
+
+        super();
+
+        this.sceneEditor = sceneEditor;
+
+        var mainLayout = this.mainLayout = new Atomic.UILayout();
+        mainLayout.spacing = 4;
+
+        var lp = new Atomic.UILayoutParams();
+        lp.width = 304;
+
+        mainLayout.layoutDistribution = Atomic.UI_LAYOUT_DISTRIBUTION_GRAVITY;
+        mainLayout.layoutPosition = Atomic.UI_LAYOUT_POSITION_LEFT_TOP;
+        mainLayout.layoutParams = lp;
+        mainLayout.axis = Atomic.UI_AXIS_Y;
+
+        this.addChild(mainLayout);
+
+        var noticeLayout = this.multipleSelectNotice = new Atomic.UILayout();
+        noticeLayout.axis = Atomic.UI_AXIS_Y;
+        noticeLayout.layoutParams = lp;
+        var noticeText = new Atomic.UITextField();
+        noticeText.textAlign = Atomic.UI_TEXT_ALIGN_CENTER;
+        noticeText.skinBg = "InspectorTextAttrName";
+        noticeText.text = "Multiple Selection - Some components are hidden";
+        noticeText.fontDescription = SelectionSection.fontDesc;
+        noticeText.gravity = Atomic.UI_GRAVITY_LEFT_RIGHT;
+        noticeText.layoutParams = lp;
+        noticeLayout.addChild(noticeText);
+        noticeLayout.visibility = Atomic.UI_WIDGET_VISIBILITY_GONE;
+        mainLayout.addChild(noticeLayout);
+
+        this.createComponentButton = new CreateComponentButton();
+        mainLayout.addChild(this.createComponentButton);
+
+        this.subscribeToEvent(sceneEditor.scene, "SceneEditStateChangesBegin", (data) => this.handleSceneEditStateChangesBeginEvent());
+        this.subscribeToEvent("SceneEditStateChange", (data) => this.handleSceneEditStateChangeEvent(data));
+        this.subscribeToEvent(sceneEditor.scene, "SceneEditStateChangesEnd", (data) => this.handleSceneEditStateChangesEndEvent());
+
+        this.subscribeToEvent(sceneEditor.scene, "SceneEditNodeRemoved", (ev: Editor.SceneEditNodeRemovedEvent) => this.handleSceneEditNodeRemoved(ev));
+        this.subscribeToEvent(sceneEditor.scene, "SceneEditComponentAddedRemoved", (ev) => this.handleSceneEditComponentAddedRemovedEvent(ev));
+
+        this.subscribeToEvent(this.createComponentButton, "SelectionCreateComponent", (data) => this.handleSelectionCreateComponent(data));
+
+    }
+
+    pruneSections() {
+
+        var remove: SelectionSection[] = [];
+
+        for (var i in this.sections) {
+
+            var section = this.sections[i];
+
+            var editType = section.editType;
+
+            if (editType.typeName == "Node") {
+                continue;
+            }
+
+            if (editType.typeName == "Scene") {
+                var gotone = false;
+                for (var j in this.nodes) {
+                    if (this.nodes[j].typeName == "Scene") {
+                        gotone = true;
+                        break;
+                    }
+                }
+                if (gotone)
+                    continue;
+            }
+
+            if (!editType.nodes.length) {
+
+                remove.push(section);
+
+            }
+
+        }
+
+        if (remove.length) {
+
+            for (var i in remove) {
+
+                var section = remove[i];
+                this.removeSection(section);
+
+            }
+
+            this.suppressSections();
+        }
+
+    }
+
+    suppressSections() {
+
+        this.multipleSelectNotice.visibility = Atomic.UI_WIDGET_VISIBILITY_GONE;
+
+        for (var i in this.sections) {
+
+            var section = this.sections[i];
+            var editType = section.editType;
+
+            if (editType.typeName == "Node" || editType.typeName == "Scene") {
+                continue;
+            }
+
+            var suppressed = false;
+
+            for (var j in this.nodes) {
+                if (editType.nodes.indexOf(this.nodes[j]) == -1) {
+                    suppressed = true;
+                    break;
+                }
+            }
+
+            if (suppressed)
+                this.multipleSelectNotice.visibility = Atomic.UI_WIDGET_VISIBILITY_VISIBLE;
+
+            section.suppress(suppressed);
+
+        }
+
+    }
+
+    refresh() {
+
+        Atomic.ui.blockChangedEvents = true;
+
+        this.pruneSections();
+        this.suppressSections();
+
+        for (var i in this.sections) {
+
+            this.sections[i].refresh();
+
+        }
+
+        if (this.nodeSection) {
+            this.nodeSection.prefabWidget.updateSelection(this.nodes);
+        }
+
+        Atomic.ui.blockChangedEvents = false;
+
+    }
+
+    addSection(editType: SerializableEditType) {
+
+        var section: SelectionSection;
+
+        if (editType.typeName == "Node") {
+
+            this.nodeSection = new NodeSection(editType);
+            section = this.nodeSection;
+
+        } else if (editType.typeName == "Scene") {
+
+            section = new SceneSection(editType);
+
+        } else if (editType.typeName == "JSComponent") {
+            section = new JSComponentSection(editType, this);
+        }
+        else {
+
+            section = new ComponentSection(editType, this);
+
+        }
+
+        section.value = SelectionInspector.sectionStates[editType.typeName] ? 1 : 0;
+
+        this.mainLayout.removeChild(this.createComponentButton, false);
+        this.mainLayout.removeChild(this.multipleSelectNotice, false);
+
+        // sort it in alphabetically
+
+        this.sections.push(section);
+
+        this.sections.sort(function(a, b) {
+
+            if (a.editType.typeName == "Node" && b.editType.typeName == "Scene")
+                return -1;
+
+            if (a.editType.typeName == "Scene" && b.editType.typeName == "Node")
+                return 1;
+
+            if (a.editType.typeName == "Node" || a.editType.typeName == "Scene")
+                return -1;
+
+            if (b.editType.typeName == "Node" || b.editType.typeName == "Scene")
+                return 1;
+
+            return a.editType.typeName.localeCompare(b.editType.typeName);
+        });
+
+        var idx = this.sections.indexOf(section);
+
+        if (idx == 0) {
+
+            if (this.sections.length == 1) {
+                this.mainLayout.addChild(section);
+            } else {
+                this.mainLayout.addChildBefore(section, this.sections[1]);
+            }
+        }
+        else if (idx == this.sections.length - 1) {
+
+            this.mainLayout.addChild(section);
+        }
+        else {
+            this.mainLayout.addChildAfter(section, this.sections[idx - 1]);
+        }
+
+        // move the create component button down
+
+        this.mainLayout.addChild(this.multipleSelectNotice);
+        this.mainLayout.addChild(this.createComponentButton);
+
+    }
+
+    removeSection(section: SelectionSection) {
+
+        SelectionInspector.sectionStates[section.editType.typeName] = section.value ? true : false;
+        var index = this.sections.indexOf(section);
+        this.sections.splice(index, 1);
+        this.mainLayout.removeChild(section);
+
+    }
+
+    removeSerializable(serial: Atomic.Serializable) {
+
+        for (var i in this.sections) {
+
+            var section = this.sections[i];
+
+            var e = section.editType;
+
+            var index = e.objects.indexOf(serial);
+
+            if (index != -1) {
+
+                e.objects.splice(index, 1);
+
+            }
+
+            if (serial.typeName == "Node") {
+
+                index = e.nodes.indexOf(<Atomic.Node>serial);
+
+                if (index != -1) {
+
+                    e.nodes.splice(index, 1);
+
+                }
+            }
+
+        }
+
+    }
+
+    addSerializable(serial: Atomic.Serializable): SerializableEditType {
+
+        var editType = this.getEditType(serial);
+
+        // does it already exist?
+        for (var i in this.sections) {
+
+            var section = this.sections[i];
+
+            var e = section.editType;
+
+            if (e.compareTypes(editType, this.nodes.length > 1)) {
+                e.addSerializable(serial);
+                return e;
+            }
+
+        }
+
+        this.addSection(editType);
+
+        return editType;
+
+    }
+
+    getEditType(serial: Atomic.Serializable): SerializableEditType {
+
+        var typeName = serial.typeName;
+
+
+        if (SelectionInspector._editTypes[typeName]) {
+            return new SelectionInspector._editTypes[typeName](serial);
+        }
+
+        return new SerializableEditType(serial);
+
+    }
+
+    addNode(node: Atomic.Node) {
+
+        var index = this.nodes.indexOf(node);
+
+        if (index == -1) {
+            this.nodes.push(node);
+            this.addSerializable(node);
+            var components = node.getComponents();
+            for (var i in components) {
+
+                if (this.filterComponent(components[i]))
+                    continue;
+
+                var editType = this.addSerializable(components[i]);
+                editType.addNode(node);
+            }
+            this.refresh();
+        }
+    }
+
+    removeNode(node: Atomic.Node) {
+
+        var index = this.nodes.indexOf(node);
+
+        if (index != -1) {
+
+            this.nodes.splice(index, 1);
+            this.removeSerializable(node);
+            var components = node.getComponents();
+            for (var i in components) {
+
+                if (this.filterComponent(components[i]))
+                    continue;
+
+                this.removeSerializable(components[i]);
+            }
+
+            this.refresh();
+        }
+
+        // save node section state
+        if (!this.nodes.length && this.nodeSection)
+            SelectionInspector.sectionStates["Node"] = this.nodeSection.value ? true : false;
+
+    }
+
+    handleSceneEditStateChangesBeginEvent() {
+
+        this.stateChangesInProgress = true;
+
+    }
+
+    handleSceneEditNodeRemoved(ev: Editor.SceneEditNodeRemovedEvent) {
+
+        this.removeNode(ev.node);
+
+    }
+
+    handleSceneEditComponentAddedRemovedEvent(ev: Editor.SceneEditComponentAddedRemovedEvent) {
+
+        if (this.filterComponent(ev.component)) {
+            // still refresh as may affect UI (for example PrefabComponents)
+            this.refresh();
+            return;
+        }
+
+        if (!ev.removed) {
+
+            editType = this.addSerializable(ev.component);
+            editType.addNode(ev.node);
+
+        } else {
+
+            for (var i in this.sections) {
+
+                var section = this.sections[i];
+                var editType = section.editType;
+
+                var index = editType.objects.indexOf(ev.component);
+                if (index != -1) {
+
+                    editType.objects.splice(index, 1);
+                    index = editType.nodes.indexOf(ev.node);
+                    if (index != -1) {
+                        editType.nodes.splice(index, 1);
+                    }
+                    break;
+
+                }
+
+            }
+
+        }
+
+        this.refresh();
+
+    }
+
+    onComponentDelete(editType: SerializableEditType) {
+
+        var removed: Atomic.Component[] = [];
+
+        for (var i in editType.objects) {
+
+            var c = <Atomic.Component>editType.objects[i];
+            removed.push(c);
+
+        }
+
+        for (var i in removed) {
+
+            var c = removed[i];
+
+            var node = c.node;
+            c.remove();
+
+            this.removeSerializable(removed[i]);
+
+            var index = editType.nodes.indexOf(node);
+            if (index != -1) {
+                editType.nodes.splice(index, 1);
+            }
+
+        }
+
+        if (removed.length) {
+
+            this.sceneEditor.scene.sendEvent("SceneEditEnd");
+            this.refresh();
+
+        }
+
+    }
+
+    handleSelectionCreateComponent(ev) {
+
+        var valid = true;
+
+        if (ev.componentTypeName != "JSComponent") {
+
+            for (var i in this.nodes) {
+
+                var node = this.nodes[i];
+                if (node.getComponent(ev.componentTypeName, false)) {
+                    valid = false;
+                    break;
+                }
+            }
+        }
+
+        if (!valid) {
+
+            EditorUI.showModalError("Component Create", "Unable to create component, a node with an existing " + ev.componentTypeName + " component is selected");
+            return;
+
+        }
+
+        for (var i in this.nodes) {
+
+            var node = this.nodes[i];
+
+            var c = node.createComponent(ev.componentTypeName);
+
+            if (!c) {
+                console.log("ERROR: unable to create component ", ev.componentTypeName);
+                return;
+            }
+
+            var editType = this.addSerializable(c);
+            editType.addNode(node);
+
+            for (var i in this.sections) {
+                if (this.sections[i].editType == editType) {
+                    this.sections[i].value = 1;
+                    break;
+                }
+
+            }
+
+        }
+
+        this.refresh();
+
+    }
+
+
+    handleSceneEditStateChangeEvent(ev: Editor.SceneEditStateChangeEvent) {
+
+        if (!this.stateChangesInProgress)
+            return;
+
+        if (this.stateChanges.indexOf(ev.serializable) == -1) {
+            this.stateChanges.push(ev.serializable);
+        }
+
+    }
+
+    getPrefabComponent(node: Atomic.Node): Atomic.PrefabComponent {
+
+        if (node.getComponent("PrefabComponent"))
+            return <Atomic.PrefabComponent>node.getComponent("PrefabComponent");
+
+        if (node.parent)
+            return this.getPrefabComponent(node.parent);
+
+        return null;
+
+    }
+
+    filterComponent(component: Atomic.Component): boolean {
+
+        if (component.typeName == "PrefabComponent") {
+            return true;
+        }
+
+        return false;
+
+    }
+
+    handleSceneEditStateChangesEndEvent() {
+
+        Atomic.ui.blockChangedEvents = true;
+
+        var sections: SelectionSection[] = [];
+
+        for (var i in this.stateChanges) {
+
+            var serial = this.stateChanges[i];
+
+            for (var j in this.sections) {
+
+                var section = this.sections[j];
+
+                if (sections.indexOf(section) != -1)
+                    continue;
+
+                if (section.editType.objects.indexOf(serial) != -1) {
+
+                    sections.push(section);
+
+                    if (section.hasDynamicAttr) {
+
+                        var object = section.editType.getFirstObject();
+                        if (object) {
+                            var attrInfos = object.getAttributes();
+                            section.updateDynamicAttrInfos(attrInfos);
+                        }
+                    }
+
+                    section.refresh();
+                }
+
+            }
+
+        }
+
+        Atomic.ui.blockChangedEvents = false;
+        this.stateChanges = [];
+        this.stateChangesInProgress = false;
+
+    }
+
+
+    mainLayout: Atomic.UILayout;
+
+    multipleSelectNotice: Atomic.UILayout;
+
+    sceneEditor: Editor.SceneEditor3D;
+    nodes: Atomic.Node[] = [];
+    sections: SelectionSection[] = [];
+
+    createComponentButton: CreateComponentButton;
+    nodeSection: NodeSection;
+
+    stateChangesInProgress: boolean = false;
+    stateChanges: Atomic.Serializable[] = [];
+
+    // ------------------------------------
+
+    static registerEditType(typeName: string, type: typeof SerializableEditType) {
+
+        SelectionInspector._editTypes[typeName] = type;
+
+    }
+
+    private static sectionStates: { [typeName: string]: boolean } = {};
+    private static _editTypes: { [typeName: string]: typeof SerializableEditType } = {};
+
+    private static Ctor = (() => {
+
+        SelectionInspector.sectionStates["Node"] = true;
+
+    })();
+
+}
+
+export = SelectionInspector;

+ 144 - 0
Script/AtomicEditor/ui/frames/inspector/SelectionPrefabWidget.ts

@@ -0,0 +1,144 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// LICENSE: Atomic Game Engine Editor and Tools EULA
+// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
+// license information: https://github.com/AtomicGameEngine/AtomicGameEngine
+//
+
+
+class SelectionPrefabWidget extends Atomic.UILayout {
+
+    widgetLayout: Atomic.UILayout;
+    noticeLayout: Atomic.UILayout;
+    node: Atomic.Node;
+
+    constructor() {
+
+        super();
+
+        var fd = new Atomic.UIFontDescription();
+        fd.id = "Vera";
+        fd.size = 11;
+
+        var widgetLayout = this.widgetLayout = new Atomic.UILayout();
+        var noticeLayout = this.noticeLayout = new Atomic.UILayout();
+
+        this.axis = Atomic.UI_AXIS_Y;
+        widgetLayout.layoutDistribution = Atomic.UI_LAYOUT_DISTRIBUTION_GRAVITY;
+        noticeLayout.layoutDistribution = Atomic.UI_LAYOUT_DISTRIBUTION_GRAVITY;
+
+        var name = new Atomic.UITextField();
+        name.textAlign = Atomic.UI_TEXT_ALIGN_LEFT;
+        name.skinBg = "InspectorTextAttrName";
+        name.text = "Prefab"
+        name.fontDescription = fd;
+
+        var saveButton = new Atomic.UIButton();
+        saveButton.text = "Save";
+        saveButton.fontDescription = fd;
+
+        saveButton.onClick = () => {
+
+            this.node.scene.sendEvent("SceneEditPrefabSave", {node : this.node});
+            return true;
+        }
+
+        var undoButton = new Atomic.UIButton();
+        undoButton.text = "Revert";
+        undoButton.fontDescription = fd;
+
+        undoButton.onClick = () => {
+
+            this.node.scene.sendEvent("SceneEditPrefabRevert", {node : this.node});
+            return true;
+
+        }
+
+        var breakButton = new Atomic.UIButton();
+        breakButton.text = "Break";
+        breakButton.fontDescription = fd;
+
+        breakButton.onClick = () => {
+
+            this.node.scene.sendEvent("SceneEditPrefabBreak", {node : this.node});
+            return true;
+        }
+
+        var noticeName = new Atomic.UITextField();
+        noticeName.textAlign = Atomic.UI_TEXT_ALIGN_LEFT;
+        noticeName.skinBg = "InspectorTextAttrName";
+        noticeName.text = "Prefab"
+        noticeName.fontDescription = fd;
+
+        var noticeText = new Atomic.UITextField();
+        noticeText.textAlign = Atomic.UI_TEXT_ALIGN_LEFT;
+        noticeText.skinBg = "InspectorTextAttrName";
+        noticeText.text = "Multiple Selection"
+        noticeText.fontDescription = fd;
+
+        noticeLayout.addChild(noticeName);
+        noticeLayout.addChild(noticeText);
+
+        widgetLayout.addChild(name);
+        widgetLayout.addChild(saveButton);
+        widgetLayout.addChild(undoButton);
+        widgetLayout.addChild(breakButton);
+
+        this.addChild(this.widgetLayout);
+        this.addChild(this.noticeLayout);
+
+        this.visibility = Atomic.UI_WIDGET_VISIBILITY_GONE;
+
+    }
+
+    detectPrefab(node: Atomic.Node): boolean {
+
+        if (node.getComponent("PrefabComponent"))
+            return true;
+
+        if (node.parent)
+            return this.detectPrefab(node.parent);
+
+        return false;
+
+    }
+
+
+    updateSelection(nodes: Atomic.Node[]) {
+
+        var hasPrefab = false;
+        this.node = null;
+
+        for (var i in nodes) {
+
+            var node = nodes[i];
+            if (this.detectPrefab(node)) {
+                hasPrefab = true;
+                break;
+            }
+
+        }
+
+        if (!hasPrefab) {
+            this.visibility = Atomic.UI_WIDGET_VISIBILITY_GONE;
+            return;
+        }
+
+        this.visibility = Atomic.UI_WIDGET_VISIBILITY_VISIBLE;
+
+        if (nodes.length > 1) {
+            this.noticeLayout.visibility = Atomic.UI_WIDGET_VISIBILITY_VISIBLE;
+            this.widgetLayout.visibility = Atomic.UI_WIDGET_VISIBILITY_GONE;
+            return;
+        }
+
+        this.noticeLayout.visibility = Atomic.UI_WIDGET_VISIBILITY_GONE;
+        this.widgetLayout.visibility = Atomic.UI_WIDGET_VISIBILITY_VISIBLE;
+        this.node = nodes[0];
+
+    }
+
+
+}
+
+export = SelectionPrefabWidget;

+ 193 - 0
Script/AtomicEditor/ui/frames/inspector/SelectionSection.ts

@@ -0,0 +1,193 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// LICENSE: Atomic Game Engine Editor and Tools EULA
+// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
+// license information: https://github.com/AtomicGameEngine/AtomicGameEngine
+//
+
+import SerializableEditType = require("./SerializableEditType");
+import AttributeInfoEdit = require("./AttributeInfoEdit");
+import SelectionSectionUI = require("./SelectionSectionUI");
+
+import "./ComponentAttributeUI";
+
+abstract class SelectionSection extends Atomic.UISection {
+
+    hasDynamicAttr: boolean = false;
+    editType: SerializableEditType;
+    attrLayout: Atomic.UILayout;
+    suppressed: boolean = false;
+    customUI: SelectionSectionUI;
+
+    attrEdits: { [name: string]: AttributeInfoEdit } = {};
+
+    constructor(editType: SerializableEditType) {
+
+        super();
+
+        this.editType = editType;
+
+        this.text = editType.typeName;
+
+        this.createUI();
+
+    }
+
+    contains(serial:Atomic.Serializable):boolean {
+
+        return this.editType.objects.indexOf(serial) == -1;
+
+    }
+
+    refresh() {
+
+        for (var name in this.attrEdits) {
+
+            this.attrEdits[name].refresh();
+
+        }
+
+        if (this.customUI)
+            this.customUI.refresh();
+
+    }
+
+    suppress(value: boolean) {
+
+        if (this.suppressed == value) {
+            return;
+        }
+
+        this.suppressed = value;
+        if (value) {
+            this.visibility = Atomic.UI_WIDGET_VISIBILITY_GONE;
+        } else {
+            this.visibility = Atomic.UI_WIDGET_VISIBILITY_VISIBLE;
+        }
+
+    }
+
+    updateDynamicAttrInfos(attrInfos: Atomic.AttributeInfo[]) {
+
+        Atomic.ui.blockChangedEvents = true;
+
+        this.editType.attrInfos = attrInfos;
+
+        var attrEdit: AttributeInfoEdit;
+        var remove: AttributeInfoEdit[] = [];
+
+        var addWidget: Atomic.UIWidget;
+
+        for (var name in this.attrEdits) {
+
+            attrEdit = this.attrEdits[name];
+
+            if (attrEdit.attrInfo.dynamic) {
+                remove.push(attrEdit);
+            } else {
+                addWidget = attrEdit;
+            }
+
+        }
+
+        for (var i in remove) {
+
+            var attrEdit = remove[i];
+            attrEdit.remove();
+            delete this.attrEdits[attrEdit.attrInfo.name];
+
+        }
+
+        for (var i in attrInfos) {
+
+            var attr = attrInfos[i];
+
+            if (!attr.dynamic) {
+                continue;
+            }
+
+            if (attr.mode & Atomic.AM_NOEDIT)
+                continue;
+
+            var attrEdit = AttributeInfoEdit.createAttrEdit(this.editType, attr);
+
+            if (!attrEdit)
+                continue;
+
+            this.attrEdits[attr.name] = attrEdit;
+
+            if (!addWidget) {
+                this.attrLayout.addChild(attrEdit);
+                addWidget = attrEdit;
+            } else {
+                this.attrLayout.addChildAfter(attrEdit, addWidget);
+                addWidget = attrEdit;
+            }
+
+        }
+
+        this.refresh();
+
+        Atomic.ui.blockChangedEvents = false;
+
+    }
+
+    createUI() {
+
+        var attrLayout = this.attrLayout = new Atomic.UILayout(Atomic.UI_AXIS_Y);
+        attrLayout.spacing = 3;
+        attrLayout.layoutPosition = Atomic.UI_LAYOUT_POSITION_LEFT_TOP;
+        attrLayout.layoutSize = Atomic.UI_LAYOUT_SIZE_AVAILABLE;
+
+        this.contentRoot.addChild(attrLayout);
+
+        for (var i in this.editType.attrInfos) {
+
+            var attr = this.editType.attrInfos[i];
+
+            if (attr.mode & Atomic.AM_NOEDIT)
+                continue;
+
+            var attrEdit = AttributeInfoEdit.createAttrEdit(this.editType, attr);
+
+            if (!attrEdit)
+                continue;
+
+            this.attrEdits[attr.name] = attrEdit;
+
+            attrLayout.addChild(attrEdit);
+
+        }
+
+        if (SelectionSection.customSectionUI[this.editType.typeName]) {
+
+            this.customUI = new SelectionSection.customSectionUI[this.editType.typeName]();
+            this.customUI.createUI(this.editType);
+            attrLayout.addChild(this.customUI);
+
+        }
+
+    }
+
+    static fontDesc: Atomic.UIFontDescription;
+
+    static customSectionUI: { [typeName: string]: typeof SelectionSectionUI } = {};
+
+    static registerCustomSectionUI(typeName: string, ui: typeof SelectionSectionUI) {
+
+        SelectionSection.customSectionUI[typeName] = ui;
+
+    }
+
+
+    private static Ctor = (() => {
+
+        var fd = SelectionSection.fontDesc = new Atomic.UIFontDescription();
+        fd.id = "Vera";
+        fd.size = 11;
+
+    })();
+
+}
+
+export = SelectionSection;

+ 48 - 0
Script/AtomicEditor/ui/frames/inspector/SelectionSectionCoreUI.ts

@@ -0,0 +1,48 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// LICENSE: Atomic Game Engine Editor and Tools EULA
+// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
+// license information: https://github.com/AtomicGameEngine/AtomicGameEngine
+//
+
+import EditorUI = require("ui/EditorUI");
+import InspectorUtils = require("./InspectorUtils");
+import SelectionSection = require("./SelectionSection");
+import SelectionSectionUI = require("./SelectionSectionUI");
+import SerializableEditType = require("./SerializableEditType");
+
+
+class CollisionShapeSectionUI extends SelectionSectionUI {
+
+    createUI(editType: SerializableEditType) {
+
+        this.editType = editType;
+
+        var button = new Atomic.UIButton();
+        button.fontDescription = InspectorUtils.attrFontDesc;
+        button.gravity = Atomic.UI_GRAVITY_RIGHT;
+        button.text = "Set from StaticModel";
+
+        button.onClick = () => {
+
+            for (var i in this.editType.objects) {
+
+                var shape = <Atomic.CollisionShape>this.editType.objects[i];
+                var model = <Atomic.Drawable>shape.node.getComponent("StaticModel");
+
+                if (model) {
+                    var box = model.boundingBox;
+                    shape.setBox([box[3] - box[0], box[4] - box[1], box[5] - box[2]]);
+                }
+
+            }
+
+        };
+
+        this.addChild(button);
+
+    }
+
+}
+
+SelectionSection.registerCustomSectionUI("CollisionShape", CollisionShapeSectionUI);

+ 26 - 0
Script/AtomicEditor/ui/frames/inspector/SelectionSectionUI.ts

@@ -0,0 +1,26 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// LICENSE: Atomic Game Engine Editor and Tools EULA
+// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
+// license information: https://github.com/AtomicGameEngine/AtomicGameEngine
+//
+
+import SerializableEditType = require("./SerializableEditType");
+
+class SelectionSectionUI extends Atomic.UILayout {
+
+    editType: SerializableEditType;
+
+    refresh() {
+
+    }
+
+    createUI(editType: SerializableEditType) {
+
+      this.editType = editType;
+
+    }
+
+}
+
+export = SelectionSectionUI;

+ 150 - 0
Script/AtomicEditor/ui/frames/inspector/SerializableEditType.ts

@@ -0,0 +1,150 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// LICENSE: Atomic Game Engine Editor and Tools EULA
+// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
+// license information: https://github.com/AtomicGameEngine/AtomicGameEngine
+//
+
+class SerializableEditType {
+
+    constructor(serial: Atomic.Serializable) {
+
+        this.typeName = serial.typeName;
+        this.attrInfos = serial.getAttributes();
+        this.addSerializable(serial);
+    }
+
+    addSerializable(serial: Atomic.Serializable) {
+
+        this.objects.push(serial);
+
+    }
+
+    getUniformValue(attrInfo: Atomic.AttributeInfo, index: number = -1): boolean {
+
+        if (this.objects.length <= 1)
+            return true;
+
+        var value: any;
+
+        for (var i in this.objects) {
+
+            var object = this.objects[i];
+
+            if (i == 0) {
+
+                value = object.getAttribute(attrInfo.name);
+                if (index >= 0) {
+
+                    if (attrInfo.type == Atomic.VAR_RESOURCEREFLIST) {
+
+                        value = value.resources[index];
+
+                    } else {
+                        value = value[index];
+                    }
+                }
+
+            } else {
+
+                var value2 = object.getAttribute(attrInfo.name);
+                if (index >= 0) {
+                    if (attrInfo.type == Atomic.VAR_RESOURCEREFLIST) {
+
+                        value2 = value2.resources[index];
+
+                    } else {
+                        value2 = value2[index];
+                    }
+                }
+
+                if (value != value2)
+                    return false;
+
+            }
+
+        }
+
+        return true;
+
+
+    }
+
+    onAttributeInfoEdited(attrInfo: Atomic.AttributeInfo, value: any, index: number = -1, genEdit: boolean = true) {
+
+        for (var i in this.objects) {
+
+            var object = this.objects[i];
+
+            if (index >= 0) {
+
+                var idxValue = object.getAttribute(attrInfo.name);
+
+                if (attrInfo.type == Atomic.VAR_RESOURCEREFLIST) {
+
+                    idxValue.resources[index] = value;
+                    object.setAttribute(attrInfo.name, idxValue);
+
+                } else {
+
+                    idxValue[index] = value;
+                    object.setAttribute(attrInfo.name, idxValue);
+
+                }
+
+            } else {
+
+                object.setAttribute(attrInfo.name, value);
+
+            }
+
+        }
+
+        if (!genEdit)
+            return;
+
+        var node: Atomic.Node = null;
+        if (this.nodes.length) {
+            node = this.nodes[0];
+        } else if (this.objects.length && this.objects[0].typeName == "Node") {
+            node = <Atomic.Node>this.objects[0];
+        }
+
+        if (node)
+            node.scene.sendEvent("SceneEditEnd");
+
+    }
+
+    compareTypes(otherType: SerializableEditType, multiSelect:boolean = false): boolean {
+
+        return this.typeName == otherType.typeName;
+
+    }
+
+    addNode(node: Atomic.Node) {
+
+        if (this.nodes.indexOf(node) == -1) {
+            this.nodes.push(node);
+        }
+
+    }
+
+    getFirstObject(): Atomic.Serializable {
+
+        if (this.objects.length) {
+            return this.objects[0];
+        }
+
+        return null;
+
+    }
+
+    typeName: string;
+    attrInfos: Atomic.AttributeInfo[];
+
+    nodes: Atomic.Node[] = [];
+    objects: Atomic.Serializable[] = [];
+
+}
+
+export = SerializableEditType;

+ 2 - 7
Script/AtomicEditor/ui/frames/menus/HierarchyFrameMenu.ts

@@ -38,8 +38,6 @@ class HierarchyFrameMenus extends Atomic.ScriptObject {
                 if (node) {
 
                     child = node.createChild("Node");
-                    node.scene.sendEvent("SceneEditNodeAddedRemoved", { scene:node.scene, node:node, added:true});
-
                 }
 
             }
@@ -56,9 +54,7 @@ class HierarchyFrameMenus extends Atomic.ScriptObject {
             }
 
             if (child) {
-
-                this.sendEvent(EditorEvents.ActiveNodeChange, { node: child });
-
+                child.scene.sendEvent("SceneEditNodeCreated", { node : child});
             }
 
             return true;
@@ -87,8 +83,7 @@ class HierarchyFrameMenus extends Atomic.ScriptObject {
             } else if (refid == "duplicate_node") {
 
                 var newnode = node.clone();
-                this.sendEvent(EditorEvents.ActiveNodeChange, { node: newnode });
-
+                node.scene.sendEvent("SceneEditNodeCreated", { node : newnode});
             }
 
             return true;

+ 11 - 3
Script/AtomicEditor/ui/frames/menus/MainFrameMenu.ts

@@ -103,6 +103,12 @@ class MainFrameMenu extends Atomic.ScriptObject {
                 return true;
             }
 
+            if (refid == "edit frame selected") {
+                EditorUI.getShortcuts().invokeFrameSelected();
+                return true;
+            }
+
+
             return false;
 
         } else if (target.id == "menu file popup") {
@@ -248,15 +254,17 @@ var editItems = {
     "Paste": ["edit paste", StringID.ShortcutPaste],
     "Select All": ["edit select all", StringID.ShortcutSelectAll],
     "-2": null,
+    "Frame Selected": ["edit frame selected", StringID.ShortcutFrameSelected],
+    "-3": null,
     "Find": ["edit find", StringID.ShortcutFind],
     "Find Next": ["edit find next", StringID.ShortcutFindNext],
     "Find Prev": ["edit find prev", StringID.ShortcutFindPrev],
-    "-3": null,
-    "Format Code": ["edit format code", StringID.ShortcutBeautify],
     "-4": null,
+    "Format Code": ["edit format code", StringID.ShortcutBeautify],
+    "-5": null,
     "Play": ["edit play", StringID.ShortcutPlay],
     "Debug (C# Project)": ["edit play debug", StringID.ShortcutPlayDebug],
-    "-5": null,
+    "-6": null,
     "Snap Settings": ["edit snap settings"]
 
 };

+ 1 - 1
Script/Packages/Atomic/IO.json

@@ -1,7 +1,7 @@
 {
 	"name" : "IO",
 	"sources" : ["Source/Atomic/IO"],
-	"classes" : ["Log", "File", "FileSystem", "FileWatcher"],
+	"classes" : ["Log", "File", "FileSystem", "FileWatcher", "BufferQueue"],
 	"overloads" : {
 		"File" : {
 			"File" : ["Context", "String", "FileMode"]

+ 3 - 1
Script/Packages/Editor/Editor.json

@@ -1,7 +1,9 @@
 {
 	"name" : "Editor",
+	"includes" : ["<Atomic/Graphics/DebugRenderer.h>"],
 	"sources" : ["Source/AtomicEditor/Application", "Source/AtomicEditor/Utils",
 							"Source/AtomicEditor/EditorMode", "Source/AtomicEditor/PlayerMode",
 							 "Source/AtomicEditor/Editors", "Source/AtomicEditor/Editors/SceneEditor3D"],
-	"classes" : ["EditorMode", "PlayerMode", "FileUtils", "ResourceEditor", "JSResourceEditor", "SceneEditor3D", "SceneView3D"]
+	"classes" : ["EditorMode", "PlayerMode", "FileUtils", "ResourceEditor", "JSResourceEditor",
+								"SceneEditor3D", "SceneView3D", "SceneSelection"]
 }

+ 69 - 9
Script/TypeScript/AtomicWork.d.ts

@@ -52,7 +52,7 @@ declare module Atomic {
         qualifiers: number;
 
         // mouse buttons down
-        buttons:number;
+        buttons: number;
 
     }
 
@@ -63,7 +63,7 @@ declare module Atomic {
         //  Atomic.QUAL_SHIFT, Atomic.QUAL_CTRL, Atomic.QUAL_ALT, Atomic.QUAL_ANY
         qualifiers: number;
         // mouse buttons down
-        buttons:number;
+        buttons: number;
 
     }
 
@@ -76,6 +76,13 @@ declare module Atomic {
 
     }
 
+    export interface UIListViewSelectionChangedEvent {
+
+        refid: string;
+        selected: boolean;
+
+    }
+
     export interface NodeAddedEvent {
 
         scene: Atomic.Scene;
@@ -121,6 +128,10 @@ declare module Atomic {
         focused: boolean;
     }
 
+    export interface UIWidgetEditCompleteEvent {
+        widget: UIWidget;
+    }
+
     export interface UIWidgetDeletedEvent {
 
         widget: UIWidget;
@@ -197,6 +208,7 @@ declare module Atomic {
         defaultValue: string;
         enumNames: string[];
         resourceTypeName: string;
+        dynamic: boolean;
 
     }
 
@@ -249,14 +261,62 @@ declare module AtomicNET {
 
 declare module Editor {
 
-  export interface GizmoEditModeChangedEvent {
-    mode:EditMode;
-  }
+    export interface SceneNodeSelectedEvent {
+        scene: Atomic.Scene;
+        node: Atomic.Node;
+        selected: boolean;
+        quiet: boolean;
+    }
+
+    export interface SceneEditAddRemoveNodesEvent {
+
+        end: boolean;
+
+    }
+
+
+    export interface SceneEditNodeAddedEvent {
+
+        scene: Atomic.Scene;
+        parent: Atomic.Node;
+        node: Atomic.Node;
+
+    }
+
+    export interface SceneEditNodeRemovedEvent {
 
-  export interface GizmoAxisModeChangedEvent {
-    mode:AxisMode;
-    toggle:boolean;
-  }
+        scene: Atomic.Scene;
+        parent: Atomic.Node;
+        node: Atomic.Node;
+
+    }
+
+    export interface SceneEditComponentAddedRemovedEvent {
+
+        scene: Atomic.Scene;
+        node: Atomic.Node;
+        component: Atomic.Component;
+        removed: boolean;
+    }
+
+    export interface SceneEditStateChangeEvent {
+
+        serializable: Atomic.Serializable;
+
+    }
+
+    export interface SceneEditNodeCreatedEvent {
+        node: Atomic.Node;
+    }
+
+    export interface GizmoEditModeChangedEvent {
+        mode: EditMode;
+    }
+
+    export interface GizmoAxisModeChangedEvent {
+        mode: AxisMode;
+        toggle: boolean;
+    }
 
 }
 

+ 12 - 3
Script/tsconfig.json

@@ -14,6 +14,9 @@
         "./ToolCore/**/*.ts",
         "./AtomicEditor/**/*.ts"
     ],
+    "atom": {
+        "rewriteTsconfig": true
+    },
     "files": [
         "./ToolCore/build/BuildSettings.ts",
         "./AtomicEditor/editor/Editor.ts",
@@ -27,17 +30,23 @@
         "./AtomicEditor/ui/frames/HierarchyFrame.ts",
         "./AtomicEditor/ui/frames/inspector/ArrayEditWidget.ts",
         "./AtomicEditor/ui/frames/inspector/AssemblyInspector.ts",
-        "./AtomicEditor/ui/frames/inspector/ComponentInspector.ts",
+        "./AtomicEditor/ui/frames/inspector/AttributeInfoEdit.ts",
+        "./AtomicEditor/ui/frames/inspector/ComponentAttributeUI.ts",
         "./AtomicEditor/ui/frames/inspector/CreateComponentButton.ts",
         "./AtomicEditor/ui/frames/inspector/CSComponentClassSelector.ts",
-        "./AtomicEditor/ui/frames/inspector/DataBinding.ts",
         "./AtomicEditor/ui/frames/inspector/InspectorFrame.ts",
         "./AtomicEditor/ui/frames/inspector/InspectorUtils.ts",
         "./AtomicEditor/ui/frames/inspector/InspectorWidget.ts",
         "./AtomicEditor/ui/frames/inspector/MaterialInspector.ts",
         "./AtomicEditor/ui/frames/inspector/ModelInspector.ts",
-        "./AtomicEditor/ui/frames/inspector/NodeInspector.ts",
         "./AtomicEditor/ui/frames/inspector/PrefabInspector.ts",
+        "./AtomicEditor/ui/frames/inspector/SelectionEditTypes.ts",
+        "./AtomicEditor/ui/frames/inspector/SelectionInspector.ts",
+        "./AtomicEditor/ui/frames/inspector/SelectionPrefabWidget.ts",
+        "./AtomicEditor/ui/frames/inspector/SelectionSection.ts",
+        "./AtomicEditor/ui/frames/inspector/SelectionSectionCoreUI.ts",
+        "./AtomicEditor/ui/frames/inspector/SelectionSectionUI.ts",
+        "./AtomicEditor/ui/frames/inspector/SerializableEditType.ts",
         "./AtomicEditor/ui/frames/inspector/TextureSelector.ts",
         "./AtomicEditor/ui/frames/MainFrame.ts",
         "./AtomicEditor/ui/frames/menus/HierarchyFrameMenu.ts",

+ 1 - 1
Source/Atomic/Container/Vector.h

@@ -922,10 +922,10 @@ public:
     /// Return whether vector is empty.
     bool Empty() const { return size_ == 0; }
 
-private:
     /// Return the buffer with right type.
     T* Buffer() const { return reinterpret_cast<T*>(buffer_); }
 
+private:
     /// Move a range of elements within the vector.
     void MoveRange(unsigned dest, unsigned src, unsigned count)
     {

+ 7 - 0
Source/Atomic/Core/StringUtils.cpp

@@ -647,6 +647,13 @@ String ToString(const char* formatString, ...)
     return ret;
 }
 
+String ToStringVariadic(const char* formatString, va_list args)
+{
+    String ret;
+    ret.AppendWithFormatArgs(formatString, args);
+    return ret;
+}
+
 bool IsAlpha(unsigned ch)
 {
     return ch < 256 ? isalpha(ch) != 0 : false;

+ 2 - 0
Source/Atomic/Core/StringUtils.h

@@ -113,6 +113,8 @@ ATOMIC_API unsigned GetStringListIndex(const char* value, const String* strings,
 ATOMIC_API unsigned GetStringListIndex(const char* value, const char** strings, unsigned defaultIndex, bool caseSensitive = false);
 /// Return a formatted string.
 ATOMIC_API String ToString(const char* formatString, ...);
+/// Return a formatted string.
+ATOMIC_API String ToStringVariadic(const char* formatString, va_list args);
 /// Return whether a char is an alphabet letter.
 ATOMIC_API bool IsAlpha(unsigned ch);
 /// Return whether a char is a digit.

+ 127 - 0
Source/Atomic/IO/BufferQueue.cpp

@@ -0,0 +1,127 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "../Precompiled.h"
+
+#include "../IO/BufferQueue.h"
+
+#include "../DebugNew.h"
+
+// This BufferQueue implementation is based on the BufferedSoundStream.
+//
+// It is a Deserializer and a Serializer. However, it should only be written
+// to in large chunks because some writes cause allocation. Reads should
+// perform well reguardless of the read chunk sizes.
+
+namespace Atomic
+{
+
+BufferQueue::BufferQueue(Context* context) :
+    Object(context),
+    position_(0)
+{
+}
+
+BufferQueue::~BufferQueue()
+{
+}
+
+unsigned BufferQueue::Read(void* dest_, unsigned numBytes)
+{
+    char* dest((char*)dest_);
+    MutexLock lock(bufferMutex_);
+
+    unsigned outBytes = 0;
+
+    while (numBytes && buffers_.Size())
+    {
+        // Copy as much from the front buffer as possible, then discard it and move to the next
+        List<Pair<SharedArrayPtr<signed char>, unsigned> >::Iterator front = buffers_.Begin();
+
+        unsigned copySize = front->second_ - position_;
+        if (copySize > numBytes)
+            copySize = numBytes;
+
+        memcpy(dest, front->first_.Get() + position_, copySize);
+        position_ += copySize;
+        if (position_ >= front->second_)
+        {
+            buffers_.PopFront();
+            position_ = 0;
+        }
+
+        dest += copySize;
+        outBytes += copySize;
+        numBytes -= copySize;
+    }
+
+    size_ -= outBytes;
+    return outBytes;
+}
+
+unsigned BufferQueue::Write(const void* data, unsigned numBytes)
+{
+    if (data && numBytes)
+    {
+        MutexLock lock(bufferMutex_);
+
+        SharedArrayPtr<signed char> newBuffer(new signed char[numBytes]);
+        memcpy(newBuffer.Get(), data, numBytes);
+        buffers_.Push(MakePair(newBuffer, numBytes));
+        size_ += numBytes;
+        return numBytes;
+    }
+    return 0;
+}
+
+void BufferQueue::Write(SharedArrayPtr<signed char> data, unsigned numBytes)
+{
+    if (data && numBytes)
+    {
+        MutexLock lock(bufferMutex_);
+
+        buffers_.Push(MakePair(data, numBytes));
+        size_ += numBytes;
+    }
+}
+
+void BufferQueue::Write(SharedArrayPtr<unsigned char> data, unsigned numBytes)
+{
+    if (data && numBytes)
+    {
+        MutexLock lock(bufferMutex_);
+
+        buffers_.Push(MakePair(ReinterpretCast<signed char>(data), numBytes));
+        size_ += numBytes;
+    }
+}
+
+void BufferQueue::Clear()
+{
+    MutexLock lock(bufferMutex_);
+
+    buffers_.Clear();
+    position_ = 0;
+    size_ = 0;
+}
+
+}

+ 72 - 0
Source/Atomic/IO/BufferQueue.h

@@ -0,0 +1,72 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "../Core/Object.h"
+#include "../Core/Mutex.h"
+#include "../Container/ArrayPtr.h"
+#include "../Container/List.h"
+#include "../Container/Pair.h"
+#include "../IO/Deserializer.h"
+#include "../IO/Serializer.h"
+
+namespace Atomic
+{
+
+/// %Act as a continuous byte buffer which can be appened indefinatly.
+class ATOMIC_API BufferQueue : public Object, public Deserializer, public Serializer
+{
+    OBJECT(BufferQueue)
+
+public:
+    /// Construct.
+    BufferQueue(Context* context);
+    /// Destruct.
+    virtual ~BufferQueue();
+
+    /// Seek operation is not supported for a BufferQueue.
+    virtual unsigned Seek(unsigned position) { return 0; }
+
+    /// Produce data into destination. Return number of bytes produced.
+    virtual unsigned Read(void* dest, unsigned numBytes);
+
+    /// Buffer data. Makes a copy of it. Returns size passed in.
+    virtual unsigned Write(const void* data, unsigned size);
+    /// Buffer data by taking ownership of it.
+    void Write(SharedArrayPtr<signed char> data, unsigned numBytes);
+    /// Buffer data by taking ownership of it.
+    void Write(SharedArrayPtr<unsigned char> data, unsigned numBytes);
+
+    /// Remove all buffered data.
+    void Clear();
+
+private:
+    /// Buffers and their sizes.
+    List<Pair<SharedArrayPtr<signed char>, unsigned> > buffers_;
+    /// Byte position in the front most buffer.
+    unsigned position_;
+    /// Mutex for buffer data.
+    mutable Mutex bufferMutex_;
+};
+
+}

+ 3 - 0
Source/Atomic/Scene/PrefabComponent.cpp

@@ -69,11 +69,14 @@ void PrefabComponent::LoadPrefabNode()
     Quaternion rot = node->GetRotation();
     Vector3 scale = node->GetScale();
 
+    String name = node->GetName();
+
     node->LoadXML(xmlfile->GetRoot());
 
     node->SetPosition(pos);
     node->SetRotation(rot);
     node->SetScale(scale);
+    node->SetName(name);
 
     // Get the root components of the load node
     const Vector<SharedPtr<Component>>& rootComponents = node->GetComponents();

+ 27 - 1
Source/Atomic/UI/UI.cpp

@@ -111,7 +111,8 @@ UI::UI(Context* context) :
     initialized_(false),
     skinLoaded_(false),
     consoleVisible_(false),
-    exitRequested_(false)
+    exitRequested_(false),
+    changedEventsBlocked_(0)
 {
 
     SubscribeToEvent(E_EXITREQUESTED, HANDLER(UI, HandleExitRequested));
@@ -153,6 +154,31 @@ void UI::Shutdown()
 
 }
 
+bool UI::GetFocusedWidget()
+{
+    if (!TBWidget::focused_widget)
+        return false;
+
+    return TBWidget::focused_widget->IsOfType<TBEditField>();
+}
+
+void UI::SetBlockChangedEvents(bool blocked)
+{
+    if (blocked)
+        changedEventsBlocked_++;
+    else
+    {
+        changedEventsBlocked_--;
+
+        if (changedEventsBlocked_ < 0)
+        {
+            LOGERROR("UI::BlockChangedEvents - mismatched block calls, setting to 0");
+            changedEventsBlocked_ = 0;
+        }
+    }
+
+}
+
 void UI::Initialize(const String& languageFile)
 {
     Graphics* graphics = GetSubsystem<Graphics>();

+ 8 - 0
Source/Atomic/UI/UI.h

@@ -98,6 +98,8 @@ public:
     void ShowConsole(bool value);
     void ToggleConsole();
 
+    bool GetFocusedWidget();
+
     /// request exit on next frame
     void RequestExit() { exitRequested_ = true; inputDisabled_ = true; }
 
@@ -105,6 +107,10 @@ public:
 
     UIWidget* GetWidgetAt(int x, int y, bool include_children);
 
+    bool GetBlockChangedEvents() const { return changedEventsBlocked_ > 0; }
+
+    void SetBlockChangedEvents(bool blocked = true);
+
 private:
 
     static WeakPtr<Context> uiContext_;
@@ -138,6 +144,8 @@ private:
     HashMap<tb::TBWidget*, SharedPtr<UIWidget> > widgetWrap_;
     HashMap<unsigned, String> tbidToString_;
 
+    int changedEventsBlocked_;
+
     bool inputDisabled_;
     bool keyboardDisabled_;
     bool initialized_;

+ 3 - 1
Source/Atomic/UI/UIEditField.cpp

@@ -198,7 +198,9 @@ bool UIEditField::OnEvent(const tb::TBWidgetEvent &ev)
 {
     if (ev.type == EVENT_TYPE_CUSTOM && ev.ref_id == TBIDC("edit_complete"))
     {
-        SendEvent(E_UIWIDGETEDITCOMPLETE);
+        VariantMap eventData;
+        eventData[UIWidgetEditComplete::P_WIDGET] = this;
+        SendEvent(E_UIWIDGETEDITCOMPLETE, eventData);
         return true;
     }
 

+ 8 - 0
Source/Atomic/UI/UIEvents.h

@@ -120,6 +120,7 @@ EVENT(E_UIWIDGETFOCUSESCAPED, UIWidgetFocusEscaped)
 
 EVENT(E_UIWIDGETEDITCOMPLETE, UIWidgetEditComplete)
 {
+    PARAM(P_WIDGET, Widget);             // UIWidget pointer
 }
 
 EVENT(E_UIUNHANDLEDSHORTCUT, UIUnhandledShortcut)
@@ -127,4 +128,11 @@ EVENT(E_UIUNHANDLEDSHORTCUT, UIUnhandledShortcut)
     PARAM(P_REFID, RefID); // string tbid
 }
 
+EVENT(E_UILISTVIEWSELECTIONCHANGED, UIListViewSelectionChanged)
+{
+    PARAM(P_REFID, RefID); // string tbid
+    PARAM(P_SELECTED, Selected);        // bool
+}
+
+
 }

+ 4 - 1
Source/Atomic/UI/UIInlineSelect.cpp

@@ -72,7 +72,10 @@ bool UIInlineSelect::OnEvent(const tb::TBWidgetEvent &ev)
 {
     if (ev.type == EVENT_TYPE_CUSTOM && ev.ref_id == TBIDC("edit_complete"))
     {
-        SendEvent(E_UIWIDGETEDITCOMPLETE);
+        VariantMap eventData;
+        eventData[UIWidgetEditComplete::P_WIDGET] = this;
+        SendEvent(E_UIWIDGETEDITCOMPLETE, eventData);
+
         return true;
     }
     return UIWidget::OnEvent(ev);

+ 1 - 1
Source/Atomic/UI/UIInput.cpp

@@ -220,7 +220,7 @@ static bool InvokeShortcut(UI* ui, int key, SPECIAL_KEY special_key, MODIFIER_KE
 #else
     bool shortcut_key = (modifierkeys & TB_CTRL) ? true : false;
 #endif
-    if (!TBWidget::focused_widget || !down || (!shortcut_key && special_key ==TB_KEY_UNDEFINED))
+    if (!down || (!shortcut_key && special_key ==TB_KEY_UNDEFINED))
         return false;
     bool reverse_key = (modifierkeys & TB_SHIFT) ? true : false;
     int upper_key = toupr_ascii(key);

+ 409 - 53
Source/Atomic/UI/UIListView.cpp

@@ -23,6 +23,11 @@
 #include <TurboBadger/tb_menu_window.h>
 #include <TurboBadger/tb_select.h>
 
+#include <Atomic/IO/Log.h>
+#include <Atomic/Core/Timer.h>
+
+#include "UI.h"
+#include "UIEvents.h"
 #include "UIListView.h"
 
 using namespace tb;
@@ -33,20 +38,71 @@ namespace Atomic
 class ListViewItemSource;
 class ListViewItemWidget;
 
+
+class ListViewItemWidget : public TBLayout
+{
+public:
+    ListViewItemWidget(ListViewItem *item, ListViewItemSource *source, TBSelectItemViewer *sourceviewer, int index);
+    virtual bool OnEvent(const TBWidgetEvent &ev);
+
+    void UpdateText(const String& text)
+    {
+        if (textField_)
+            textField_->SetText(text.CString());
+    }
+
+    void UpdateIcon(const String& icon)
+    {
+        if (icon_)
+            icon_->SetSkinBg(TBIDC(icon.CString()));
+    }
+
+    void UpdateTextSkin(const String& skin)
+    {
+        if (textField_)
+            textField_->SetSkinBg(TBIDC(skin.CString()));
+    }
+
+    void SetExpanded(bool expanded)
+    {
+        if (expandBox_)
+            expandBox_->SetValue(expanded ? 1 : 0);
+
+    }
+
+
+private:
+    TBCheckBox* expandBox_;
+    TBTextField* textField_;
+    TBSkinImage* icon_;
+    ListViewItemSource *source_;
+    TBSelectItemViewer *sourceviewer_;
+    int index_;
+    ListViewItem* item_;
+};
+
+
 class ListViewItem : public TBGenericStringItem
 {
     bool expanded_;
+    bool selected_;
 
 public:
     ListViewItem(const char *str, const TBID &id, const char* icon,  ListViewItemSource* source)
         : TBGenericStringItem(str, id), source_(source), parent_(0),
-          depth_(0), widget_(0), expanded_(false), icon_(icon)
+          depth_(0), widget_(0), expanded_(false), icon_(icon), selected_(false)
     {
 
     }
 
     ListViewItem* AddChild(const char* text, const char* icon, const TBID &id);
 
+    bool GetSelected() { return selected_; }
+    void SetSelected(bool value)
+    {
+        selected_ = value;
+    }
+
     bool GetExpanded() { return expanded_; }
 
     void GetChildren(PODVector<ListViewItem*>& children, bool recursive = false)
@@ -64,20 +120,20 @@ public:
 
     void SetExpanded(bool expanded)
     {
+        if (widget_)
+            widget_->SetExpanded(expanded);
+
         expanded_ = expanded;
+
         if (!expanded_)
         {
-            for (unsigned i = 0; i < children_.Size(); i ++)
-                children_[i]->SetExpanded(expanded);
+            //for (unsigned i = 0; i < children_.Size(); i ++)
+            //    children_[i]->SetExpanded(expanded);
         }
         else
         {
-            ListViewItem* p = parent_;
-            while (p)
-            {
-                p->expanded_ = true;
-                p = p->parent_;
-            }
+            if (parent_)
+                parent_->SetExpanded(expanded_);
         }
     }
 
@@ -94,46 +150,12 @@ public:
     String textSkin_;
 };
 
-class ListViewItemWidget : public TBLayout
-{
-public:
-    ListViewItemWidget(ListViewItem *item, ListViewItemSource *source, TBSelectItemViewer *sourceviewer, int index);
-    virtual bool OnEvent(const TBWidgetEvent &ev);
-
-    void UpdateText(const String& text)
-    {
-        if (textField_)
-            textField_->SetText(text.CString());
-    }
-
-    void UpdateIcon(const String& icon)
-    {
-        if (icon_)
-            icon_->SetSkinBg(TBIDC(icon.CString()));
-    }
-
-    void UpdateTextSkin(const String& skin)
-    {
-        if (textField_)
-            textField_->SetSkinBg(TBIDC(skin.CString()));
-    }
-
-
-private:
-    TBCheckBox* expandBox_;
-    TBTextField* textField_;
-    TBSkinImage* icon_;
-    ListViewItemSource *source_;
-    TBSelectItemViewer *sourceviewer_;
-    int index_;
-    ListViewItem* item_;
-};
 
 class ListViewItemSource : public TBSelectItemSourceList<ListViewItem>
 {
 public:
-    TBSelectList* list_;
-    ListViewItemSource(TBSelectList* list) : list_(list) {}
+    UIListView* uiListView_;
+    ListViewItemSource(UIListView* list) : uiListView_(list) {}
     virtual ~ListViewItemSource() {}
     virtual bool Filter(int index, const char *filter);
     virtual TBWidget *CreateItemWidget(int index, TBSelectItemViewer *viewer);
@@ -320,7 +342,7 @@ bool ListViewItemWidget::OnEvent(const TBWidgetEvent &ev)
     {
         item_->SetExpanded(!item_->GetExpanded());
 
-        source_->list_->InvalidateList();
+        source_->uiListView_->UpdateItemVisibility();
 
         // want to bubble
         return false;
@@ -340,7 +362,7 @@ bool ListViewItemSource::Filter(int index, const char *filter)
     if (item->parent_->GetExpanded())
         return true;
 
-    return false;
+    return true;
 
 }
 
@@ -379,9 +401,10 @@ static int select_list_sort_cb(TBSelectItemSource *_source, const int *a, const
 
 UIListView::UIListView(Context* context, bool createWidget) :
     UIWidget(context, createWidget),
-    source_(0), itemLookupId_(0)
+    source_(0), itemLookupId_(0), multiSelect_(false), moveDelta_(0.0f)
 {
     rootList_ = new UISelectList(context);
+    rootList_->SetUIListView(true);
 
     // dummy filter so filter is called
     rootList_->SetFilter(" ");
@@ -389,7 +412,7 @@ UIListView::UIListView(Context* context, bool createWidget) :
     widget_->SetGravity(WIDGET_GRAVITY_ALL);
     rootList_->SetGravity(UI_GRAVITY_ALL);
 
-    source_ = new ListViewItemSource(rootList_->GetTBSelectList());
+    source_ = new ListViewItemSource(this);
 
     rootList_->GetTBSelectList()->SetSource(source_);
 
@@ -489,6 +512,8 @@ void UIListView::DeleteItemByID(const String& id)
 
             source_->DeleteItem(i);
 
+            rootList_->InvalidateList();
+
             return;
         }
     }
@@ -543,7 +568,7 @@ void UIListView::DeleteAllItems()
 }
 
 
-void UIListView::SelectItemByID(const String& id)
+void UIListView::SelectItemByID(const String& id, bool selected)
 {
     TBID tid = TBIDC(id.CString());
 
@@ -553,15 +578,63 @@ void UIListView::SelectItemByID(const String& id)
 
         if (tid == item->id)
         {
-            //item->SetExpanded(true);
-            rootList_->SetValue(i);
-            rootList_->InvalidateList();
+            if (selected)
+            {
+                if (item->GetSelected())
+                    return;
+
+                item->SetSelected(selected);
+                if (item->parent_)
+                    item->parent_->SetExpanded(true);
+                SetValueFirstSelected();
+                UpdateItemVisibility();
+                ScrollToSelectedItem();
+            }
+            else
+            {
+                if (!item->GetSelected())
+                    return;
+
+                item->SetSelected(false);
+                UpdateItemVisibility();
+
+            }
+
             return;
         }
 
     }
 }
 
+void UIListView::UpdateItemVisibility()
+{
+    for (int i = 0; i < source_->GetNumItems(); i++)
+    {
+        ListViewItem* item = source_->GetItem(i);
+
+        if (!item->widget_)
+            continue;
+
+        item->widget_->SetVisibilility(WIDGET_VISIBILITY_VISIBLE);
+        item->widget_->SetState(WIDGET_STATE_SELECTED, item->GetSelected());
+
+        ListViewItem* parent = item->parent_;
+        while (parent)
+        {
+            if (!parent->GetExpanded())
+                break;
+
+            parent = parent->parent_;
+        }
+
+        if (parent)
+            item->widget_->SetVisibilility(WIDGET_VISIBILITY_GONE);
+    }
+
+    tb::TBScrollContainer* scroll = (tb::TBScrollContainer*) rootList_->GetInternalWidget()->GetFirstChild();
+    scroll->OnProcess();
+}
+
 void UIListView::ScrollToSelectedItem()
 {
     if (rootList_.Null())
@@ -570,5 +643,288 @@ void UIListView::ScrollToSelectedItem()
     rootList_->ScrollToSelectedItem();
 }
 
+void UIListView::SelectAllItems(bool select)
+{
+    for (int i = 0; i < source_->GetNumItems(); i++)
+    {
+        ListViewItem* item = source_->GetItem(i);
+        item->SetSelected(select);
+    }
+
+}
+
+void UIListView::SetValueFirstSelected()
+{
+    int index = -1;
+
+    for (int i = 0; i < source_->GetNumItems(); i++)
+    {
+        ListViewItem* item = source_->GetItem(i);
+        if (item->GetSelected())
+        {
+            index = i;
+            break;
+        }
+    }
+
+    rootList_->SetValue(index);
+
+}
+
+void UIListView::SelectSingleItem(ListViewItem* item, bool expand)
+{
+
+    if (!item)
+        return;
+
+    bool dirty = !item->GetSelected();
+
+    if (!dirty)
+    {
+        for (unsigned i = 0; i < source_->GetNumItems(); i++)
+        {
+            ListViewItem* sitem = source_->GetItem(i);
+
+            if (sitem != item && sitem->GetSelected())
+            {
+                dirty = true;
+                break;
+            }
+        }
+    }
+
+    if (!dirty)
+        return;
+
+    for (unsigned i = 0; i < source_->GetNumItems(); i++)
+    {
+        ListViewItem* sitem = source_->GetItem(i);
+
+        if (sitem->GetSelected())
+        {
+            sitem->SetSelected(false);
+            SendItemSelectedChanged(sitem);
+        }
+
+    }
+
+    if (expand)
+        item->SetExpanded(true);
+
+    item->SetSelected(true);
+    UpdateItemVisibility();
+
+    SetValueFirstSelected();
+    ScrollToSelectedItem();
+
+    SendItemSelectedChanged(item);
+
+}
+
+void UIListView::Move(tb::SPECIAL_KEY key)
+{
+    const float delta = 0.015f;
+    if (moveDelta_)
+    {
+        Time* time = GetSubsystem<Time>();
+        moveDelta_ -= time->GetTimeStep();
+        if (moveDelta_ < 0.0f)
+            moveDelta_ = 0.0f;
+    }
+
+    if (moveDelta_ > 0.0f)
+        return;
+
+    // selected index
+    int index = -1;
+
+    for (int i = 0; i < source_->GetNumItems(); i++)
+    {
+        ListViewItem* item = source_->GetItem(i);
+        if (item->GetSelected())
+        {
+            index = i;
+            break;
+        }
+    }
+
+    // nothing selected
+    if (index == -1)
+        return;
+
+    if (key == TB_KEY_LEFT)
+    {
+        ListViewItem* item = source_->GetItem(index);
+        if (item->children_.Size() > 0 && item->GetExpanded())
+        {
+            item->SetExpanded(false);
+            UpdateItemVisibility();
+            moveDelta_ = delta;
+            return;
+        }
+        else
+        {
+            if (!item->parent_)
+                return;
+
+            SelectSingleItem(item->parent_, false);
+            moveDelta_ = delta;
+            return;
+        }
+    }
+
+    if (key == TB_KEY_RIGHT)
+    {
+        ListViewItem* item = source_->GetItem(index);
+        if (item->children_.Size() > 0 && !item->GetExpanded())
+        {
+            item->SetExpanded(true);
+            UpdateItemVisibility();
+            moveDelta_ = delta;
+            return;
+        }
+        else
+        {
+            if (!item->children_.Size())
+                return;
+
+            SelectSingleItem(source_->GetItem(index + 1), false);
+            moveDelta_ = delta;
+            return;
+
+        }
+    }
+
+
+    if (key == TB_KEY_UP)
+    {
+        // can't go any further up list
+        if (index == 0)
+            return;
+
+        for (int i = (int) (index - 1 ); i >= 0; i--)
+        {
+            ListViewItem* item = source_->GetItem(i);
+            if (item->widget_ && item->widget_->GetVisibility() == WIDGET_VISIBILITY_VISIBLE)
+            {
+                SelectSingleItem(item, false);
+                moveDelta_ = delta;
+                return;
+            }
+
+        }
+
+    }
+
+    if (key == TB_KEY_DOWN)
+    {
+        // can't go any further down list
+        if (index == source_->GetNumItems() - 1)
+            return;
+
+        for (int i = index + 1; i < source_->GetNumItems(); i++)
+        {
+            ListViewItem* item = source_->GetItem(i);
+            if (item->widget_ && item->widget_->GetVisibility() == WIDGET_VISIBILITY_VISIBLE)
+            {
+                SelectSingleItem(item, false);
+                moveDelta_ = delta;
+                return;
+            }
+
+        }
+
+    }
+
+}
+
+void UIListView::SendItemSelectedChanged(ListViewItem* item)
+{
+    UI* ui = GetSubsystem<UI>();
+
+    VariantMap eventData;
+    String refid;
+
+    ui->GetTBIDString(item->id, refid);
+
+    eventData[UIListViewSelectionChanged::P_REFID] = refid;
+    eventData[UIListViewSelectionChanged::P_SELECTED] = item->GetSelected();
+    this->SendEvent(E_UILISTVIEWSELECTIONCHANGED, eventData);
+
+}
+
+bool UIListView::OnEvent(const tb::TBWidgetEvent &ev)
+{
+    if (ev.type == EVENT_TYPE_KEY_UP )
+    {
+        moveDelta_ = 0.0f;
+    }
+
+    if (ev.type == EVENT_TYPE_KEY_DOWN )
+    {
+        if (ev.special_key == TB_KEY_DOWN || ev.special_key == TB_KEY_UP || ev.special_key == TB_KEY_LEFT || ev.special_key == TB_KEY_RIGHT)
+        {
+            Move(ev.special_key);
+            return true;
+        }
+    }
+
+    if (ev.type == EVENT_TYPE_CUSTOM && ev.ref_id == TBIDC("select_list_validation_end"))
+    {
+        UpdateItemVisibility();
+        return true;
+    }
+
+    if (ev.type == EVENT_TYPE_CUSTOM && ev.ref_id == TBIDC("select_list_selection_changed"))
+    {
+        for (int i = 0; i < source_->GetNumItems(); i++)
+        {
+            ListViewItem* item = source_->GetItem(i);
+
+            if (item->id == ev.target->GetID())
+            {
+                bool multi = false;
+                if (multiSelect_ && (ev.modifierkeys & TB_SHIFT || ev.modifierkeys & TB_CTRL || ev.modifierkeys & TB_SUPER))
+                    multi = true;
+
+                if (multi)
+                {
+                    if (item->GetSelected())
+                    {
+                        item->SetSelected(false);
+                        UpdateItemVisibility();
+
+                        SendItemSelectedChanged(item);
+                    }
+                    else
+                    {
+
+                        item->SetSelected(true);
+                        UpdateItemVisibility();
+
+                        SendItemSelectedChanged(item);
+                    }
+
+                    SetValueFirstSelected();
+
+                }
+                else
+                {
+                    SelectSingleItem(item, false);
+                }
+
+                return true;
+            }
+
+        }
+    }
+
+    if (ev.type == EVENT_TYPE_SHORTCUT)
+    {
+        return false;
+    }
+
+    return UIWidget::OnEvent(ev);
+}
 
 }

+ 22 - 1
Source/Atomic/UI/UIListView.h

@@ -58,16 +58,37 @@ public:
     bool GetExpanded(unsigned itemID);
     bool GetExpandable(unsigned itemID);
 
+    bool GetMultiSelect() const { return multiSelect_; }
+    void SetMultiSelect(bool value) { multiSelect_ = value; }
+
     void DeleteAllItems();
-    void SelectItemByID(const String& id);
+    void SelectItemByID(const String& id, bool selected = true);
 
     String GetHoverItemID() { return rootList_.Null() ? "" : rootList_->GetHoverItemID(); }
     String GetSelectedItemID() { return rootList_.Null() ? "" : rootList_->GetSelectedItemID(); }
 
     UISelectList* GetRootList() { return rootList_; }
 
+    void UpdateItemVisibility();
+
+    void SelectAllItems(bool select = true);
+
+protected:
+
+    virtual bool OnEvent(const tb::TBWidgetEvent &ev);
+
 private:
 
+    void SendItemSelectedChanged(ListViewItem* item);
+
+    void SelectSingleItem(ListViewItem* item, bool expand = true);
+    void SetValueFirstSelected();
+    void Move(tb::SPECIAL_KEY key);
+
+    bool multiSelect_;
+
+    float moveDelta_;
+
     SharedPtr<UISelectList> rootList_;
     ListViewItemSource* source_;
 

+ 50 - 0
Source/Atomic/UI/UISelectList.cpp

@@ -65,6 +65,24 @@ void UISelectList::SetFilter(const String& filter)
     ((TBSelectList*)widget_)->SetFilter(filter.CString());
 }
 
+int UISelectList::GetNumItems() const
+{
+    if (!widget_)
+        return 0;
+
+    return ((TBSelectList*)widget_)->GetNumItems();
+
+}
+
+void UISelectList::SelectItem(int index, bool selected)
+{
+    if (!widget_)
+        return;
+
+    ((TBSelectList*)widget_)->SelectItem(index, selected);
+
+}
+
 void UISelectList::SetValue(int value)
 {
     if (!widget_)
@@ -103,6 +121,28 @@ String UISelectList::GetSelectedItemID()
     GetSubsystem<UI>()->GetTBIDString(id, id_);
 
     return id_;
+}
+
+bool UISelectList::GetItemSelected(int index)
+{
+    if (!widget_)
+        return false;
+
+    return ((TBSelectList*)widget_)->GetItemSelected(index);
+}
+
+String UISelectList::GetItemID(int index)
+{
+    if (!widget_)
+        return "";
+
+    String _id;
+
+    TBID id = ((TBSelectList*)widget_)->GetItemID(index);
+
+    GetSubsystem<UI>()->GetTBIDString(id, _id);
+
+    return _id;
 
 }
 
@@ -217,4 +257,14 @@ void UISelectList::SelectPreviousItem()
     ((TBSelectList*)widget_)->ChangeValue(TB_KEY_UP);
 }
 
+void UISelectList::SetUIListView(bool value)
+{
+    if (!widget_)
+        return;
+
+    ((TBSelectList*)widget_)->SetUIListView(value);
+
+}
+
+
 }

+ 10 - 0
Source/Atomic/UI/UISelectList.h

@@ -51,15 +51,25 @@ public:
     double GetValue();
 
     String GetHoverItemID();
+
     String GetSelectedItemID();
 
     void ScrollToSelectedItem();
 
+    String GetItemID(int index);
+    bool GetItemSelected(int index);
+
+    int GetNumItems() const;
+
+    void SelectItem(int index, bool selected = true);
+
     tb::TBSelectList* GetTBSelectList();
 
     void SelectNextItem();
     void SelectPreviousItem();
 
+    void SetUIListView(bool value);
+
 protected:
 
     void HandleUIUpdate(StringHash eventType, VariantMap& eventData);

+ 27 - 2
Source/Atomic/UI/UIWidget.cpp

@@ -165,6 +165,15 @@ void UIWidget::ConvertEvent(UIWidget *handler, UIWidget* target, const tb::TBWid
         }
     }
 
+    unsigned modifierKeys = 0;
+
+    if( ev.special_key && TB_SHIFT)
+        modifierKeys |= QUAL_SHIFT;
+    if( ev.special_key && TB_CTRL)
+        modifierKeys |= QUAL_CTRL;
+    if( ev.special_key && TB_ALT)
+        modifierKeys |= QUAL_ALT;
+
     using namespace WidgetEvent;
     data[P_HANDLER] = handler;
     data[P_TARGET] = target;
@@ -175,8 +184,9 @@ void UIWidget::ConvertEvent(UIWidget *handler, UIWidget* target, const tb::TBWid
     data[P_DELTAY] = ev.delta_y;
     data[P_COUNT] = ev.count;
     data[P_KEY] = key;
+
     data[P_SPECIALKEY] = (unsigned) ev.special_key;
-    data[P_MODIFIERKEYS] = (unsigned) ev.modifierkeys;
+    data[P_MODIFIERKEYS] = modifierKeys;
     data[P_REFID] = refid;
     data[P_TOUCH] = (unsigned) ev.touch;
 }
@@ -644,7 +654,7 @@ bool UIWidget::OnEvent(const tb::TBWidgetEvent &ev)
 {
     UI* ui = GetSubsystem<UI>();
 
-    if (ev.type == EVENT_TYPE_CHANGED || ev.type == EVENT_TYPE_KEY_UP)
+    if ((ev.type == EVENT_TYPE_CHANGED && !ui->GetBlockChangedEvents()) || ev.type == EVENT_TYPE_KEY_UP)
     {
         if (!ev.target || ui->IsWidgetWrapped(ev.target))
         {
@@ -747,6 +757,21 @@ bool UIWidget::OnEvent(const tb::TBWidgetEvent &ev)
         }
 
     }
+    if (ev.type == EVENT_TYPE_CUSTOM)
+    {
+        if (!ev.target || ui->IsWidgetWrapped(ev.target))
+        {
+            VariantMap eventData;
+            ConvertEvent(this, ui->WrapWidget(ev.target), ev, eventData);
+            SendEvent(E_WIDGETEVENT, eventData);
+
+            if (eventData[WidgetEvent::P_HANDLED].GetBool())
+                return true;
+
+        }
+
+    }
+
 
     return false;
 }

+ 2 - 1
Source/Atomic/Web/Web.h

@@ -23,6 +23,7 @@
 #pragma once
 
 #include "../Core/Object.h"
+#include "../Container/ArrayPtr.h"
 #include "../IO/VectorBuffer.h"
 
 namespace Atomic
@@ -31,7 +32,7 @@ namespace Atomic
 class WebRequest;
 class WebSocket;
 
-class WebPrivate;
+struct WebPrivate;
 
 /// %Web subsystem. Manages HTTP requests and WebSocket communications.
 class ATOMIC_API Web : public Object

+ 135 - 120
Source/Atomic/Web/WebSocket.cpp

@@ -23,6 +23,7 @@
 #include "../Precompiled.h"
 
 #include "../Core/Profiler.h"
+#include "../IO/BufferQueue.h"
 #include "../IO/Log.h"
 #include "../Web/WebSocket.h"
 
@@ -38,9 +39,6 @@
 #include <websocketpp/config/asio_no_tls_client.hpp>
 #include <websocketpp/client.hpp>
 #include <iostream>
-
-#include "../Core/Thread.h"
-
 #include "../DebugNew.h"
 
 typedef websocketpp::client<websocketpp::config::asio_client> client;
@@ -56,164 +54,181 @@ namespace Atomic
 
 struct WebSocketInternalState
 {
-  /// The work queue.
-  asio::io_service service;
-  /// The WebSocket external state.
-  WebSocket &es;
-  /// URL.
-  String url;
-  /// Error string. Empty if no error.
-  String error;
-  /// Connection state.
-  WebSocketState state;
-  /// WebSocket client.
-  client c;
-  /// WebSocket connection.
-  client::connection_ptr con;
-
-  WebSocketInternalState(WebSocket &es_)
-    : es(es_)
-  {
-  }
-
-  ~WebSocketInternalState()
-  {
-  }
-
-  void OnOpen(websocketpp::connection_hdl hdl)
-  {
-    state = WS_OPEN;
-    LOGDEBUG("WebSocket CONNECTED to: " + url);
-    es.SendEvent("open");
-  }
-
-  void OnClose(websocketpp::connection_hdl hdl)
-  {
-    state = WS_CLOSED;
-    LOGDEBUG("WebSocket DISCONNECTED from: " + url);
-    es.SendEvent("close");
-  }
-
-  void OnFail(websocketpp::connection_hdl hdl)
-  {
-    state = WS_FAIL_TO_CONNECT;
-    LOGDEBUG("WebSocket FAILED to connect to: " + url);
-    es.SendEvent("fail_to_connect");
-  }
-
-  void OnMessage(websocketpp::connection_hdl hdl, message_ptr msg)
-  {
-    VariantMap eventData;
-    const std::string &payload(msg->get_payload());
-
-    eventData.Insert(MakePair(StringHash("type"), Variant(int(msg->get_opcode()))));
-
-    switch (msg->get_opcode())
+    /// The WebSocket external state.
+    WebSocket *es;
+    /// URL.
+    String url;
+    /// Error string. Empty if no error.
+    String error;
+    /// Connection state.
+    WebSocketState state;
+    /// WebSocket client.
+    client c;
+    /// WebSocket connection.
+    client::connection_ptr con;
+
+    WebSocketInternalState(WebSocket *es_)
+        : es(es_)
     {
-      case websocketpp::frame::opcode::text:
-        eventData.Insert(MakePair(StringHash("data"), Variant(String(payload.data(), payload.length()))));
-        es.SendEvent("message", eventData);
-        break;
-
-      case websocketpp::frame::opcode::binary:
-        eventData.Insert(MakePair(StringHash("data"), Variant(PODVector<unsigned char>((const unsigned char *)payload.data(), payload.length()))));
-        es.SendEvent("message", eventData);
-        break;
-
-      default:
-        eventData.Insert(MakePair(StringHash("data"), Variant(String(payload.data(), payload.length()))));
-        LOGWARNING("Unsupported WebSocket message type: " + String(int(msg->get_opcode())));
-        break;
+        LOGDEBUG("Create WebSocketInternalState");
     }
-  }
 
-  void MakeConnection()
-  {
-    websocketpp::lib::error_code ec;
-    con = c.get_connection(url.CString(), ec);
-    if (ec)
+    ~WebSocketInternalState()
     {
-      state = WS_INVALID;
-      error = ec.message().c_str();
-      LOGDEBUG("WebSocket error: " + error);
-      es.SendEvent("invalid");
-      return;
+        LOGDEBUG("Destroy WebSocketInternalState");
+    }
+
+    void OnOpen(websocketpp::connection_hdl hdl)
+    {
+        state = WS_OPEN;
+        LOGDEBUG("WebSocket CONNECTED to: " + url);
+        if (es)
+        {
+            es->SendEvent("open");
+        }
+    }
+
+    void OnClose(websocketpp::connection_hdl hdl)
+    {
+        state = WS_CLOSED;
+        LOGDEBUG("WebSocket DISCONNECTED from: " + url);
+        if (es)
+        {
+            es->SendEvent("close");
+        }
+    }
+
+    void OnFail(websocketpp::connection_hdl hdl)
+    {
+        state = WS_FAIL_TO_CONNECT;
+        LOGDEBUG("WebSocket FAILED to connect to: " + url);
+        if (es)
+        {
+            es->SendEvent("fail_to_connect");
+        }
+    }
+
+    void OnMessage(websocketpp::connection_hdl hdl, message_ptr msg)
+    {
+        if (!es)
+        {
+            return;
+        }
+        VariantMap eventData;
+        const std::string& payload(msg->get_payload());
+        SharedPtr<BufferQueue> message(new BufferQueue(es->GetContext()));
+        message->Write((const void*)payload.data(), (unsigned)payload.size());
+
+        eventData.Insert(MakePair(StringHash("type"), Variant(int(msg->get_opcode()))));
+        eventData.Insert(MakePair(StringHash("message"), Variant(message)));
+        es->SendEvent("message", eventData);
+    }
+
+    void MakeConnection()
+    {
+        websocketpp::lib::error_code ec;
+        con = c.get_connection(url.CString(), ec);
+        if (ec)
+        {
+            state = WS_INVALID;
+            error = ec.message().c_str();
+            LOGDEBUG("WebSocket error: " + error);
+            if (es)
+            {
+                es->SendEvent("invalid");
+            }
+            return;
+        }
+        c.connect(con);
     }
-    c.connect(con);
-  }
 };
 
 WebSocket::WebSocket(Context* context, const String& url) :
-  Object(context),
-  is_(new WebSocketInternalState(*this))
+    Object(context),
+    is_(new WebSocketInternalState(this))
 {
-  is_->url = url.Trimmed();
-  is_->state = WS_CONNECTING;
+    is_->url = url.Trimmed();
+    is_->state = WS_CONNECTING;
 
-  is_->c.clear_access_channels(websocketpp::log::alevel::all);
-  is_->c.clear_error_channels(websocketpp::log::elevel::all);
+    is_->c.clear_access_channels(websocketpp::log::alevel::all);
+    is_->c.clear_error_channels(websocketpp::log::elevel::all);
 }
 
 void WebSocket::setup(asio::io_service *service)
 {
-  websocketpp::lib::error_code ec;
-  is_->c.init_asio(service, ec);
-  if (ec)
-  {
-    is_->state = WS_INVALID;
-    is_->error = ec.message().c_str();
-    LOGDEBUG("WebSocket error: " + is_->error);
-    SendEvent("invalid");
-    return;
-  }
-  is_->c.set_open_handler(bind(&WebSocketInternalState::OnOpen, is_, ::_1));
-  is_->c.set_close_handler(bind(&WebSocketInternalState::OnClose, is_, ::_1));
-  is_->c.set_fail_handler(bind(&WebSocketInternalState::OnFail, is_, ::_1));
-  is_->c.set_message_handler(bind(&WebSocketInternalState::OnMessage, is_, ::_1, ::_2));
-
-  LOGDEBUG("WebSocket request to URL " + is_->url);
-
-  is_->MakeConnection();
+    LOGDEBUG("Create WebSocket");
+    websocketpp::lib::error_code ec;
+    is_->c.init_asio(service, ec);
+    if (ec)
+    {
+        is_->state = WS_INVALID;
+        is_->error = ec.message().c_str();
+        LOGDEBUG("WebSocket error: " + is_->error);
+        SendEvent("invalid");
+        return;
+    }
+    is_->c.set_open_handler(bind(&WebSocketInternalState::OnOpen, is_, ::_1));
+    is_->c.set_close_handler(bind(&WebSocketInternalState::OnClose, is_, ::_1));
+    is_->c.set_fail_handler(bind(&WebSocketInternalState::OnFail, is_, ::_1));
+    is_->c.set_message_handler(bind(&WebSocketInternalState::OnMessage, is_, ::_1, ::_2));
+
+    LOGDEBUG("WebSocket request to URL " + is_->url);
+
+    is_->MakeConnection();
 }
 
 WebSocket::~WebSocket()
 {
-  delete is_;
+    std::error_code ec;
+    is_->es = nullptr;
+    is_->con->terminate(ec);
+    is_->c.set_open_handler(nullptr);
+    is_->c.set_close_handler(nullptr);
+    is_->c.set_fail_handler(nullptr);
+    is_->c.set_message_handler(nullptr);
+    is_->con.reset();
+    is_.reset();
+    LOGDEBUG("Destroy WebSocket");
 }
 
 const String& WebSocket::GetURL() const
 {
-  return is_->url;
+    return is_->url;
 }
 
 String WebSocket::GetError() const
 {
-  return is_->error;
+    return is_->error;
 }
 
 WebSocketState WebSocket::GetState() const
 {
-  return is_->state;
+    return is_->state;
 }
 
-void WebSocket::Send(String message)
+void WebSocket::Send(const String& message)
 {
-  is_->c.send(is_->con, message.CString(), message.Length(), websocketpp::frame::opcode::text);
+    is_->c.send(is_->con, message.CString(), message.Length(), websocketpp::frame::opcode::text);
+}
+
+void WebSocket::SendBinary(const PODVector<unsigned char>& message)
+{
+    is_->c.send(is_->con, message.Buffer(), message.Size(), websocketpp::frame::opcode::binary);
 }
 
 void  WebSocket::Close()
 {
-  is_->state = WS_CLOSING;
-  websocketpp::lib::error_code ec;
-  LOGDEBUG("WebSocket atempting to close URL " + is_->url);
-  is_->con->terminate(ec);
+    is_->state = WS_CLOSING;
+    websocketpp::lib::error_code ec;
+    LOGDEBUG("WebSocket atempting to close URL " + is_->url);
+    is_->con->terminate(ec);
 }
 
 void WebSocket::OpenAgain()
 {
-  is_->state = WS_CONNECTING;
-  LOGDEBUG("WebSocket request (again) to URL " + is_->url);
-  is_->MakeConnection();
+    is_->state = WS_CONNECTING;
+    LOGDEBUG("WebSocket request (again) to URL " + is_->url);
+    is_->MakeConnection();
 }
 
 }

+ 28 - 3
Source/Atomic/Web/WebSocket.h

@@ -24,7 +24,9 @@
 
 #include "../Core/Object.h"
 #include "../Container/Str.h"
-#include "../IO/Deserializer.h"
+#include "../Container/Vector.h"
+
+#include <memory>
 
 namespace asio
 {
@@ -45,6 +47,26 @@ enum WebSocketState
     WS_FAIL_TO_CONNECT   // WebSocket attempted to open, but the server refused
 };
 
+enum WebSocketMessageType
+{
+    WSMT_CONTINUATION = 0x0,
+    WSMT_TEXT = 0x1,
+    WSMT_BINARY = 0x2,
+    WSMT_RSV3 = 0x3,
+    WSMT_RSV4 = 0x4,
+    WSMT_RSV5 = 0x5,
+    WSMT_RSV6 = 0x6,
+    WSMT_RSV7 = 0x7,
+    WSMT_CLOSE = 0x8,
+    WSMT_PING = 0x9,
+    WSMT_PONG = 0xA,
+    WSMT_CONTROL_RSVB = 0xB,
+    WSMT_CONTROL_RSVC = 0xC,
+    WSMT_CONTROL_RSVD = 0xD,
+    WSMT_CONTROL_RSVE = 0xE,
+    WSMT_CONTROL_RSVF = 0xF
+};
+
 struct WebSocketInternalState;
 
 /// A WebSocket connection.
@@ -69,7 +91,10 @@ public:
     WebSocketState GetState() const;
 
     /// Send a message.
-    void Send(String message);
+    void Send(const String& message);
+
+    /// Send a binary message.
+    void SendBinary(const PODVector<unsigned char>& message);
 
     /// Disconnect the WebSocket.
     void Close();
@@ -82,7 +107,7 @@ public:
 
 private:
     void setup(asio::io_service *service);
-    WebSocketInternalState* is_;
+    std::shared_ptr<WebSocketInternalState> is_;
 };
 
 }

+ 2 - 7
Source/AtomicEditor/EditorMode/AEEditorEvents.h

@@ -94,14 +94,9 @@ EVENT(E_EDITORMODAL, EditorModal)
     PARAM(P_MESSAGE, Message);    // for modal errors, error text
 }
 
-EVENT(E_EDITORACTIVESCENECHANGE, EditorActiveSceneChange)
+EVENT(E_EDITORACTIVESCENEEDITORCHANGE, EditorActiveSceneEditorChange)
 {
-    PARAM(P_SCENE, Scene);      // Scene*
-}
-
-EVENT(E_EDITORACTIVENODECHANGE, EditorActiveNodeChange)
-{
-    PARAM(P_NODE, Node);      // Node*
+    PARAM(P_SCENEEDITOR, SceneEditor);
 }
 
 

+ 1 - 1
Source/AtomicEditor/Editors/ResourceEditor.h

@@ -53,7 +53,7 @@ public:
 
     UIWidget* GetRootContentWidget() { return rootContentWidget_; }
 
-    void InvokeShortcut(const String& shortcut);
+    virtual void InvokeShortcut(const String& shortcut);
 
     void RequestClose();
 

+ 30 - 25
Source/AtomicEditor/Editors/SceneEditor3D/Gizmo3D.cpp

@@ -15,6 +15,8 @@
 #include <Atomic/Input/Input.h>
 
 #include "SceneEditor3DEvents.h"
+#include "SceneSelection.h"
+#include "SceneEditor3D.h"
 #include "Gizmo3D.h"
 
 namespace AtomicEditor
@@ -59,6 +61,7 @@ void Gizmo3D::SetView(SceneView3D* view3D)
     view3D_ = view3D;
     scene_ = view3D->GetScene();
     camera_ = view3D->GetCameraNode()->GetComponent<Camera>();
+    selection_ = view3D_->GetSceneEditor3D()->GetSelection();
     assert(camera_.NotNull());
 }
 
@@ -67,30 +70,32 @@ void Gizmo3D::Position()
     Vector3 center(0, 0, 0);
     bool containsScene = false;
 
-    for (unsigned i = 0; i < editNodes_->Size(); ++i)
+    Vector<SharedPtr<Node>>& editNodes = selection_->GetNodes();
+
+    for (unsigned i = 0; i < editNodes.Size(); ++i)
     {
         // Scene's transform should not be edited, so hide gizmo if it is included
-        if (editNodes_->At(i) == scene_)
+        if (editNodes[i] == scene_)
         {
             containsScene = true;
             break;
         }
-        center += editNodes_->At(i)->GetWorldPosition();
+        center += editNodes[i]->GetWorldPosition();
     }
 
-    if (editNodes_->Empty() || containsScene)
+    if (editNodes.Empty() || containsScene)
     {
         Hide();
         return;
     }
 
-    center /= editNodes_->Size();
+    center /= editNodes.Size();
     gizmoNode_->SetPosition(center);
 
-    if (axisMode_ == AXIS_WORLD || editNodes_->Size() > 1)
+    if (axisMode_ == AXIS_WORLD || editNodes.Size() > 1)
         gizmoNode_->SetRotation(Quaternion());
     else
-        gizmoNode_->SetRotation(editNodes_->At(0)->GetWorldRotation());
+        gizmoNode_->SetRotation(editNodes[0]->GetWorldRotation());
 
     ResourceCache* cache = GetSubsystem<ResourceCache>();
 
@@ -137,10 +142,8 @@ void Gizmo3D::Position()
     }
 }
 
-void Gizmo3D::Update(Vector<Node *> &editNodes)
+void Gizmo3D::Update()
 {
-    editNodes_ = &editNodes;
-
     Use();
     Position();
 }
@@ -156,14 +159,13 @@ void Gizmo3D::Use()
 {
     if (gizmo_.Null() || !gizmo_->IsEnabled() || editMode_ == EDIT_SELECT)
     {
-        //StoreGizmoEditActions();
-        //previousGizmoDrag = false;
         return;
     }
 
     ResourceCache* cache = GetSubsystem<ResourceCache>();
     Input* input = GetSubsystem<Input>();
 
+    Vector<SharedPtr<Node>>& editNodes = selection_->GetNodes();
     Ray cameraRay = view3D_->GetCameraRay();
     float scale = gizmoNode_->GetScale().x_;
 
@@ -173,10 +175,7 @@ void Gizmo3D::Use()
     {
         if (dragging_)
         {
-            VariantMap eventData;
-            eventData[SceneEditSerializable::P_SERIALIZABLE] = editNodes_->At(0);
-            eventData[SceneEditSerializable::P_OPERATION] = 1;
-            scene_->SendEvent(E_SCENEEDITSERIALIZABLE, eventData);
+            scene_->SendEvent(E_SCENEEDITEND);
             dragging_ = false;
         }
 
@@ -187,7 +186,7 @@ void Gizmo3D::Use()
     gizmoAxisY_.Update(cameraRay, scale, drag, camera_->GetNode());
     gizmoAxisZ_.Update(cameraRay, scale, drag, camera_->GetNode());
 
-    if (!editNodes_->Size() || editNodes_->At(0) == scene_)
+    if (!editNodes.Size() || editNodes[0] == scene_)
     {
         gizmoAxisX_.selected_ = gizmoAxisY_.selected_ = gizmoAxisZ_.selected_ = false;
         // this just forces an update
@@ -238,9 +237,11 @@ bool Gizmo3D::MoveEditNodes(Vector3 adjust)
     bool moveSnap = input->GetKeyDown(KEY_LCTRL) || input->GetKeyDown(KEY_RCTRL);
 #endif
 
+    Vector<SharedPtr<Node>>& editNodes = selection_->GetNodes();
+
     if (adjust.Length() > M_EPSILON)
     {
-        for (unsigned i = 0; i < editNodes_->Size(); ++i)
+        for (unsigned i = 0; i < editNodes.Size(); ++i)
         {
             if (moveSnap)
             {
@@ -252,9 +253,9 @@ bool Gizmo3D::MoveEditNodes(Vector3 adjust)
                 adjust.z_ = floorf(adjust.z_ / moveStepScaled + 0.5) * moveStepScaled;
             }
 
-            Node* node = editNodes_->At(i);
+            Node* node = editNodes[i];
             Vector3 nodeAdjust = adjust;
-            if (axisMode_ == AXIS_LOCAL && editNodes_->Size() == 1)
+            if (axisMode_ == AXIS_LOCAL && editNodes.Size() == 1)
                 nodeAdjust = node->GetWorldRotation() * nodeAdjust;
 
             Vector3 worldPos = node->GetWorldPosition();
@@ -289,6 +290,8 @@ bool Gizmo3D::RotateEditNodes(Vector3 adjust)
     bool rotateSnap = input->GetKeyDown(KEY_LCTRL) || input->GetKeyDown(KEY_RCTRL);
 #endif
 
+    Vector<SharedPtr<Node>>& editNodes = selection_->GetNodes();
+
     if (rotateSnap)
     {
         float rotateStepScaled = snapRotation_;
@@ -301,11 +304,12 @@ bool Gizmo3D::RotateEditNodes(Vector3 adjust)
     {
         moved = true;
 
-        for (unsigned i = 0; i < editNodes_->Size(); ++i)
+        for (unsigned i = 0; i < editNodes.Size(); ++i)
         {
-            Node* node = editNodes_->At(i);
+            Node* node = editNodes[i];
+
             Quaternion rotQuat(adjust.x_, adjust.y_, adjust.z_);
-            if (axisMode_ == AXIS_LOCAL && editNodes_->Size() == 1)
+            if (axisMode_ == AXIS_LOCAL && editNodes.Size() == 1)
                 node->SetRotation(node->GetRotation() * rotQuat);
             else
             {
@@ -336,12 +340,13 @@ bool Gizmo3D::ScaleEditNodes(Vector3 adjust)
     bool scaleSnap = input->GetKeyDown(KEY_LCTRL) || input->GetKeyDown(KEY_RCTRL);
 #endif
 
+    Vector<SharedPtr<Node>>& editNodes = selection_->GetNodes();
 
     if (adjust.Length() > M_EPSILON)
     {
-        for (unsigned i = 0; i < editNodes_->Size(); ++i)
+        for (unsigned i = 0; i < editNodes.Size(); ++i)
         {
-            Node* node = editNodes_->At(i);
+            Node* node = editNodes[i];
 
             Vector3 scale = node->GetScale();
             Vector3 oldScale = scale;

+ 5 - 2
Source/AtomicEditor/Editors/SceneEditor3D/Gizmo3D.h

@@ -31,6 +31,8 @@ namespace Atomic
 namespace AtomicEditor
 {
 
+class SceneSelection;
+
 class Gizmo3DAxis
 {
 public:
@@ -121,7 +123,7 @@ public:
 
     void Show();
     void Hide();
-    void Update(Vector<Node*>& editNodes);
+    void Update();
 
     Node* GetGizmoNode() { return gizmoNode_; }
 
@@ -155,6 +157,7 @@ private:
     WeakPtr<Scene> scene_;
     WeakPtr<Camera> camera_;
     WeakPtr<StaticModel> gizmo_;
+    WeakPtr<SceneSelection> selection_;
 
     Gizmo3DAxis gizmoAxisX_;
     Gizmo3DAxis gizmoAxisY_;
@@ -165,9 +168,9 @@ private:
 
     AxisMode axisMode_;
 
-    Vector<Node *> *editNodes_;
     bool dragging_;
 
+    // snap settings
     float snapTranslationX_;
     float snapTranslationY_;
     float snapTranslationZ_;

+ 189 - 64
Source/AtomicEditor/Editors/SceneEditor3D/SceneEditHistory.cpp

@@ -1,25 +1,38 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// LICENSE: Atomic Game Engine Editor and Tools EULA
+// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
+// license information: https://github.com/AtomicGameEngine/AtomicGameEngine
+//
 
 #include <Atomic/IO/Log.h>
-#include <Atomic/Core/Timer.h>
-#include <Atomic/Scene/Scene.h>
-#include <Atomic/Scene/Component.h>
 #include <Atomic/Scene/SceneEvents.h>
 
-#include "../../EditorMode/AEEditorEvents.h"
+#include "SceneEditor3D.h"
 
-#include "SceneEditOps.h"
 #include "SceneEditor3DEvents.h"
+#include "SceneEditOp.h"
+#include "SceneSelection.h"
 #include "SceneEditHistory.h"
 
 namespace AtomicEditor
 {
 
-SceneEditHistory::SceneEditHistory(Context* context, Scene* scene) :
-    Object(context),
-    scene_(scene)
+SceneEditHistory::SceneEditHistory(Context* context, SceneEditor3D* sceneEditor) : Object(context),
+    sceneEditor_(sceneEditor),
+    curSelEditOp_(0),
+    addingRemovingNodes_(false)
 {
-    SubscribeToEvent(scene_, E_SCENEEDITSERIALIZABLE, HANDLER(SceneEditHistory, HandleSceneEditSerializable));    
-    SubscribeToEvent(scene_, E_SCENEEDITNODEADDEDREMOVED, HANDLER(SceneEditHistory, HandleSceneEditNodeAddedRemoved));
+    SubscribeToEvent(sceneEditor_->GetScene(), E_SCENENODESELECTED, HANDLER(SceneEditHistory, HandleSceneNodeSelected));
+
+    SubscribeToEvent(sceneEditor_->GetScene(), E_SCENEEDITBEGIN, HANDLER(SceneEditHistory, HandleSceneEditBegin));
+    SubscribeToEvent(sceneEditor_->GetScene(), E_SCENEEDITEND, HANDLER(SceneEditHistory, HandleSceneEditEnd));
+
+    SubscribeToEvent(sceneEditor_->GetScene(), E_SCENEEDITADDREMOVENODES, HANDLER(SceneEditHistory, HandleSceneEditAddRemoveNodes));
+
+    SubscribeToEvent(sceneEditor_->GetScene(), E_SCENEEDITNODEADDED, HANDLER(SceneEditHistory, HandleSceneEditNodeAdded));
+    SubscribeToEvent(sceneEditor_->GetScene(), E_SCENEEDITNODEREMOVED, HANDLER(SceneEditHistory, HandleSceneEditNodeRemoved));
+
 }
 
 SceneEditHistory::~SceneEditHistory()
@@ -27,84 +40,111 @@ SceneEditHistory::~SceneEditHistory()
 
 }
 
-void SceneEditHistory::AddUndoOp(SceneEditOp* op)
+void SceneEditHistory::HandleSceneEditBegin(StringHash eventType, VariantMap& eventData)
 {
-    undoHistory_.Push(op);
+    assert(0);
+    BeginSelectionEdit();
+}
 
-    scene_->SendEvent(E_SCENEEDITSCENEMODIFIED);
+void SceneEditHistory::HandleSceneEditEnd(StringHash eventType, VariantMap& eventData)
+{
+    EndSelectionEdit();
+}
 
-    for (unsigned i = 0; i < redoHistory_.Size(); i++)
+void SceneEditHistory::HandleSceneEditAddRemoveNodes(StringHash eventType, VariantMap& eventData)
+{
+
+    bool end = eventData[SceneEditAddRemoveNodes::P_END].GetBool();
+
+    if (end)
     {
-        delete redoHistory_[i];
+        addingRemovingNodes_ = false;
+
+        EndSelectionEdit(true);
     }
+    else
+    {
+        addingRemovingNodes_ = true;
+        EndSelectionEdit(false);
 
-    redoHistory_.Clear();
+        curSelEditOp_ = new SelectionEditOp(sceneEditor_->GetScene());
+
+    }
 }
 
-void SceneEditHistory::HandleSceneEditSerializableUndoRedo(StringHash eventType, VariantMap& eventData)
+void SceneEditHistory::HandleSceneEditNodeAdded(StringHash eventType, VariantMap& eventData)
 {
-    SharedPtr<Serializable> serial(static_cast<Serializable*>(eventData[SceneEditSerializableUndoRedo::P_SERIALIZABLE].GetPtr()));
+    if (!addingRemovingNodes_)
+        return;
 
-    if (editStates_.Contains(serial))
-    {
-        scene_->SendEvent(E_SCENEEDITSCENEMODIFIED);
-        editStates_[serial] = eventData[SceneEditSerializableUndoRedo::P_STATE].GetVectorBuffer();
-    }
+    assert(curSelEditOp_);
+
+    Node* node = static_cast<Node*>(eventData[NodeAdded::P_NODE].GetPtr());
+    Node* parent = static_cast<Node*>(eventData[NodeAdded::P_PARENT].GetPtr());
+
+    curSelEditOp_->NodeAdded(node, parent);
 }
 
-void SceneEditHistory::HandleSceneEditNodeAddedRemoved(StringHash eventType, VariantMap& eventData)
+void SceneEditHistory::HandleSceneEditNodeRemoved(StringHash eventType, VariantMap& eventData)
 {
-    bool added = eventData[SceneEditNodeAddedRemoved::P_ADDED].GetBool();
-    Node* node = static_cast<Node*>(eventData[SceneEditNodeAddedRemoved::P_NODE].GetPtr());
-    NodeAddedRemovedOp* op = new NodeAddedRemovedOp(node, added);
-    AddUndoOp(op);
+    if (!addingRemovingNodes_)
+        return;
+
+    assert(curSelEditOp_);
+
+    Node* node = static_cast<Node*>(eventData[NodeAdded::P_NODE].GetPtr());
+    Node* parent = static_cast<Node*>(eventData[NodeAdded::P_PARENT].GetPtr());
+
+    curSelEditOp_->NodeRemoved(node, parent);
 }
 
-void SceneEditHistory::HandleSceneEditSerializable(StringHash eventType, VariantMap& eventData)
+void SceneEditHistory::AddUndoOp(SelectionEditOp* op)
 {
+    undoHistory_.Push(op);
 
-    int editop = eventData[SceneEditSerializable::P_OPERATION].GetInt();
-
-    SharedPtr<Serializable> serial(static_cast<Serializable*>(eventData[SceneEditSerializable::P_SERIALIZABLE].GetPtr()));
+    sceneEditor_->GetScene()->SendEvent(E_SCENEEDITSCENEMODIFIED);
 
-    if (editop == 0) // begin
+    for (unsigned i = 0; i < redoHistory_.Size(); i++)
     {
-        if (editStates_.Contains(serial))
-            return;
-
-        SubscribeToEvent(serial, E_SCENEEDITSERIALIZABLEUNDOREDO, HANDLER(SceneEditHistory, HandleSceneEditSerializableUndoRedo));
-        VectorBuffer& vb = editStates_[serial];
-        vb.Clear();
-        serial->Serializable::Save(vb);
-        vb.Seek(0);
+        delete redoHistory_[i];
     }
-    else if (editop == 1) // change
-    {
-        if (!editStates_.Contains(serial))
-            return;
 
-        VectorBuffer& beginState = editStates_[serial];
-        VectorBuffer deltaState;
-        serial->Serializable::Save(deltaState);
-        deltaState.Seek(0);
+    redoHistory_.Clear();
+}
 
-        if (beginState.GetSize() != deltaState.GetSize() ||
-                memcmp(beginState.GetData(), deltaState.GetData(), deltaState.GetSize()))
-        {
-            SerializableEditOp* op = new SerializableEditOp(serial, beginState, deltaState);
-            AddUndoOp(op);
-            editStates_[serial] = deltaState;
-        }
+void SceneEditHistory::BeginSelectionEdit()
+{
+    assert(!curSelEditOp_);
+
+    Vector<SharedPtr<Node>>& nodes = sceneEditor_->GetSelection()->GetNodes();
+    if (!nodes.Size())
+        return;
+
+    curSelEditOp_ = new SelectionEditOp(sceneEditor_->GetScene());
+    curSelEditOp_->SetNodes(nodes);
+}
+
+void SceneEditHistory::EndSelectionEdit(bool begin)
+{
+    if (!curSelEditOp_)
+        return;
+
+    curSelEditOp_->RegisterEdit();
+
+    if (curSelEditOp_->Commit())
+    {
+        AddUndoOp(curSelEditOp_);
     }
-    else if (editop == 2) // end
+    else
     {
-        if (!editStates_.Contains(serial))
-            return;
+        delete curSelEditOp_;
+    }
 
-        UnsubscribeFromEvent(serial, E_SCENEEDITSERIALIZABLEUNDOREDO);
+    curSelEditOp_ = 0;
+
+    if (begin)
+        BeginSelectionEdit();
 
-        editStates_.Erase(serial);
-    }
 }
 
 void SceneEditHistory::Undo()
@@ -112,13 +152,22 @@ void SceneEditHistory::Undo()
     if (!undoHistory_.Size())
         return;
 
-    SceneEditOp* op = undoHistory_.Back();
+    SelectionEditOp* op = undoHistory_.Back();
     undoHistory_.Pop();
 
     op->Undo();
+    sceneEditor_->GetScene()->SendEvent(E_SCENEEDITSCENEMODIFIED);
 
     redoHistory_.Push(op);
 
+    if (curSelEditOp_)
+    {
+        delete curSelEditOp_;
+        curSelEditOp_ = 0;
+    }
+
+    BeginSelectionEdit();
+
 }
 
 void SceneEditHistory::Redo()
@@ -126,13 +175,89 @@ void SceneEditHistory::Redo()
     if (!redoHistory_.Size())
         return;
 
-    SceneEditOp* op = redoHistory_.Back();
+    SelectionEditOp* op = redoHistory_.Back();
     redoHistory_.Pop();
 
     op->Redo();
+    sceneEditor_->GetScene()->SendEvent(E_SCENEEDITSCENEMODIFIED);
 
     undoHistory_.Push(op);
 
+    if (curSelEditOp_)
+    {
+        delete curSelEditOp_;
+        curSelEditOp_ = 0;
+    }
+
+    BeginSelectionEdit();
+
+
 }
 
+
+void SceneEditHistory::HandleSceneNodeSelected(StringHash eventType, VariantMap& eventData)
+{
+
+    if (eventData[SceneNodeSelected::P_QUIET].GetBool())
+        return;
+
+    if (curSelEditOp_)
+    {
+        EndSelectionEdit();
+    }
+    else
+        BeginSelectionEdit();
 }
+
+void SceneEditHistory::RemoveNode(Node *node)
+{
+    PODVector<SelectionEditOp*> remove;
+
+    for (unsigned i = 0; i < undoHistory_.Size(); i++)
+    {
+
+        SelectionEditOp* op = undoHistory_[i];
+
+        if (op->EraseNode(node))
+        {
+            assert(!remove.Contains(op));
+            remove.Push(op);
+        }
+
+    }
+
+    for (unsigned i = 0; i < redoHistory_.Size(); i++)
+    {
+
+        SelectionEditOp* op = redoHistory_[i];
+
+        if (op->EraseNode(node))
+        {
+            assert(!remove.Contains(op));
+            remove.Push(op);
+        }
+
+    }
+
+    for (unsigned i = 0; i < remove.Size(); i++)
+    {
+        PODVector<SelectionEditOp*>::Iterator itr = undoHistory_.Find(remove[i]);
+
+        if (itr != undoHistory_.End())
+        {
+            delete *itr;
+            undoHistory_.Erase(itr);
+        }
+        else
+        {
+            itr = redoHistory_.Find(remove[i]);
+            delete *itr;
+            redoHistory_.Erase(itr);
+        }
+    }
+
+
+}
+
+}
+

+ 25 - 15
Source/AtomicEditor/Editors/SceneEditor3D/SceneEditHistory.h

@@ -10,47 +10,57 @@
 #include <Atomic/Core/Object.h>
 
 using namespace Atomic;
+
 namespace Atomic
 {
-
-class Serializable;
-class Scene;
-
+class Node;
 }
 
 namespace AtomicEditor
 {
 
-class SceneEditOp;
+class SceneEditor3D;
+class SelectionEditOp;
 
-/// Simple scene history to support undo/redo via snapshots of scene state
 class SceneEditHistory: public Object
 {
     OBJECT(SceneEditHistory);
 
 public:
 
-    SceneEditHistory(Context* context, Scene* scene);
+    SceneEditHistory(Context* context, SceneEditor3D* sceneEditor);
     virtual ~SceneEditHistory();
 
+    void BeginSelectionEdit();
+    void EndSelectionEdit(bool begin = true);
+
+    /// Removes a node from the edit history
+    void RemoveNode(Node* node);
+
     void Undo();
     void Redo();
 
 private:
 
-    void HandleSceneEditSerializable(StringHash eventType, VariantMap& eventData);
-    void HandleSceneEditSerializableUndoRedo(StringHash eventType, VariantMap& eventData);
-    void HandleSceneEditNodeAddedRemoved(StringHash eventType, VariantMap& eventData);
+    void HandleSceneNodeSelected(StringHash eventType, VariantMap& eventData);
+    void HandleSceneEditBegin(StringHash eventType, VariantMap& eventData);
+    void HandleSceneEditEnd(StringHash eventType, VariantMap& eventData);
+    void HandleSceneEditAddRemoveNodes(StringHash eventType, VariantMap& eventData);
+    void HandleSceneEditNodeAdded(StringHash eventType, VariantMap& eventData);
+    void HandleSceneEditNodeRemoved(StringHash eventType, VariantMap& eventData);
+
+    void AddUndoOp(SelectionEditOp* op);
 
-    void AddUndoOp(SceneEditOp* op);
+    SharedPtr<SceneEditor3D> sceneEditor_;
 
-    WeakPtr<Scene> scene_;
+    SelectionEditOp* curSelEditOp_;
 
-    HashMap< SharedPtr<Serializable>, VectorBuffer > editStates_;
+    PODVector<SelectionEditOp*> undoHistory_;
+    PODVector<SelectionEditOp*> redoHistory_;
 
-    PODVector<SceneEditOp*> undoHistory_;
-    PODVector<SceneEditOp*> redoHistory_;
+    bool addingRemovingNodes_;
 
 };
 
 }
+

+ 429 - 0
Source/AtomicEditor/Editors/SceneEditor3D/SceneEditOp.cpp

@@ -0,0 +1,429 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// LICENSE: Atomic Game Engine Editor and Tools EULA
+// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
+// license information: https://github.com/AtomicGameEngine/AtomicGameEngine
+//
+
+#include <Atomic/IO/Log.h>
+#include <Atomic/Scene/Node.h>
+#include <Atomic/Scene/Component.h>
+#include <Atomic/Scene/PrefabComponent.h>
+#include <Atomic/Scene/Scene.h>
+
+#include "SceneEditOp.h"
+#include "SceneEditor3DEvents.h"
+
+namespace AtomicEditor
+{
+
+SelectionEditOp::SelectionEditOp(Scene *scene) : SceneEditOp(scene, SCENEEDIT_SELECTION)
+{
+}
+
+SelectionEditOp::~SelectionEditOp()
+{
+
+    for (unsigned i = 0; i < editNodes_.Size(); i++)
+    {
+        EditNode* enode = editNodes_[i];
+        for (unsigned j = 0; j < enode->components_.Size(); j++)
+            delete enode->components_[j];
+        delete enode;
+    }
+
+}
+
+bool SelectionEditOp::EraseNode(Node *node)
+{
+    PODVector<EditNode*>::Iterator itr = editNodes_.Begin();
+
+    while (itr != editNodes_.End())
+    {
+        if ((*itr)->node_ == node)
+        {
+            for (unsigned j = 0; j < (*itr)->components_.Size(); j++)
+                delete (*itr)->components_[j];
+
+            delete *itr;
+            editNodes_.Erase(itr);
+            break;
+        }
+
+        itr++;
+    }
+
+    return editNodes_.Size() == 0;
+}
+
+void SelectionEditOp::AddNode(Node* node)
+{
+
+    for (unsigned i = 0; i < editNodes_.Size(); i++)
+    {
+        if (editNodes_[i]->node_ == node)
+            return;
+    }
+
+    EditNode* enode = new EditNode();
+    enode->node_ = node;
+    enode->parentBegin_ = enode->parentEnd_ = node->GetParent();
+    node->Serializable::Save(enode->stateBegin_);
+    enode->stateBegin_.Seek(0);
+    enode->stateEnd_ = enode->stateBegin_;
+
+    const Vector<SharedPtr<Component>>& components = node->GetComponents();
+
+    for (unsigned j = 0; j < components.Size(); j++)
+    {
+        Component* component = components[j];
+
+        EditComponent* ecomponent = new EditComponent();
+        ecomponent->component_ = component;
+        ecomponent->nodeBegin_ = ecomponent->nodeEnd_ = node;
+        component->Serializable::Save(ecomponent->stateBegin_);
+        ecomponent->stateBegin_.Seek(0);
+        ecomponent->stateEnd_ = ecomponent->stateBegin_;
+
+        enode->components_.Push(ecomponent);
+    }
+
+    editNodes_.Push(enode);
+
+}
+
+void SelectionEditOp::NodeAdded(Node* node, Node* parent)
+{
+    AddNode(node);
+
+    for (unsigned i = 0; i < editNodes_.Size(); i++)
+    {
+        if (editNodes_[i]->node_ == node)
+        {
+            editNodes_[i]->parentBegin_ = 0;
+            editNodes_[i]->parentEnd_ = parent;
+            return;
+        }
+    }
+}
+
+void SelectionEditOp::NodeRemoved(Node* node, Node* parent)
+{
+    AddNode(node);
+
+    for (unsigned i = 0; i < editNodes_.Size(); i++)
+    {
+        if (editNodes_[i]->node_ == node)
+        {
+            editNodes_[i]->parentBegin_ = parent;
+            editNodes_[i]->parentEnd_ = 0;
+            return;
+        }
+    }
+}
+
+void SelectionEditOp::SetNodes(Vector<SharedPtr<Node> > &nodes)
+{
+    // Generate initial snapshot
+    for (unsigned i = 0; i < nodes.Size(); i++)
+    {
+        AddNode(nodes[i]);
+
+    }
+}
+
+bool SelectionEditOp::Commit()
+{
+    // See if any nodes, components have been edited
+    for (unsigned i = 0; i < editNodes_.Size(); i++)
+    {
+        EditNode* enode = editNodes_[i];
+
+        if (enode->parentBegin_ != enode->parentEnd_)
+            return true;
+
+        if (!CompareStates(enode->stateBegin_, enode->stateEnd_))
+            return true;
+
+        for (unsigned j = 0; j < enode->components_.Size(); j++)
+        {
+            EditComponent* ecomponent = enode->components_[j];
+
+            if (ecomponent->nodeBegin_ != ecomponent->nodeEnd_)
+                return true;
+
+            if (!CompareStates(ecomponent->stateBegin_, ecomponent->stateEnd_))
+                return true;
+
+        }
+
+    }
+
+    return false;
+
+}
+
+void SelectionEditOp::RegisterEdit()
+{
+    for (unsigned i = 0; i < editNodes_.Size(); i++)
+    {
+        EditNode* enode = editNodes_[i];
+        enode->stateEnd_.Clear();
+        enode->node_->Serializable::Save(enode->stateEnd_);
+        enode->stateEnd_.Seek(0);
+        enode->parentEnd_ = enode->node_->GetParent();
+
+        for (unsigned j = 0; j < enode->components_.Size(); j++)
+        {
+            EditComponent* ecomponent = enode->components_[j];
+            ecomponent->stateEnd_.Clear();
+            ecomponent->component_->Serializable::Save(ecomponent->stateEnd_);
+            ecomponent->stateEnd_.Seek(0);
+            ecomponent->nodeEnd_ = ecomponent->component_->GetNode();
+        }
+
+    }
+
+}
+
+bool SelectionEditOp::Undo()
+{
+
+    scene_->SendEvent(E_SCENEEDITSTATECHANGESBEGIN);
+
+    for (unsigned i = 0; i < editNodes_.Size(); i++)
+    {
+        EditNode* enode = editNodes_[i];
+
+        Node* node = enode->node_;
+
+        bool changed = !CompareStates(enode->stateBegin_, enode->stateEnd_);
+        if (changed && !node->Serializable::Load(enode->stateBegin_))
+        {
+            LOGERRORF("Unable to Undo node serializable");
+            return false;
+        }
+
+        if (changed)
+        {
+            VariantMap eventData;
+            eventData[SceneEditStateChange::P_SERIALIZABLE] = node;
+            node->SendEvent(E_SCENEEDITSTATECHANGE, eventData);
+        }
+
+        enode->stateBegin_.Seek(0);
+
+        if (node->GetParent() != enode->parentBegin_)
+        {
+            if(enode->parentBegin_.NotNull())
+            {
+                // moving back to original parent
+
+                if (node->GetParent())
+                {
+                    VariantMap nodeRemovedEventData;
+                    nodeRemovedEventData[SceneEditNodeRemoved::P_NODE] = node;
+                    nodeRemovedEventData[SceneEditNodeRemoved::P_PARENT] =  node->GetParent();
+                    nodeRemovedEventData[SceneEditNodeRemoved::P_SCENE] = scene_;
+                    scene_->SendEvent(E_SCENEEDITNODEREMOVED, nodeRemovedEventData);
+                }
+
+                node->Remove();
+                enode->parentBegin_->AddChild(node);
+
+                VariantMap nodeAddedEventData;
+                nodeAddedEventData[SceneEditNodeAdded::P_NODE] = node;
+                nodeAddedEventData[SceneEditNodeAdded::P_PARENT] = enode->parentBegin_;
+                nodeAddedEventData[SceneEditNodeAdded::P_SCENE] = scene_;
+                scene_->SendEvent(E_SCENEEDITNODEADDED, nodeAddedEventData);
+
+            }
+            else
+            {
+                VariantMap nodeRemovedEventData;
+                nodeRemovedEventData[SceneEditNodeRemoved::P_NODE] = node;
+                nodeRemovedEventData[SceneEditNodeRemoved::P_PARENT] =  enode->parentEnd_;
+                nodeRemovedEventData[SceneEditNodeRemoved::P_SCENE] = scene_;
+                scene_->SendEvent(E_SCENEEDITNODEREMOVED, nodeRemovedEventData);
+                node->Remove();
+            }
+
+        }
+
+        for (unsigned j = 0; j < enode->components_.Size(); j++)
+        {
+            EditComponent* ecomponent = enode->components_[j];
+            Component* component = ecomponent->component_;
+
+            changed = !CompareStates(ecomponent->stateBegin_, ecomponent->stateEnd_);
+            if (changed && !component->Serializable::Load(ecomponent->stateBegin_))
+            {
+                LOGERRORF("Unable to Undo component serializable");
+                return false;
+            }
+
+            if (changed)
+            {
+                VariantMap eventData;
+                eventData[SceneEditStateChange::P_SERIALIZABLE] = component;
+                component->SendEvent(E_SCENEEDITSTATECHANGE, eventData);
+            }
+
+
+            ecomponent->stateBegin_.Seek(0);
+
+            if (component->GetNode() != ecomponent->nodeBegin_)
+            {
+                component->Remove();
+
+                bool add = ecomponent->nodeBegin_.NotNull();
+
+                VariantMap caData;
+                caData[SceneEditComponentAddedRemoved::P_SCENE] = scene_;
+                caData[SceneEditComponentAddedRemoved::P_COMPONENT] = component;
+
+                if (add)
+                {
+                    ecomponent->nodeBegin_->AddComponent(component, 0, REPLICATED);
+                    caData[SceneEditComponentAddedRemoved::P_NODE] = ecomponent->nodeBegin_;
+                    caData[SceneEditComponentAddedRemoved::P_REMOVED] = false;
+                }
+                else
+                {
+                    caData[SceneEditComponentAddedRemoved::P_NODE] = ecomponent->nodeEnd_;
+                    caData[SceneEditComponentAddedRemoved::P_REMOVED] = true;
+                }
+
+                scene_->SendEvent(E_SCENEEDITCOMPONENTADDEDREMOVED, caData);
+            }
+
+        }
+
+    }
+
+    scene_->SendEvent(E_SCENEEDITSTATECHANGESEND);
+
+    return true;
+}
+
+bool SelectionEditOp::Redo()
+{
+    scene_->SendEvent(E_SCENEEDITSTATECHANGESBEGIN);
+
+    for (unsigned i = 0; i < editNodes_.Size(); i++)
+    {
+        EditNode* enode = editNodes_[i];
+
+        Node* node = enode->node_;
+
+        bool changed = !CompareStates(enode->stateBegin_, enode->stateEnd_);
+        if ( changed && !node->Serializable::Load(enode->stateEnd_))
+        {
+            LOGERRORF("Unable to Redo node serializable");
+            return false;
+        }
+
+        enode->stateEnd_.Seek(0);
+
+        if (changed)
+        {
+            VariantMap eventData;
+            eventData[SceneEditStateChange::P_SERIALIZABLE] = node;
+            node->SendEvent(E_SCENEEDITSTATECHANGE, eventData);
+        }
+
+        if (node->GetParent() != enode->parentEnd_)
+        {
+
+            if(enode->parentEnd_.NotNull())
+            {
+                if (node->GetParent())
+                {
+                    VariantMap nodeRemovedEventData;
+                    nodeRemovedEventData[SceneEditNodeRemoved::P_NODE] = node;
+                    nodeRemovedEventData[SceneEditNodeRemoved::P_PARENT] =  node->GetParent();
+                    nodeRemovedEventData[SceneEditNodeRemoved::P_SCENE] = scene_;
+                    scene_->SendEvent(E_SCENEEDITNODEREMOVED, nodeRemovedEventData);
+                }
+
+                node->Remove();
+                enode->parentEnd_->AddChild(node);
+
+                VariantMap nodeAddedEventData;
+                nodeAddedEventData[SceneEditNodeAdded::P_NODE] = node;
+                nodeAddedEventData[SceneEditNodeAdded::P_PARENT] = enode->parentEnd_;
+                nodeAddedEventData[SceneEditNodeAdded::P_SCENE] = scene_;
+                scene_->SendEvent(E_SCENEEDITNODEADDED, nodeAddedEventData);
+
+            }
+            else
+            {
+                VariantMap nodeRemovedEventData;
+                nodeRemovedEventData[SceneEditNodeRemoved::P_NODE] = node;
+                nodeRemovedEventData[SceneEditNodeRemoved::P_PARENT] = enode->parentBegin_;
+                nodeRemovedEventData[SceneEditNodeRemoved::P_SCENE] = scene_;
+                scene_->SendEvent(E_SCENEEDITNODEREMOVED, nodeRemovedEventData);
+                node->Remove();
+            }
+
+        }
+
+        for (unsigned j = 0; j < enode->components_.Size(); j++)
+        {
+            EditComponent* ecomponent = enode->components_[j];
+            Component* component = ecomponent->component_;
+
+            changed = !CompareStates(ecomponent->stateBegin_, ecomponent->stateEnd_);
+            if ( changed && !component->Serializable::Load(ecomponent->stateEnd_))
+            {
+                LOGERRORF("Unable to Redo component serializable");
+                return false;
+            }
+
+            ecomponent->stateEnd_.Seek(0);
+
+            if (changed)
+            {
+                VariantMap eventData;
+                eventData[SceneEditStateChange::P_SERIALIZABLE] = component;
+                component->SendEvent(E_SCENEEDITSTATECHANGE, eventData);
+            }
+
+            if (component->GetNode() != ecomponent->nodeEnd_)
+            {
+                component->Remove();
+
+                bool add = ecomponent->nodeEnd_.NotNull();
+
+                VariantMap caData;
+                caData[SceneEditComponentAddedRemoved::P_SCENE] = scene_;
+                caData[SceneEditComponentAddedRemoved::P_COMPONENT] = component;
+
+                if (add)
+                {
+                    ecomponent->nodeEnd_->AddComponent(component, 0, REPLICATED);
+                    caData[SceneEditComponentAddedRemoved::P_NODE] = ecomponent->nodeEnd_;
+                    caData[SceneEditComponentAddedRemoved::P_REMOVED] = false;
+                }
+                else
+                {
+                    caData[SceneEditComponentAddedRemoved::P_NODE] = ecomponent->nodeBegin_;
+                    caData[SceneEditComponentAddedRemoved::P_REMOVED] = true;
+                }
+
+                scene_->SendEvent(E_SCENEEDITCOMPONENTADDEDREMOVED, caData);
+
+            }
+
+        }
+
+    }
+
+    scene_->SendEvent(E_SCENEEDITSTATECHANGESEND);
+
+    return true;
+}
+
+
+}
+

+ 109 - 0
Source/AtomicEditor/Editors/SceneEditor3D/SceneEditOp.h

@@ -0,0 +1,109 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// LICENSE: Atomic Game Engine Editor and Tools EULA
+// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
+// license information: https://github.com/AtomicGameEngine/AtomicGameEngine
+//
+
+#pragma once
+
+#include <Atomic/Core/Object.h>
+#include <Atomic/IO/VectorBuffer.h>
+
+namespace Atomic
+{
+class Node;
+class Component;
+}
+
+using namespace Atomic;
+
+namespace AtomicEditor
+{
+
+enum SceneEditType
+{
+    SCENEEDIT_UNKNOWN = 0,
+    SCENEEDIT_SELECTION
+};
+
+class SceneEditOp
+{
+
+public:
+
+    SceneEditOp(Scene* scene, SceneEditType type) { type_ = type; scene_ = scene;}
+    virtual ~SceneEditOp() { }
+
+    virtual bool Undo() = 0;
+    virtual bool Redo() = 0;
+
+    /// Returns true if the states are identical
+    bool CompareStates(const VectorBuffer& stateOne, const VectorBuffer& stateTwo) const
+    {
+        if (stateOne.GetSize() != stateTwo.GetSize())
+            return false;
+
+        if (memcmp(stateOne.GetData(), stateTwo.GetData(), stateOne.GetSize()))
+            return false;
+
+        return true;
+    }
+
+    SharedPtr<Scene> scene_;
+    SceneEditType type_;
+
+};
+
+class SelectionEditOp : public SceneEditOp
+{
+
+public:
+
+    SelectionEditOp(Scene* scene);
+    ~SelectionEditOp();
+
+    bool Undo();
+    bool Redo();
+
+    void RegisterEdit();
+
+    void SetNodes(Vector<SharedPtr<Node>>& nodes);
+
+    void AddNode(Node* node);
+
+    void NodeAdded(Node* node, Node* parent);
+    void NodeRemoved(Node* node, Node* parent);
+
+    // Erases a node from the edit op, return true if no other nodes in the operation
+    bool EraseNode(Node *node);
+
+    bool Commit();
+
+private:
+
+    struct EditComponent
+    {
+        SharedPtr<Component> component_;
+        SharedPtr<Node> nodeBegin_;
+        SharedPtr<Node> nodeEnd_;
+        VectorBuffer stateBegin_;
+        VectorBuffer stateEnd_;
+    };
+
+    struct EditNode
+    {
+        SharedPtr<Node> node_;
+        SharedPtr<Node> parentBegin_;
+        SharedPtr<Node> parentEnd_;
+        VectorBuffer stateBegin_;
+        VectorBuffer stateEnd_;
+        PODVector<EditComponent*> components_;
+    };
+
+    PODVector<EditNode*> editNodes_;
+
+};
+
+}
+

+ 0 - 109
Source/AtomicEditor/Editors/SceneEditor3D/SceneEditOps.cpp

@@ -1,109 +0,0 @@
-
-#include <Atomic/IO/Log.h>
-#include <Atomic/Scene/Node.h>
-#include <Atomic/Scene/Component.h>
-
-#include "SceneEditOps.h"
-#include "SceneEditor3DEvents.h"
-
-namespace AtomicEditor
-{
-
-
-NodeEditOp::NodeEditOp(Node* node)
-{
-    node_ = node;
-
-    // parent at the time of the operation
-    parent_ = node->GetParent();
-}
-
-NodeAddedRemovedOp::NodeAddedRemovedOp(Node* node, bool added) : NodeEditOp(node),
-    added_(added)
-{
-    type_ = SCENEEDIT_NODE_ADDED_REMOVED;
-}
-
-bool NodeAddedRemovedOp::Undo()
-{
-    if (added_)
-    {
-        node_->Remove();
-    }
-    else
-    {
-        parent_->AddChild(node_);
-    }
-
-    return true;
-}
-
-bool NodeAddedRemovedOp::Redo()
-{
-    if (added_)
-    {
-        parent_->AddChild(node_);
-    }
-    else
-    {
-        node_->Remove();
-    }
-
-    return true;
-}
-
-SerializableEditOp::SerializableEditOp(Serializable* serializable, const VectorBuffer& beginState, const VectorBuffer& endState) : SceneEditOp(),
-    serializable_(serializable),
-    beginState_(beginState),
-    endState_(endState)
-{
-    type_ = SCENEEDIT_SERIALIZABLE_EDIT;
-
-    if (!beginState_.GetSize())
-    {
-        LOGERRORF("Zero size beginState");
-    }
-
-    if (!endState_.GetSize())
-    {
-        LOGERRORF("Zero size endState");
-    }
-}
-
-bool SerializableEditOp::Undo()
-{    
-    if (!serializable_->Serializable::Load(beginState_))
-    {
-        LOGERRORF("Error loading beginState");
-    }
-    beginState_.Seek(0);
-
-    VariantMap eventData;
-    eventData[SceneEditSerializableUndoRedo::P_SERIALIZABLE] = serializable_;
-    eventData[SceneEditSerializableUndoRedo::P_UNDO] = true;
-    eventData[SceneEditSerializableUndoRedo::P_STATE] = beginState_;
-    serializable_->SendEvent(E_SCENEEDITSERIALIZABLEUNDOREDO, eventData);
-
-    return true;
-}
-
-bool SerializableEditOp::Redo()
-{
-
-    if (!serializable_->Serializable::Load(endState_))
-    {
-        LOGERRORF("Error loading endState");
-    }
-
-    endState_.Seek(0);
-
-    VariantMap eventData;
-    eventData[SceneEditSerializableUndoRedo::P_SERIALIZABLE] = serializable_;
-    eventData[SceneEditSerializableUndoRedo::P_UNDO] = false;
-    eventData[SceneEditSerializableUndoRedo::P_STATE] = endState_;
-    serializable_->SendEvent(E_SCENEEDITSERIALIZABLEUNDOREDO, eventData);
-
-    return true;
-}
-
-}

+ 0 - 118
Source/AtomicEditor/Editors/SceneEditor3D/SceneEditOps.h

@@ -1,118 +0,0 @@
-//
-// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
-// LICENSE: Atomic Game Engine Editor and Tools EULA
-// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
-// license information: https://github.com/AtomicGameEngine/AtomicGameEngine
-//
-
-#pragma once
-
-#include <Atomic/Core/Object.h>
-#include <Atomic/IO/VectorBuffer.h>
-
-namespace Atomic
-{
-
-class Scene;
-class Node;
-class Component;
-class Serializable;
-
-}
-
-using namespace Atomic;
-
-namespace AtomicEditor
-{
-
-enum SceneEditType
-{
-    SCENEEDIT_UNKNOWN = 0,
-    SCENEEDIT_NODE_ADDED_REMOVED,
-    SCENEEDIT_COMPONENT_ADDED_REMOVED,
-    SCENEEDIT_SERIALIZABLE_EDIT,
-};
-
-class SceneEditOp
-{
-
-public:
-
-    SceneEditOp() { type_ = SCENEEDIT_UNKNOWN; }
-    virtual ~SceneEditOp() { }
-
-    virtual bool Undo() = 0;
-    virtual bool Redo() = 0;
-
-    SceneEditType type_;
-
-};
-
-class NodeEditOp : public SceneEditOp
-{
-protected:
-
-    NodeEditOp(Node* node);
-
-public:
-
-    SharedPtr<Node> node_;
-    SharedPtr<Node> parent_;
-
-};
-
-class NodeAddedRemovedOp : public NodeEditOp
-{
-
-public:
-
-    NodeAddedRemovedOp(Node* node, bool added);
-
-    bool added_;
-
-    bool Undo();
-    bool Redo();
-};
-
-class ComponentEditOp : public SceneEditOp
-{
-protected:
-
-    ComponentEditOp(Component* component);
-
-public:
-
-    SharedPtr<Component> component_;
-
-};
-
-class ComponentAddedRemovedOp : public ComponentEditOp
-{
-
-public:
-
-    ComponentAddedRemovedOp(Node* node, bool added);
-
-    bool added_;
-
-    bool Undo();
-    bool Redo();
-};
-
-class SerializableEditOp : public SceneEditOp
-{
-
-public:
-
-    SerializableEditOp(Serializable* serializable, const VectorBuffer& beginState, const VectorBuffer& endState);
-
-    SharedPtr<Serializable> serializable_;
-    VectorBuffer beginState_;
-    VectorBuffer endState_;
-
-    bool Undo();
-    bool Redo();
-};
-
-
-}

+ 142 - 95
Source/AtomicEditor/Editors/SceneEditor3D/SceneEditor3D.cpp

@@ -34,6 +34,7 @@
 #include "../../EditorMode/AEEditorEvents.h"
 
 #include "SceneEditor3D.h"
+#include "SceneSelection.h"
 #include "SceneEditHistory.h"
 #include "SceneEditor3DEvents.h"
 
@@ -63,7 +64,9 @@ SceneEditor3D ::SceneEditor3D(Context* context, const String &fullpath, UITabCon
 
     scene_->SetUpdateEnabled(false);
 
+    selection_ = new SceneSelection(context, this);
     sceneView_ = new SceneView3D(context_, this);
+    editHistory_ = new SceneEditHistory(context, this);
 
     // EARLY ACCESS
     if (fullpath.Find(String("ToonTown")) != String::NPOS)
@@ -95,7 +98,6 @@ SceneEditor3D ::SceneEditor3D(Context* context, const String &fullpath, UITabCon
     UpdateGizmoSnapSettings();
 
     SubscribeToEvent(E_UPDATE, HANDLER(SceneEditor3D, HandleUpdate));
-    SubscribeToEvent(E_EDITORACTIVENODECHANGE, HANDLER(SceneEditor3D, HandleEditorActiveNodeChange));
 
     SubscribeToEvent(E_GIZMOEDITMODECHANGED, HANDLER(SceneEditor3D, HandleGizmoEditModeChanged));
     SubscribeToEvent(E_GIZMOAXISMODECHANGED, HANDLER(SceneEditor3D, HandleGizmoAxisModeChanged));
@@ -107,16 +109,12 @@ SceneEditor3D ::SceneEditor3D(Context* context, const String &fullpath, UITabCon
 
     SubscribeToEvent(E_PROJECTUSERPREFSAVED, HANDLER(SceneEditor3D, HandleUserPrefSaved));
 
+    SubscribeToEvent(scene_, E_SCENEEDITNODECREATED, HANDLER(SceneEditor3D, HandleSceneEditNodeCreated));
+
     SubscribeToEvent(E_EDITORPLAYERSTARTED, HANDLER(SceneEditor3D, HandlePlayStarted));
     SubscribeToEvent(E_EDITORPLAYERSTOPPED, HANDLER(SceneEditor3D, HandlePlayStopped));
-
-    SubscribeToEvent(scene_, E_NODEADDED, HANDLER(SceneEditor3D, HandleNodeAdded));
-    SubscribeToEvent(scene_, E_NODEREMOVED, HANDLER(SceneEditor3D, HandleNodeRemoved));
-
     SubscribeToEvent(scene_, E_SCENEEDITSCENEMODIFIED, HANDLER(SceneEditor3D, HandleSceneEditSceneModified));
 
-    editHistory_ = new SceneEditHistory(context_, scene_);
-
 }
 
 SceneEditor3D::~SceneEditor3D()
@@ -130,17 +128,7 @@ bool SceneEditor3D::OnEvent(const TBWidgetEvent &ev)
     {
         if (ev.special_key == TB_KEY_DELETE || ev.special_key == TB_KEY_BACKSPACE)
         {
-            if (selectedNode_)
-            {
-                VariantMap editData;
-                editData[SceneEditNodeAddedRemoved::P_SCENE] = scene_;
-                editData[SceneEditNodeAddedRemoved::P_NODE] = selectedNode_;
-                editData[SceneEditNodeAddedRemoved::P_ADDED] = false;
-                scene_->SendEvent(E_SCENEEDITNODEADDEDREMOVED, editData);
-
-                selectedNode_->Remove();
-                selectedNode_ = 0;
-            }
+            selection_->Delete();
         }
 
     }
@@ -149,28 +137,13 @@ bool SceneEditor3D::OnEvent(const TBWidgetEvent &ev)
     {
         if (ev.ref_id == TBIDC("copy"))
         {
-            if (selectedNode_.NotNull())
-            {
-                clipboardNode_ = selectedNode_;
-            }
+            selection_->Copy();
+            return true;
         }
         else if (ev.ref_id == TBIDC("paste"))
         {
-            if (clipboardNode_.NotNull() && selectedNode_.NotNull())
-            {
-                SharedPtr<Node> pasteNode(clipboardNode_->Clone());
-
-                VariantMap eventData;
-                eventData[EditorActiveNodeChange::P_NODE] = pasteNode;
-                SendEvent(E_EDITORACTIVENODECHANGE, eventData);
-
-                VariantMap editData;
-                editData[SceneEditNodeAddedRemoved::P_SCENE] = scene_;
-                editData[SceneEditNodeAddedRemoved::P_NODE] = pasteNode;
-                editData[SceneEditNodeAddedRemoved::P_ADDED] = true;
-
-                scene_->SendEvent(E_SCENEEDITNODEADDEDREMOVED, editData);
-            }
+            selection_->Paste();
+            return true;
         }
         else if (ev.ref_id == TBIDC("close"))
         {
@@ -221,45 +194,9 @@ void SceneEditor3D::SetFocus()
     sceneView_->SetFocus();
 }
 
-void SceneEditor3D::SelectNode(Node* node)
-{
-    selectedNode_ = node;
-    if (!node)
-        gizmo3D_->Hide();
-    else
-        gizmo3D_->Show();
-
-
-}
-
-void SceneEditor3D::HandleNodeAdded(StringHash eventType, VariantMap& eventData)
-{
-    // Node does not have values set here
-
-    //Node* node =  static_cast<Node*>(eventData[NodeAdded::P_NODE].GetPtr());
-    //LOGINFOF("Node Added: %s", node->GetName().CString());
-}
-
-
-void SceneEditor3D::HandleNodeRemoved(StringHash eventType, VariantMap& eventData)
-{
-    Node* node = (Node*) (eventData[NodeRemoved::P_NODE].GetPtr());
-    if (node == selectedNode_)
-        SelectNode(0);
-}
-
 void SceneEditor3D::HandleUpdate(StringHash eventType, VariantMap& eventData)
 {
-    Vector<Node*> editNodes;
-    if (selectedNode_.NotNull())
-        editNodes.Push(selectedNode_);
-    gizmo3D_->Update(editNodes);
-}
-
-void SceneEditor3D::HandleEditorActiveNodeChange(StringHash eventType, VariantMap& eventData)
-{
-    Node* node = (Node*) (eventData[EditorActiveNodeChange::P_NODE].GetPtr());
-    SelectNode(node);
+    gizmo3D_->Update();
 }
 
 void SceneEditor3D::HandlePlayStarted(StringHash eventType, VariantMap& eventData)
@@ -321,6 +258,48 @@ bool SceneEditor3D::Save()
 
 }
 
+void SceneEditor3D::RegisterNode(Node * node)
+{
+    VariantMap eventData;
+    eventData[SceneEditAddRemoveNodes::P_END] = false;
+    scene_->SendEvent(E_SCENEEDITADDREMOVENODES, eventData);
+
+    // generate scene edit event
+
+    VariantMap nodeAddedEventData;
+    nodeAddedEventData[SceneEditNodeAdded::P_NODE] = node;
+    nodeAddedEventData[SceneEditNodeAdded::P_PARENT] = node->GetParent();
+    nodeAddedEventData[SceneEditNodeAdded::P_SCENE] = scene_;
+    scene_->SendEvent(E_SCENEEDITNODEADDED, nodeAddedEventData);
+
+    eventData[SceneEditAddRemoveNodes::P_END] = true;
+    scene_->SendEvent(E_SCENEEDITADDREMOVENODES, eventData);
+
+}
+
+void SceneEditor3D::RegisterNodes(const PODVector<Node*>& nodes)
+{
+    VariantMap eventData;
+    eventData[SceneEditAddRemoveNodes::P_END] = false;
+    scene_->SendEvent(E_SCENEEDITADDREMOVENODES, eventData);
+
+    // generate scene edit event
+
+    for (unsigned i = 0; i < nodes.Size(); i++)
+    {
+        Node* node = nodes[i];
+        VariantMap nodeAddedEventData;
+        nodeAddedEventData[SceneEditNodeAdded::P_NODE] = node;
+        nodeAddedEventData[SceneEditNodeAdded::P_PARENT] = node->GetParent();
+        nodeAddedEventData[SceneEditNodeAdded::P_SCENE] = scene_;
+        scene_->SendEvent(E_SCENEEDITNODEADDED, nodeAddedEventData);
+    }
+
+    eventData[SceneEditAddRemoveNodes::P_END] = true;
+    scene_->SendEvent(E_SCENEEDITADDREMOVENODES, eventData);
+
+}
+
 void SceneEditor3D::Undo()
 {
     editHistory_->Undo();
@@ -331,6 +310,29 @@ void SceneEditor3D::Redo()
     editHistory_->Redo();
 }
 
+void SceneEditor3D::Cut()
+{
+    selection_->Cut();
+}
+
+void SceneEditor3D::Copy()
+{
+    selection_->Copy();
+}
+
+void SceneEditor3D::Paste()
+{
+    selection_->Paste();
+}
+
+void SceneEditor3D::HandleSceneEditNodeCreated(StringHash eventType, VariantMap& eventData)
+{
+    PODVector<Node*> nodes;
+    nodes.Push(static_cast<Node*>(eventData[SceneEditNodeCreated::P_NODE].GetPtr()));
+    RegisterNodes(nodes);
+    selection_->AddNode(nodes[0], true);
+}
+
 void SceneEditor3D::HandleSceneEditSceneModified(StringHash eventType, VariantMap& eventData)
 {
     SetModified(true);    
@@ -341,41 +343,86 @@ void SceneEditor3D::HandleUserPrefSaved(StringHash eventType, VariantMap& eventD
     UpdateGizmoSnapSettings();
 }
 
-void SceneEditor3D::GetSelectionBoundingBox(BoundingBox& bbox)
+void SceneEditor3D::UpdateGizmoSnapSettings()
 {
-    bbox.Clear();
+    gizmo3D_->SetSnapTranslationX(userPrefs_->GetSnapTranslationX());
+    gizmo3D_->SetSnapTranslationY(userPrefs_->GetSnapTranslationY());
+    gizmo3D_->SetSnapTranslationZ(userPrefs_->GetSnapTranslationZ());
+    gizmo3D_->SetSnapRotation(userPrefs_->GetSnapRotation());
+    gizmo3D_->SetSnapScale(userPrefs_->GetSnapScale());
 
-    if (selectedNode_.Null())
+}
+
+void SceneEditor3D::InvokeShortcut(const String& shortcut)
+{
+    if (shortcut == "frameselected")
+    {
+        sceneView_->FrameSelection();
         return;
+    }
+
+    ResourceEditor::InvokeShortcut(shortcut);
+}
 
-    // TODO: Adjust once multiple selection is in
-    if (selectedNode_.Null())
+void SceneEditor3D::ReparentNode(Node* node, Node* newParent)
+{
+    // can't parent to self
+    if (node == newParent)
         return;
 
-    // Get all the drawables, which define the bounding box of the selection
-    PODVector<Drawable*> drawables;
-    selectedNode_->GetDerivedComponents<Drawable>(drawables, true);
+    // already parented
+    Node* oldParent = node->GetParent();
+    if (oldParent == newParent)
+        return;
 
-    if (!drawables.Size())
+    // must be in same scene
+    if (node->GetScene() != newParent->GetScene())
+    {
         return;
+    }
+
+    // check if dropping on child of ourselves
 
-    // Calculate the combined bounding box of all drawables
-    for (unsigned i = 0; i < drawables.Size(); i++  )
+    Node* parent = newParent;
+
+    while (parent)
     {
-        Drawable* drawable = drawables[i];
-        bbox.Merge(drawable->GetWorldBoundingBox());
+        if (parent == node)
+        {
+            return;
+        }
+
+        parent = parent->GetParent();
     }
 
+    selection_->AddNode(node, true);
 
-}
+    Matrix3x4 transform = node->GetWorldTransform();
+
+    newParent->AddChild(node);
+
+    node->SetWorldTransform(transform.Translation(), transform.Rotation(), transform.Scale());
+
+    scene_->SendEvent(E_SCENEEDITEND);
+
+    PODVector<Node*> nodes;
+    node->GetChildren(nodes, true);
+    nodes.Insert(0, node);
+
+    VariantMap evData;
+    for (unsigned i = 0; i < nodes.Size(); i++)
+    {
+        evData[SceneEditNodeReparent::P_NODE] = nodes[i];
+        evData[SceneEditNodeReparent::P_ADDED] = false;
+        scene_->SendEvent(E_SCENEEDITNODEREPARENT, evData);
+    }
+
+    evData[SceneEditNodeReparent::P_NODE] = node;
+    evData[SceneEditNodeReparent::P_ADDED] = true;
+    scene_->SendEvent(E_SCENEEDITNODEREPARENT, evData);
+
+    selection_->AddNode(node, true);
 
-void SceneEditor3D::UpdateGizmoSnapSettings()
-{
-    gizmo3D_->SetSnapTranslationX(userPrefs_->GetSnapTranslationX());
-    gizmo3D_->SetSnapTranslationY(userPrefs_->GetSnapTranslationY());
-    gizmo3D_->SetSnapTranslationZ(userPrefs_->GetSnapTranslationZ());
-    gizmo3D_->SetSnapRotation(userPrefs_->GetSnapRotation());
-    gizmo3D_->SetSnapScale(userPrefs_->GetSnapScale());
 
 }
 

+ 18 - 9
Source/AtomicEditor/Editors/SceneEditor3D/SceneEditor3D.h

@@ -37,6 +37,7 @@ using namespace ToolCore;
 namespace AtomicEditor
 {
 
+class SceneSelection;
 class SceneEditHistory;
 
 class SceneEditor3D: public ResourceEditor
@@ -51,8 +52,14 @@ public:
 
     bool OnEvent(const TBWidgetEvent &ev);
 
-    void SelectNode(Node* node);
-    void GetSelectionBoundingBox(BoundingBox& bbox);
+    SceneSelection* GetSelection() { return selection_; }
+    SceneEditHistory* GetEditHistory() { return editHistory_; }
+    SceneView3D* GetSceneView3D() { return sceneView_; }
+
+    void RegisterNode(Node* node);
+    void RegisterNodes(const PODVector<Node*>& nodes);
+
+    void ReparentNode(Node* node, Node* newParent);
 
     Scene* GetScene() { return scene_; }
     Gizmo3D* GetGizmo() { return gizmo3D_; }
@@ -66,13 +73,17 @@ public:
 
     void Undo();
     void Redo();
+    void Cut();
+    void Copy();
+    void Paste();
 
     ProjectUserPrefs* GetUserPrefs();
 
+    void InvokeShortcut(const String& shortcut);
+
 private:
 
     void HandleUpdate(StringHash eventType, VariantMap& eventData);
-    void HandleEditorActiveNodeChange(StringHash eventType, VariantMap& eventData);
     void HandlePlayStarted(StringHash eventType, VariantMap& eventData);
     void HandlePlayStopped(StringHash eventType, VariantMap& eventData);
     void HandleGizmoEditModeChanged(StringHash eventType, VariantMap& eventData);
@@ -80,10 +91,8 @@ private:
 
     void HandleUserPrefSaved(StringHash eventType, VariantMap& eventData);
 
-    void HandleNodeAdded(StringHash eventType, VariantMap& eventData);
-    void HandleNodeRemoved(StringHash eventType, VariantMap& eventData);
-
     void HandleSceneEditSceneModified(StringHash eventType, VariantMap& eventData);
+    void HandleSceneEditNodeCreated(StringHash eventType, VariantMap& eventData);
 
     void UpdateGizmoSnapSettings();
 
@@ -94,11 +103,11 @@ private:
 
     SharedPtr<Gizmo3D> gizmo3D_;
 
-    WeakPtr<Node> selectedNode_;
-    SharedPtr<Node> clipboardNode_;
-
+    SharedPtr<SceneSelection> selection_;
     SharedPtr<SceneEditHistory> editHistory_;
 
+    SharedPtr<Node> clipboardNode_;
+
     WeakPtr<ProjectUserPrefs> userPrefs_;
 
 };

+ 82 - 14
Source/AtomicEditor/Editors/SceneEditor3D/SceneEditor3DEvents.h

@@ -30,37 +30,105 @@ EVENT(E_GIZMOMOVED, GizmoMoved)
 
 }
 
-EVENT(E_SCENEEDITSCENEMODIFIED, SceneEditSceneModified)
+EVENT(E_SCENENODESELECTED, SceneNodeSelected)
 {
-
+    PARAM(P_SCENE, Scene);             // Scene
+    PARAM(P_NODE, Node);               // Node
+    PARAM(P_SELECTED, Selected);       // bool
+    PARAM(P_QUIET, Quiet);             // bool (don't record edit event)
 }
 
-EVENT(E_SCENEEDITNODEADDEDREMOVED, SceneEditNodeAddedRemoved)
+EVENT(E_SCENEEDITBEGIN, SceneEditBegin)
 {
     PARAM(P_SCENE, Scene);             // Scene
-    PARAM(P_NODE, Node);               // Node
-    PARAM(P_ADDED, Added);             // bool
+
 }
 
-EVENT(E_SCENEEDITCOMPONENTADDEDREMOVED, SceneEditComponentAddedRemoved)
+EVENT(E_SCENEEDITNODEREPARENT, SceneEditNodeReparent)
+{
+    PARAM(P_NODE, Node);             // Node
+    PARAM(P_ADDED, Added);             // Boolean
+}
+
+
+EVENT(E_SCENEEDITEND, SceneEditEnd)
 {
     PARAM(P_SCENE, Scene);             // Scene
-    PARAM(P_COMPONENT, Component);     // Component
-    PARAM(P_ADDED, Added);             // bool
 }
 
-EVENT(E_SCENEEDITSERIALIZABLE, SceneEditSerializable)
+EVENT(E_SCENEEDITSTATECHANGESBEGIN, SceneEditStateChangesBegin)
 {
-    PARAM(P_SERIALIZABLE, Serializable);     // Serializable
-    PARAM(P_OPERATION, Operation);           // int (0: begin, 1: change, 2: end)
+
 }
 
-EVENT(E_SCENEEDITSERIALIZABLEUNDOREDO, SceneEditSerializableUndoRedo)
+EVENT(E_SCENEEDITSTATECHANGE, SceneEditStateChange)
 {
     PARAM(P_SERIALIZABLE, Serializable);     // Serializable
-    PARAM(P_STATE, State);                   // State (VectorBuffer);
-    PARAM(P_UNDO, Undo);                     // bool (true: undo, false: redo)
 }
 
+EVENT(E_SCENEEDITSTATECHANGESEND, SceneEditStateChangesEnd)
+{
+
+}
+
+/// A child node has been added to a parent node.
+EVENT(E_SCENEEDITNODECREATED, SceneEditNodeCreated)
+{
+    PARAM(P_NODE, Node);                    // Node pointer
+}
+
+
+/// A child node has been added to a parent node.
+EVENT(E_SCENEEDITNODEADDED, SceneEditNodeAdded)
+{
+    PARAM(P_SCENE, Scene);                  // Scene pointer
+    PARAM(P_PARENT, Parent);                // Node pointer
+    PARAM(P_NODE, Node);                    // Node pointer
+}
+
+/// A child node is about to be removed from a parent node.
+EVENT(E_SCENEEDITNODEREMOVED, SceneEditNodeRemoved)
+{
+    PARAM(P_SCENE, Scene);                  // Scene pointer
+    PARAM(P_PARENT, Parent);                // Node pointer
+    PARAM(P_NODE, Node);                    // Node pointer
+}
+
+
+/// A child node has been added to a parent node.
+EVENT(E_SCENEEDITCOMPONENTADDEDREMOVED, SceneEditComponentAddedRemoved)
+{
+    PARAM(P_SCENE, Scene);                  // Scene pointer
+    PARAM(P_NODE, Node);                    // Node pointer
+    PARAM(P_COMPONENT, Component);          // Component pointer
+    PARAM(P_REMOVED, Removed);          // bool
+
+}
+
+EVENT(E_SCENEEDITPREFABSAVE, SceneEditPrefabSave)
+{
+    PARAM(P_NODE, Node);                    // Node pointer
+}
+
+EVENT(E_SCENEEDITPREFABREVERT, SceneEditPrefabRevert)
+{
+    PARAM(P_NODE, Node);                    // Node pointer
+}
+
+
+EVENT(E_SCENEEDITPREFABBREAK, SceneEditPrefabBreak)
+{
+    PARAM(P_NODE, Node);                    // Node pointer
+}
+
+EVENT(E_SCENEEDITADDREMOVENODES, SceneEditAddRemoveNodes)
+{
+    PARAM(P_END, End);       // bool
+}
+
+EVENT(E_SCENEEDITSCENEMODIFIED, SceneEditSceneModified)
+{
+
+}
 
 }

+ 383 - 0
Source/AtomicEditor/Editors/SceneEditor3D/SceneSelection.cpp

@@ -0,0 +1,383 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// LICENSE: Atomic Game Engine Editor and Tools EULA
+// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
+// license information: https://github.com/AtomicGameEngine/AtomicGameEngine
+//
+
+
+#include <Atomic/IO/Log.h>
+#include <Atomic/Core/CoreEvents.h>
+
+#include <Atomic/Graphics/Graphics.h>
+#include <Atomic/Graphics/Drawable.h>
+#include <Atomic/Graphics/DebugRenderer.h>
+#include <Atomic/Atomic3D/Terrain.h>
+
+#include <Atomic/Scene/SceneEvents.h>
+#include <Atomic/Scene/Node.h>
+#include <Atomic/Scene/Scene.h>
+#include <Atomic/Scene/PrefabComponent.h>
+
+#include "SceneEditor3D.h"
+#include "SceneEditor3DEvents.h"
+#include "SceneSelection.h"
+#include "SceneEditHistory.h"
+
+namespace AtomicEditor
+{
+
+SceneSelection::SceneSelection(Context* context, SceneEditor3D *sceneEditor) : Object(context)
+{
+    sceneEditor3D_ = sceneEditor;
+    scene_ = sceneEditor3D_->GetScene();
+
+    SubscribeToEvent(E_POSTRENDERUPDATE, HANDLER(SceneSelection, HandlePostRenderUpdate));
+    SubscribeToEvent(scene_, E_NODEREMOVED, HANDLER(SceneSelection, HandleNodeRemoved));
+
+    SubscribeToEvent(scene_, E_SCENEEDITPREFABSAVE, HANDLER(SceneSelection, HandleSceneEditPrefabSave));
+    SubscribeToEvent(scene_, E_SCENEEDITPREFABREVERT, HANDLER(SceneSelection, HandleSceneEditPrefabRevert));
+    SubscribeToEvent(scene_, E_SCENEEDITPREFABBREAK, HANDLER(SceneSelection, HandleSceneEditPrefabBreak));
+}
+
+SceneSelection::~SceneSelection()
+{
+
+}
+
+Node* SceneSelection::GetSelectedNode(unsigned index) const
+{
+    if (index > nodes_.Size())
+        return 0;
+
+    return nodes_[index];
+}
+
+bool SceneSelection::Contains(Node* node)
+{
+    SharedPtr<Node> _node(node);
+    return nodes_.Contains(_node);
+}
+
+void SceneSelection::AddNode(Node* node, bool clear)
+{
+    if (clear)
+        Clear();
+
+    SharedPtr<Node> _node(node);
+    if (!nodes_.Contains(_node))
+    {
+        nodes_.Push(_node);
+
+        VariantMap eventData;
+        eventData[SceneNodeSelected::P_SCENE] = scene_;
+        eventData[SceneNodeSelected::P_NODE] = node;
+        eventData[SceneNodeSelected::P_SELECTED] = true;
+        scene_->SendEvent(E_SCENENODESELECTED, eventData);
+    }
+}
+
+void SceneSelection::RemoveNode(Node* node, bool quiet)
+{    
+    SharedPtr<Node> _node(node);
+    if(!nodes_.Contains(_node))
+        return;
+
+    nodes_.Remove(_node);
+
+    VariantMap eventData;
+    eventData[SceneNodeSelected::P_SCENE] = scene_;
+    eventData[SceneNodeSelected::P_NODE] = node;
+    eventData[SceneNodeSelected::P_SELECTED] = false;    
+    eventData[SceneNodeSelected::P_QUIET] = quiet;
+    scene_->SendEvent(E_SCENENODESELECTED, eventData);
+
+}
+
+void SceneSelection::Clear()
+{
+    Vector<SharedPtr<Node>> nodes = nodes_;
+
+    for (unsigned i = 0; i < nodes.Size(); i++)
+    {
+        RemoveNode(nodes[i]);
+    }
+
+}
+
+void SceneSelection::Paste()
+{
+
+    if (!clipBoardNodes_.Size())
+        return;
+
+    Vector<SharedPtr<Node>> newClipBoardNodes;
+
+    Node* parent = scene_;
+
+    if (nodes_.Size() >= 1)
+        parent = nodes_[0]->GetParent();
+
+    if (!parent)
+        parent = scene_;
+
+    Clear();
+
+    VariantMap eventData;
+    eventData[SceneEditAddRemoveNodes::P_END] = false;
+    scene_->SendEvent(E_SCENEEDITADDREMOVENODES, eventData);
+
+    for (unsigned i = 0; i < clipBoardNodes_.Size(); i++)
+    {
+        // Nodes must have a parent to clone, so first parent
+        Node* clipNode = clipBoardNodes_[i];
+
+        Matrix3x4 transform = clipNode->GetWorldTransform();
+
+        parent->AddChild(clipNode);
+        clipNode->SetWorldTransform(transform.Translation(), transform.Rotation(), transform.Scale());
+
+        // clone
+        newClipBoardNodes.Push(SharedPtr<Node>(clipNode->Clone()));
+        // remove from parent
+        newClipBoardNodes.Back()->Remove();
+        newClipBoardNodes.Back()->SetWorldTransform(transform.Translation(), transform.Rotation(), transform.Scale());
+
+        // generate scene edit event
+        VariantMap nodeAddedEventData;
+        nodeAddedEventData[SceneEditNodeAdded::P_NODE] = clipNode;
+        nodeAddedEventData[SceneEditNodeAdded::P_PARENT] = parent;
+        nodeAddedEventData[SceneEditNodeAdded::P_SCENE] = scene_;
+        scene_->SendEvent(E_SCENEEDITNODEADDED, nodeAddedEventData);
+    }
+
+    eventData[SceneEditAddRemoveNodes::P_END] = true;
+    scene_->SendEvent(E_SCENEEDITADDREMOVENODES, eventData);
+
+    for (unsigned i = 0; i < clipBoardNodes_.Size(); i++)
+    {
+        AddNode(clipBoardNodes_[i], false);
+    }
+
+    clipBoardNodes_ = newClipBoardNodes;
+
+}
+
+void SceneSelection::Cut()
+{
+    Copy();
+    Delete();
+}
+
+void SceneSelection::Copy()
+{
+    clipBoardNodes_.Clear();
+
+    for (unsigned i = 0; i < nodes_.Size(); i++)
+    {
+        Node* node = nodes_[i];
+
+        if (!node->GetParent())
+        {
+            clipBoardNodes_.Clear();
+            LOGERROR("SceneSelection::Copy - unable to copy node to clipboard (no parent)");
+            return;
+        }
+
+        for (unsigned j = 0; j < nodes_.Size(); j++)
+        {
+            if ( i == j )
+                continue;
+
+            PODVector<Node*> children;
+            nodes_[j]->GetChildren(children, true);
+            if (children.Contains(node))
+            {
+                node = 0;
+                break;
+            }
+
+        }
+
+        if (node)
+        {
+            Matrix3x4 transform = node->GetWorldTransform();
+            SharedPtr<Node> clipNode(node->Clone());            
+            clipNode->Remove();
+            clipNode->SetWorldTransform(transform.Translation(), transform.Rotation(), transform.Scale());
+            clipBoardNodes_.Push(clipNode);
+        }
+
+    }
+}
+
+void SceneSelection::Delete()
+{
+
+    Vector<SharedPtr<Node>> nodes = nodes_;
+
+    Clear();
+
+    VariantMap eventData;
+    eventData[SceneEditAddRemoveNodes::P_END] = false;
+    scene_->SendEvent(E_SCENEEDITADDREMOVENODES, eventData);
+
+    for (unsigned i = 0; i < nodes.Size(); i++)
+    {
+        // generate scene edit event
+        VariantMap nodeRemovedEventData;
+        nodeRemovedEventData[SceneEditNodeAdded::P_NODE] = nodes[i];
+        nodeRemovedEventData[SceneEditNodeAdded::P_PARENT] = nodes[i]->GetParent();
+        nodeRemovedEventData[SceneEditNodeAdded::P_SCENE] = scene_;
+        scene_->SendEvent(E_SCENEEDITNODEREMOVED, nodeRemovedEventData);
+
+        nodes[i]->Remove();
+
+    }
+
+    eventData[SceneEditAddRemoveNodes::P_END] = true;
+    scene_->SendEvent(E_SCENEEDITADDREMOVENODES, eventData);
+
+}
+
+void SceneSelection::GetBounds(BoundingBox& bbox)
+{
+
+    bbox.Clear();
+
+    if (!nodes_.Size())
+        return;
+
+    // Get all the drawables, which define the bounding box of the selection
+    PODVector<Drawable*> drawables;
+
+    for (unsigned i = 0; i < nodes_.Size(); i++)
+    {
+        Node* node = nodes_[i];
+        node->GetDerivedComponents<Drawable>(drawables, true, false);
+    }
+
+    if (!drawables.Size())
+        return;
+
+    // Calculate the combined bounding box of all drawables
+    for (unsigned i = 0; i < drawables.Size(); i++  )
+    {
+        Drawable* drawable = drawables[i];
+        bbox.Merge(drawable->GetWorldBoundingBox());
+    }
+
+}
+
+void SceneSelection::DrawNodeDebug(Node* node, DebugRenderer* debug, bool drawNode)
+{
+    if (drawNode)
+        debug->AddNode(node, 1.0, false);
+
+    // Exception for the scene to avoid bringing the editor to its knees: drawing either the whole hierarchy or the subsystem-
+    // components can have a large performance hit. Also do not draw terrain child nodes due to their large amount
+    // (TerrainPatch component itself draws nothing as debug geometry)
+    if (node != scene_ && !node->GetComponent<Terrain>())
+    {
+        const Vector<SharedPtr<Component> >& components = node->GetComponents();
+
+        for (unsigned j = 0; j < components.Size(); ++j)
+            components[j]->DrawDebugGeometry(debug, false);
+
+        // To avoid cluttering the view, do not draw the node axes for child nodes
+        for (unsigned k = 0; k < node->GetNumChildren(); ++k)
+            DrawNodeDebug(node->GetChild(k), debug, false);
+    }
+}
+
+void SceneSelection::HandleNodeRemoved(StringHash eventType, VariantMap& eventData)
+{
+    Node* node = (Node*) (eventData[NodeRemoved::P_NODE].GetPtr());
+
+    RemoveNode(node, true);
+}
+
+
+void SceneSelection::HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
+{
+
+    if (!nodes_.Size())
+        return;
+
+    // Visualize the currently selected nodes
+    DebugRenderer* debugRenderer = sceneEditor3D_->GetSceneView3D()->GetDebugRenderer();
+
+    for (unsigned i = 0; i < nodes_.Size(); i++)
+    {
+        DrawNodeDebug(nodes_[i], debugRenderer);
+    }
+
+}
+
+void SceneSelection::HandleSceneEditPrefabSave(StringHash eventType, VariantMap& eventData)
+{
+
+    Node* node = static_cast<Node*> ( eventData[SceneEditPrefabSave::P_NODE].GetPtr());
+
+    PrefabComponent* prefab = node->GetComponent<PrefabComponent>();
+    if (!prefab)
+    {
+        LOGERRORF("Prefab Save: Unable to get prefab component for node: %s", node->GetName().CString());
+        return;
+    }
+
+    prefab->SavePrefab();
+
+    AddNode(node, true);
+}
+
+void SceneSelection::HandleSceneEditPrefabRevert(StringHash eventType, VariantMap& eventData)
+{
+    Node* node = static_cast<Node*> ( eventData[SceneEditPrefabRevert::P_NODE].GetPtr());
+
+    PrefabComponent* prefab = node->GetComponent<PrefabComponent>();
+    if (!prefab)
+    {
+        LOGERRORF("Prefab Revert: Unable to get prefab component for node: %s", node->GetName().CString());
+        return;
+    }
+
+    prefab->UndoPrefab();
+
+    AddNode(node, true);
+}
+
+void SceneSelection::HandleSceneEditPrefabBreak(StringHash eventType, VariantMap& eventData)
+{
+    Node* node = static_cast<Node*> ( eventData[SceneEditPrefabBreak::P_NODE].GetPtr());
+
+    PrefabComponent* prefab = node->GetComponent<PrefabComponent>();
+    if (!prefab)
+    {
+        LOGERRORF("Prefab Break: Unable to get prefab component for node: %s", node->GetName().CString());
+        return;
+    }
+
+    Clear();
+
+    prefab->BreakPrefab();
+
+    PODVector<Node*> nodes;
+    node->GetChildren(nodes, true);
+    nodes.Insert(0, node);
+
+    SceneEditHistory* editHistory = sceneEditor3D_->GetEditHistory();
+
+    for (unsigned i = 0; i < nodes.Size(); i++)
+    {
+        editHistory->RemoveNode(nodes[i]);
+    }
+
+    AddNode(node, true);
+
+    scene_->SendEvent(E_SCENEEDITSCENEMODIFIED);
+
+}
+
+
+}

+ 72 - 0
Source/AtomicEditor/Editors/SceneEditor3D/SceneSelection.h

@@ -0,0 +1,72 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// LICENSE: Atomic Game Engine Editor and Tools EULA
+// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
+// license information: https://github.com/AtomicGameEngine/AtomicGameEngine
+//
+
+#pragma once
+
+#include <Atomic/Core/Object.h>
+
+using namespace Atomic;
+
+namespace Atomic
+{
+class Node;
+class Scene;
+}
+
+namespace AtomicEditor
+{
+
+class SceneEditor3D;
+
+class SceneSelection: public Object
+{
+    OBJECT(SceneSelection);
+
+public:
+
+    SceneSelection(Context* context, SceneEditor3D* sceneEditor);
+    virtual ~SceneSelection();
+
+    Vector<SharedPtr<Node>>& GetNodes() { return nodes_; }
+
+    void Cut();
+    void Copy();
+    void Paste();
+    void Delete();
+
+    /// Add a node to the selection, if clear specified removes current nodes first
+    void AddNode(Node* node, bool clear = false);
+    void RemoveNode(Node* node, bool quiet = false);
+    void GetBounds(BoundingBox& bbox);
+
+    bool Contains(Node* node);
+
+    Node* GetSelectedNode(unsigned index) const;
+    unsigned GetSelectedNodeCount() const { return nodes_.Size(); }
+
+    void Clear();
+
+private:
+
+    void DrawNodeDebug(Node* node, DebugRenderer* debug, bool drawNode = true);
+
+    void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData);
+    void HandleNodeRemoved(StringHash eventType, VariantMap& eventData);
+
+    void HandleSceneEditPrefabSave(StringHash eventType, VariantMap& eventData);
+    void HandleSceneEditPrefabRevert(StringHash eventType, VariantMap& eventData);
+    void HandleSceneEditPrefabBreak(StringHash eventType, VariantMap& eventData);
+
+    WeakPtr<SceneEditor3D> sceneEditor3D_;
+    WeakPtr<Scene> scene_;
+
+    Vector<SharedPtr<Node>> clipBoardNodes_;
+    Vector<SharedPtr<Node>> nodes_;
+
+};
+
+}

+ 24 - 73
Source/AtomicEditor/Editors/SceneEditor3D/SceneView3D.cpp

@@ -20,7 +20,6 @@
 #include <Atomic/Graphics/Octree.h>
 #include <Atomic/Graphics/Material.h>
 
-#include <Atomic/Atomic3D/Terrain.h>
 #include <Atomic/Atomic3D/Model.h>
 #include <Atomic/Atomic3D/StaticModel.h>
 #include <Atomic/Atomic3D/AnimatedModel.h>
@@ -45,6 +44,7 @@
 #include "SceneView3D.h"
 #include "SceneEditor3D.h"
 #include "SceneEditor3DEvents.h"
+#include "SceneSelection.h"
 
 using namespace ToolCore;
 
@@ -72,6 +72,7 @@ SceneView3D ::SceneView3D(Context* context, SceneEditor3D *sceneEditor) :
     if (debugRenderer_.Null())
     {
         debugRenderer_ = scene_->CreateComponent<DebugRenderer>();
+        debugRenderer_->SetTemporary(true);
     }
 
     octree_ = scene_->GetComponent<Octree>();
@@ -97,11 +98,8 @@ SceneView3D ::SceneView3D(Context* context, SceneEditor3D *sceneEditor) :
     SetAutoUpdate(false);
 
     SubscribeToEvent(E_UPDATE, HANDLER(SceneView3D, HandleUpdate));
-    SubscribeToEvent(E_EDITORACTIVENODECHANGE, HANDLER(SceneView3D, HandleEditorActiveNodeChange));
     SubscribeToEvent(E_POSTRENDERUPDATE, HANDLER(SceneView3D, HandlePostRenderUpdate));
 
-    SubscribeToEvent(scene_, E_NODEREMOVED, HANDLER(SceneView3D, HandleNodeRemoved));
-
     SubscribeToEvent(E_MOUSEMOVE, HANDLER(SceneView3D,HandleMouseMove));
 
     SubscribeToEvent(this, E_DRAGENTERWIDGET, HANDLER(SceneView3D, HandleDragEnterWidget));
@@ -144,7 +142,7 @@ void SceneView3D::Disable()
 bool SceneView3D::GetOrbitting()
 {
     Input* input = GetSubsystem<Input>();
-    return framedNode_.NotNull() && MouseInView() && input->GetKeyDown(KEY_ALT) && input->GetMouseButtonDown(MOUSEB_LEFT);
+    return framedBBox_.defined_ && MouseInView() && input->GetKeyDown(KEY_ALT) && input->GetMouseButtonDown(MOUSEB_LEFT);
 }
 
 bool SceneView3D::GetZooming()
@@ -195,7 +193,7 @@ void SceneView3D::MoveCamera(float timeStep)
     if (orbitting)
     {
         BoundingBox bbox;
-        sceneEditor_->GetSelectionBoundingBox(bbox);
+        sceneEditor_->GetSelection()->GetBounds(bbox);
         if (bbox.defined_)
         {
             Vector3 centerPoint = bbox.Center();
@@ -254,13 +252,6 @@ void SceneView3D::MoveCamera(float timeStep)
             cameraNode_->Translate(Vector3::DOWN * MOVE_SPEED * timeStep);
         }
     }
-    else if (!superdown)
-    {
-        if (input->GetKeyPress(KEY_F))
-        {
-            FrameSelection();
-        }
-    }
 
     if (cameraMove_)
     {
@@ -299,27 +290,6 @@ Ray SceneView3D::GetCameraRay()
                                   float(cpos.y_ - y) / rect.Height());
 }
 
-void SceneView3D::DrawNodeDebug(Node* node, DebugRenderer* debug, bool drawNode)
-{
-    if (drawNode)
-        debug->AddNode(node, 1.0, false);
-
-    // Exception for the scene to avoid bringing the editor to its knees: drawing either the whole hierarchy or the subsystem-
-    // components can have a large performance hit. Also do not draw terrain child nodes due to their large amount
-    // (TerrainPatch component itself draws nothing as debug geometry)
-    if (node != scene_ && !node->GetComponent<Terrain>())
-    {
-        const Vector<SharedPtr<Component> >& components = node->GetComponents();
-
-        for (unsigned j = 0; j < components.Size(); ++j)
-            components[j]->DrawDebugGeometry(debug, false);
-
-        // To avoid cluttering the view, do not draw the node axes for child nodes
-        for (unsigned k = 0; k < node->GetNumChildren(); ++k)
-            DrawNodeDebug(node->GetChild(k), debug, false);
-    }
-}
-
 bool SceneView3D::MouseInView()
 {
     if (!GetInternalWidget())
@@ -351,6 +321,12 @@ void SceneView3D::HandleUIUnhandledShortcut(StringHash eventType, VariantMap& ev
         sceneEditor_->Undo();
     else if (id == TBIDC("redo"))
         sceneEditor_->Redo();
+    else if (id == TBIDC("copy"))
+        sceneEditor_->Copy();
+    else if (id == TBIDC("cut"))
+        sceneEditor_->Cut();
+    else if (id == TBIDC("paste"))
+        sceneEditor_->Paste();
 
     return;
 
@@ -367,19 +343,13 @@ void SceneView3D::HandleUIWidgetFocusEscaped(StringHash eventType, VariantMap& e
 void SceneView3D::HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
 {
 
-    // Visualize the currently selected nodes
-    if (selectedNode_.NotNull())
-    {
-        DrawNodeDebug(selectedNode_, debugRenderer_);
-
-    }
-
     if (!MouseInView() || GetOrbitting())
         return;
 
     Input* input = GetSubsystem<Input>();
 
     mouseLeftDown_ = false;
+    bool shiftDown = input->GetKeyDown(KEY_LSHIFT) || input->GetKeyDown(KEY_RSHIFT);
 
     if (input->GetMouseButtonPress(MOUSEB_LEFT))
     {
@@ -409,8 +379,14 @@ void SceneView3D::HandlePostRenderUpdate(StringHash eventType, VariantMap& event
                     if (node->IsTemporary())
                         node = node->GetParent();
 
-                    neventData[EditorActiveNodeChange::P_NODE] = node;
-                    SendEvent(E_EDITORACTIVENODECHANGE, neventData);
+                    if (sceneEditor_->GetSelection()->Contains(node) && shiftDown)
+                    {
+                        sceneEditor_->GetSelection()->RemoveNode(node);
+                    }
+                    else
+                    {
+                        sceneEditor_->GetSelection()->AddNode(node, !shiftDown);
+                    }
 
                 }
             }
@@ -460,11 +436,6 @@ void SceneView3D::HandlePostRenderUpdate(StringHash eventType, VariantMap& event
 
 }
 
-void SceneView3D::SelectNode(Node* node)
-{
-    selectedNode_ = node;
-}
-
 bool SceneView3D::OnEvent(const TBWidgetEvent &ev)
 {
     if (ev.type == EVENT_TYPE_SHORTCUT)
@@ -516,18 +487,6 @@ void SceneView3D::HandleUpdate(StringHash eventType, VariantMap& eventData)
 
 }
 
-void SceneView3D::HandleEditorActiveNodeChange(StringHash eventType, VariantMap& eventData)
-{
-    Node* node = (Node*) (eventData[EditorActiveNodeChange::P_NODE].GetPtr());
-    SelectNode(node);
-}
-
-void SceneView3D::HandleNodeRemoved(StringHash eventType, VariantMap& eventData)
-{
-    Node* node = (Node*) (eventData[NodeRemoved::P_NODE].GetPtr());
-    if (node == selectedNode_)
-        SelectNode(0);
-}
 
 void SceneView3D::UpdateDragNode(int mouseX, int mouseY)
 {
@@ -621,16 +580,9 @@ void SceneView3D::HandleDragEnded(StringHash eventType, VariantMap& eventData)
 
     if (dragNode_.NotNull())
     {
-        VariantMap neventData;
-        neventData[EditorActiveNodeChange::P_NODE] = dragNode_;
-        SendEvent(E_EDITORACTIVENODECHANGE, neventData);
-
-        VariantMap editData;
-        editData[SceneEditNodeAddedRemoved::P_SCENE] = scene_;
-        editData[SceneEditNodeAddedRemoved::P_NODE] = dragNode_;
-        editData[SceneEditNodeAddedRemoved::P_ADDED] = true;
-        scene_->SendEvent(E_SCENEEDITNODEADDEDREMOVED, editData);
-
+        VariantMap nodeCreatedEvent;
+        nodeCreatedEvent[SceneEditNodeCreated::P_NODE] = dragNode_;
+        scene_->SendEvent(E_SCENEEDITNODECREATED, nodeCreatedEvent);
     }
 
     if (dragObject && dragObject->GetObject()->GetType() == ToolCore::Asset::GetTypeStatic())
@@ -676,8 +628,7 @@ void SceneView3D::HandleDragEnded(StringHash eventType, VariantMap& eventData)
 void SceneView3D::FrameSelection()
 {
     BoundingBox bbox;
-
-    sceneEditor_->GetSelectionBoundingBox(bbox);
+    sceneEditor_->GetSelection()->GetBounds(bbox);
 
     if (!bbox.defined_)
         return;
@@ -687,7 +638,7 @@ void SceneView3D::FrameSelection()
     if (sphere.radius_ < .01f || sphere.radius_ > 512)
         return;
 
-    framedNode_ = selectedNode_;
+    framedBBox_ = bbox;
     cameraMoveStart_ = cameraNode_->GetWorldPosition();
     cameraMoveTarget_ = bbox.Center() - (cameraNode_->GetWorldDirection() * sphere.radius_ * 3);
     cameraMoveTime_ = 0.0f;

+ 6 - 8
Source/AtomicEditor/Editors/SceneEditor3D/SceneView3D.h

@@ -37,7 +37,6 @@ public:
 
     SceneView3D(Context* context, SceneEditor3D* sceneEditor);
     virtual ~SceneView3D();
-    void SelectNode(Node* node);
 
     Ray GetCameraRay();
 
@@ -52,6 +51,9 @@ public:
     void Disable();
     bool IsEnabled() { return enabled_; }
 
+    DebugRenderer* GetDebugRenderer() { return debugRenderer_; }
+    SceneEditor3D* GetSceneEditor3D() { return sceneEditor_; }
+
 private:
 
     bool MouseInView();
@@ -68,13 +70,9 @@ private:
 
     void HandleUpdate(StringHash eventType, VariantMap& eventData);
     void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData);
-    void HandleEditorActiveNodeChange(StringHash eventType, VariantMap& eventData);
-    void HandleNodeRemoved(StringHash eventType, VariantMap& eventData);
 
     void HandleUIWidgetFocusEscaped(StringHash eventType, VariantMap& eventData);
-    void HandleUIUnhandledShortcut(StringHash eventType, VariantMap& eventData);
-
-    void DrawNodeDebug(Node* node, DebugRenderer* debug, bool drawNode = true);
+    void HandleUIUnhandledShortcut(StringHash eventType, VariantMap& eventData);    
 
     void MoveCamera(float timeStep);
 
@@ -96,8 +94,8 @@ private:
     SharedPtr<Camera> camera_;
     SharedPtr<DebugRenderer> debugRenderer_;
     SharedPtr<Octree> octree_;
-    SharedPtr<Node> selectedNode_;
-    WeakPtr<Node> framedNode_;
+
+    BoundingBox framedBBox_;
 
     SharedPtr<Scene> preloadResourceScene_;
     String dragAssetGUID_;

+ 168 - 40
Source/AtomicJS/Javascript/JSAPI.cpp

@@ -235,7 +235,43 @@ void js_object_to_variantmap(duk_context* ctx, int objIdx, VariantMap &v)
 
 }
 
-void js_to_variant(duk_context* ctx, int variantIdx, Variant &v)
+duk_bool_t js_check_is_buffer_and_get_data(duk_context* ctx, duk_idx_t idx, void** data, duk_size_t* size)
+{
+    void* temp;
+    if (duk_is_buffer(ctx, idx))
+    {
+        temp = duk_get_buffer_data(ctx, idx, size);
+        if (data)
+        {
+            *data = temp;
+        }
+        return true;
+    }
+    if (!(duk_is_object(ctx, idx) &&
+        duk_has_prop_string(ctx, idx, "length") &&
+        duk_has_prop_string(ctx, idx, "byteLength") &&
+        duk_has_prop_string(ctx, idx, "byteOffset") &&
+        duk_has_prop_string(ctx, idx, "BYTES_PER_ELEMENT")))
+    {
+        if (data)
+        {
+            *data = nullptr;
+        }
+        if (size)
+        {
+            *size = 0;
+        }
+        return false;
+    }
+    temp = duk_require_buffer_data(ctx, idx, size);
+    if (data)
+    {
+        *data = temp;
+    }
+    return true;
+}
+
+void js_to_variant(duk_context* ctx, int variantIdx, Variant &v, VariantType variantType)
 {
     v.Clear();
 
@@ -313,19 +349,68 @@ void js_to_variant(duk_context* ctx, int variantIdx, Variant &v)
         return;
     }
 
-    // object check after array
+    {
+        void* bufferData;
+        duk_size_t bufferSize;
+        if (js_check_is_buffer_and_get_data(ctx, variantIdx, &bufferData, &bufferSize))
+        {
+            // copy the buffer into the variant
+            v.SetBuffer(bufferData, (unsigned)bufferSize);
+            return;
+        }
+    }
+
+    // object check after array and buffer object check
     if (duk_is_object(ctx, variantIdx))
     {
-        RefCounted* o = js_to_class_instance<RefCounted>(ctx, variantIdx, 0);
-        if (o)
-            v = o;
+        if (variantType == VAR_RESOURCEREFLIST)
+        {
+            ResourceRefList refList;
+
+            duk_get_prop_string(ctx, variantIdx, "typeName");
+            refList.type_ = duk_to_string(ctx, -1);
+
+            duk_get_prop_string(ctx, variantIdx, "resources");
+            int length = duk_get_length(ctx, -1);
+
+            for (int i = 0; i < length; i++) {
+
+                duk_get_prop_index(ctx, -1, i);
+
+                Resource* resource = NULL;
+
+                if (duk_is_object(ctx, -1))
+                {
+                     resource = js_to_class_instance<Resource>(ctx, -1, 0);
+
+                }
+
+                if (resource) {
+                    refList.names_.Push(resource->GetName());
+                }
+                else
+                    refList.names_.Push(String::EMPTY);
+
+                duk_pop(ctx);
+            }
+
+            duk_pop_n(ctx, 2);
+
+            v = refList;
+        }
+        else
+        {
+            RefCounted* o = js_to_class_instance<RefCounted>(ctx, variantIdx, 0);
+            if (o)
+                v = o;
+        }
+
         return;
     }
 
 
 }
 
-
 // variant map Proxy getter, so we can convert access to string based
 // member lookup, to string hash on the fly
 
@@ -405,18 +490,7 @@ void js_push_variantmap(duk_context* ctx, const VariantMap &vmap)
 
 void js_push_variant(duk_context *ctx, const Variant& v)
 {
-    VariantType type = v.GetType();
-    RefCounted* ref;
-    Vector2 vector2 = Vector2::ZERO;
-    IntVector2 intVector2 = IntVector2::ZERO;
-    Vector3 vector3 = Vector3::ZERO;
-    Vector4 vector4 = Vector4::ZERO;
-    Color color = Color::BLACK;
-    Resource* resource = NULL;
-    ResourceCache* cache = NULL;
-    ResourceRef resourceRef;
-
-    switch (type)
+    switch (v.GetType())
     {
     case VAR_NONE:
         duk_push_undefined(ctx);
@@ -425,9 +499,10 @@ void js_push_variant(duk_context *ctx, const Variant& v)
     case VAR_VOIDPTR:
         duk_push_null(ctx);
         break;
-    case VAR_PTR:
 
-        ref = v.GetPtr();
+    case VAR_PTR:
+    {
+        RefCounted* ref = v.GetPtr();
 
         // if we're null or don't have any refs, return null
         if (!ref || !ref->Refs())
@@ -438,7 +513,7 @@ void js_push_variant(duk_context *ctx, const Variant& v)
 
         // check that class is supported
         duk_push_heap_stash(ctx);
-        duk_push_pointer(ctx, (void*) ref->GetClassID());
+        duk_push_pointer(ctx, (void*)ref->GetClassID());
         duk_get_prop(ctx, -2);
 
         if (!duk_is_object(ctx, -1))
@@ -452,45 +527,92 @@ void js_push_variant(duk_context *ctx, const Variant& v)
             js_push_class_object_instance(ctx, ref);
         }
 
-        break;
+    }   break;
 
     case VAR_RESOURCEREF:
-        resourceRef = v.GetResourceRef();
-        cache = JSVM::GetJSVM(ctx)->GetContext()->GetSubsystem<ResourceCache>();
-        resource = cache->GetResource(resourceRef.type_, resourceRef.name_);
+    {
+        const ResourceRef& resourceRef(v.GetResourceRef());
+        ResourceCache* cache = JSVM::GetJSVM(ctx)->GetContext()->GetSubsystem<ResourceCache>();
+        Resource* resource = cache->GetResource(resourceRef.type_, resourceRef.name_);
         js_push_class_object_instance(ctx, resource);
-        break;
+    }   break;
+
+    case VAR_RESOURCEREFLIST:
+    {
+
+        const ResourceRefList& resourceRefList(v.GetResourceRefList());
+        const Context* context = JSVM::GetJSVM(ctx)->GetContext();
+
+        duk_push_object(ctx);
+        duk_push_string(ctx, context->GetTypeName(resourceRefList.type_).CString());
+        duk_put_prop_string(ctx, -2, "typeName");
+
+        duk_push_array(ctx);
+
+        ResourceCache* cache = context->GetSubsystem<ResourceCache>();
+
+        for (unsigned i = 0; i < resourceRefList.names_.Size(); i++) {
+
+            Resource* resource = cache->GetResource(resourceRefList.type_, resourceRefList.names_[i]);
+            js_push_class_object_instance(ctx, resource);
+            duk_put_prop_index(ctx, -2, i);
+        }
+
+        duk_put_prop_string(ctx, -2, "resources");
+
+    } break;
 
     case VAR_BOOL:
         duk_push_boolean(ctx, v.GetBool() ? 1 : 0);
         break;
+
     case VAR_INT:
         duk_push_number(ctx, v.GetInt());
         break;
+
     case VAR_FLOAT:
         duk_push_number(ctx, v.GetFloat());
         break;
+
     case VAR_STRING:
-        duk_push_string(ctx, v.GetString().CString());
-        break;
+    {
+        const String& string(v.GetString());
+        duk_push_lstring(ctx, string.CString(), string.Length());
+    }   break;
+
+    case VAR_BUFFER:
+    {
+        const PODVector<unsigned char>& buffer(v.GetBuffer()); // The braces are to scope this reference.
+        duk_push_fixed_buffer(ctx, buffer.Size());
+        duk_push_buffer_object(ctx, -1, 0, buffer.Size(), DUK_BUFOBJ_UINT8ARRAY);
+        duk_replace(ctx, -2);
+        unsigned char* data = (unsigned char*)duk_require_buffer_data(ctx, -1, (duk_size_t*)nullptr);
+        memcpy(data, buffer.Buffer(), buffer.Size());
+    }   break;
+
     case VAR_VECTOR2:
-        vector2 = v.GetVector2();
+    {
+        const Vector2& vector2(v.GetVector2());
         duk_push_array(ctx);
         duk_push_number(ctx, vector2.x_);
         duk_put_prop_index(ctx, -2, 0);
         duk_push_number(ctx, vector2.y_);
         duk_put_prop_index(ctx, -2, 1);
-        break;
+    }   break;
+
     case VAR_INTVECTOR2:
-        intVector2 = v.GetIntVector2();
+    {
+        const IntVector2& intVector2(v.GetIntVector2());
         duk_push_array(ctx);
         duk_push_number(ctx, intVector2.x_);
         duk_put_prop_index(ctx, -2, 0);
         duk_push_number(ctx, intVector2.y_);
         duk_put_prop_index(ctx, -2, 1);
-        break;
+    }   break;
+
     case VAR_VECTOR3:
-        vector3 = v.GetVector3();
+    {
+        const Vector3& vector3(v.GetVector3());
         duk_push_array(ctx);
         duk_push_number(ctx, vector3.x_);
         duk_put_prop_index(ctx, -2, 0);
@@ -498,9 +620,11 @@ void js_push_variant(duk_context *ctx, const Variant& v)
         duk_put_prop_index(ctx, -2, 1);
         duk_push_number(ctx, vector3.z_);
         duk_put_prop_index(ctx, -2, 2);
-        break;
+    }   break;
+
     case VAR_QUATERNION:
-        vector3 = v.GetQuaternion().EulerAngles();
+    {
+        const Vector3& vector3(v.GetQuaternion().EulerAngles());
         duk_push_array(ctx);
         duk_push_number(ctx, vector3.x_);
         duk_put_prop_index(ctx, -2, 0);
@@ -508,9 +632,11 @@ void js_push_variant(duk_context *ctx, const Variant& v)
         duk_put_prop_index(ctx, -2, 1);
         duk_push_number(ctx, vector3.z_);
         duk_put_prop_index(ctx, -2, 2);
-        break;
+    }   break;
+
     case VAR_COLOR:
-        color = v.GetColor();
+    {
+        const Color& color(v.GetColor());
         duk_push_array(ctx);
         duk_push_number(ctx, color.r_);
         duk_put_prop_index(ctx, -2, 0);
@@ -520,9 +646,11 @@ void js_push_variant(duk_context *ctx, const Variant& v)
         duk_put_prop_index(ctx, -2, 2);
         duk_push_number(ctx, color.a_);
         duk_put_prop_index(ctx, -2, 3);
-        break;
+    }   break;
+
     case VAR_VECTOR4:
-        vector4 = v.GetVector4();
+    {
+        const Vector4& vector4(v.GetVector4());
         duk_push_array(ctx);
         duk_push_number(ctx, vector4.x_);
         duk_put_prop_index(ctx, -2, 0);
@@ -532,7 +660,7 @@ void js_push_variant(duk_context *ctx, const Variant& v)
         duk_put_prop_index(ctx, -2, 2);
         duk_push_number(ctx, vector4.w_);
         duk_put_prop_index(ctx, -2, 3);
-        break;
+    }   break;
 
     default:
         duk_push_undefined(ctx);

+ 4 - 1
Source/AtomicJS/Javascript/JSAPI.h

@@ -59,8 +59,11 @@ void js_class_get_constructor(duk_context* ctx, const char* package, const char
 void js_push_variant(duk_context* ctx, const Variant &v);
 void js_push_variantmap(duk_context* ctx, const VariantMap &vmap);
 
-void js_to_variant(duk_context* ctx, int variantIdx, Variant &v);
+void js_to_variant(duk_context* ctx, int variantIdx, Variant &v, VariantType variantType = VAR_NONE);
 
 void js_object_to_variantmap(duk_context* ctx, int objIdx, VariantMap &v);
 
+/// Returns true if the item is a buffer, and if data and size are passed, they are given values to access the buffer data.
+duk_bool_t js_check_is_buffer_and_get_data(duk_context* ctx, duk_idx_t idx, void** data, duk_size_t* size);
+
 }

+ 69 - 12
Source/AtomicJS/Javascript/JSIO.cpp

@@ -22,6 +22,7 @@
 
 #include "JSIO.h"
 #include "JSVM.h"
+#include <Atomic/IO/BufferQueue.h>
 #include <Atomic/IO/File.h>
 
 namespace Atomic
@@ -34,7 +35,8 @@ enum IO_MAGIC_TYPE
     IO_MAGIC_BOOL,
     IO_MAGIC_FLOAT,
     IO_MAGIC_STRING,
-    IO_MAGIC_ZEROSTRING
+    IO_MAGIC_ZEROSTRING,
+    IO_MAGIC_BINARY
 };
 
 
@@ -46,11 +48,17 @@ static Serializer* CastToSerializer(duk_context* ctx, int index)
     if (!o)
         return NULL;
 
-    // type check! file supported for now
-    if (o->GetType() == File::GetTypeStatic())
+    // Type check! Only File and BufferQueue are supported for now.
+    StringHash type(o->GetType());
+    if (type == File::GetTypeStatic())
     {
-        // cast it!
-        serial = (Serializer*) ((File*)o);
+        // Cast it!
+        serial = (Serializer*)((File*)o);
+    }
+    else if (type == BufferQueue::GetTypeStatic())
+    {
+        // Cast it!
+        serial = (Serializer*)((BufferQueue*)o);
     }
 
     return serial;
@@ -65,11 +73,17 @@ static Deserializer* CastToDeserializer(duk_context* ctx, int index)
     if (!o)
         return NULL;
 
-    // type check! file supported for now
-    if (o->GetType() == File::GetTypeStatic())
+    // Type check! Only File and BufferQueue are supported for now.
+    StringHash type(o->GetType());
+    if (type == File::GetTypeStatic())
+    {
+        // Cast it!
+        deserial = (Deserializer*)((File*)o);
+    }
+    else if (type == BufferQueue::GetTypeStatic())
     {
-        // cast it!
-        deserial = (Deserializer*) ((File*)o);
+        // Cast it!
+        deserial = (Deserializer*)((BufferQueue*)o);
     }
 
     return deserial;
@@ -125,6 +139,10 @@ static int Serializer_Write(duk_context* ctx)
         case IO_MAGIC_ZEROSTRING:
             success = serial->WriteString(duk_require_string(ctx, 0));
             break;
+        case IO_MAGIC_BINARY:
+            str = (const char*) duk_require_buffer_data(ctx, 0, &length);
+            success = serial->Write(str, length);
+            break;
         default:
             break;
     }
@@ -152,7 +170,7 @@ static int Deserializer_Read(duk_context* ctx)
         return 1;
     }
 
-    PODVector<unsigned char> buffer;
+    char* data;
     String str;
     size_t length;
 
@@ -175,6 +193,14 @@ static int Deserializer_Read(duk_context* ctx)
         case IO_MAGIC_ZEROSTRING:
             success = duk_push_string(ctx, deserial->ReadString().CString());
             return 1;
+        case IO_MAGIC_BINARY:
+            length = deserial->GetSize() - deserial->GetPosition();
+            duk_push_fixed_buffer(ctx, length);
+            duk_push_buffer_object(ctx, -1, 0, length, DUK_BUFOBJ_UINT8ARRAY);
+            duk_replace(ctx, -2);
+            data = (char*) duk_require_buffer_data(ctx, 0, &length);
+            success = deserial->Read(data, length);
+            return 1;
         default:
             break;
     }
@@ -185,6 +211,25 @@ static int Deserializer_Read(duk_context* ctx)
 
 }
 
+static int Deserializer_GetSize(duk_context* ctx)
+{
+    duk_push_this(ctx);
+
+    // safe cast based on type check above
+    Deserializer* deserial = CastToDeserializer(ctx, duk_get_top_index(ctx));
+
+    duk_pop(ctx);
+
+    if (!deserial)
+    {
+        duk_push_boolean(ctx, 0);
+        return 1;
+    }
+
+    duk_push_number(ctx, (double)deserial->GetSize());
+    return 1;
+}
+
 static void AddSerializerMixin(duk_context* ctx, const String& package, const String& classname)
 {
     js_class_get_prototype(ctx, package.CString(), classname.CString());
@@ -201,8 +246,11 @@ static void AddSerializerMixin(duk_context* ctx, const String& package, const St
     duk_set_magic(ctx, -1, (unsigned) IO_MAGIC_ZEROSTRING);
     duk_put_prop_string(ctx, -2, "writeZeroString");
 
-    duk_pop(ctx);
+    duk_push_c_function(ctx, Serializer_Write, 1);
+    duk_set_magic(ctx, -1, (unsigned)IO_MAGIC_BINARY);
+    duk_put_prop_string(ctx, -2, "writeBinary");
 
+    duk_pop(ctx);
 }
 
 static void AddDeserializerMixin(duk_context* ctx, const String& package, const String& classname)
@@ -221,8 +269,14 @@ static void AddDeserializerMixin(duk_context* ctx, const String& package, const
     duk_set_magic(ctx, -1, (unsigned) IO_MAGIC_ZEROSTRING);
     duk_put_prop_string(ctx, -2, "readZeroString");
 
-    duk_pop(ctx);
+    duk_push_c_function(ctx, Deserializer_Read, 0);
+    duk_set_magic(ctx, -1, (unsigned)IO_MAGIC_BINARY);
+    duk_put_prop_string(ctx, -2, "readBinary");
+
+    duk_push_c_function(ctx, Deserializer_GetSize, 0);
+    duk_put_prop_string(ctx, -2, "getSize");
 
+    duk_pop(ctx);
 }
 
 
@@ -251,6 +305,9 @@ void jsapi_init_io(JSVM* vm)
     AddSerializerMixin(ctx, "Atomic", "File");
     AddDeserializerMixin(ctx, "Atomic", "File");
 
+    AddSerializerMixin(ctx, "Atomic", "BufferQueue");
+    AddDeserializerMixin(ctx, "Atomic", "BufferQueue");
+
 }
 
 }

+ 28 - 26
Source/AtomicJS/Javascript/JSSceneSerializable.cpp

@@ -60,8 +60,6 @@ namespace Atomic
 static int Serializable_SetAttribute(duk_context* ctx)
 {
     const char* name = duk_to_string(ctx, 0);
-    Variant v;
-    js_to_variant(ctx, 1, v);
 
     duk_push_this(ctx);
     Serializable* serial = js_to_class_instance<Serializable>(ctx, -1, 0);
@@ -87,6 +85,10 @@ static int Serializable_SetAttribute(duk_context* ctx)
         }
     }
 
+
+    Variant v;
+    js_to_variant(ctx, 1, v, variantType);
+
     ScriptComponent* jsc = NULL;
 
     // check dynamic
@@ -161,7 +163,7 @@ static int Serializable_SetAttribute(duk_context* ctx)
 
         }
 
-    }
+    }    
 
     if (isAttr)
     {
@@ -237,25 +239,6 @@ static int Serializable_GetAttribute(duk_context* ctx)
     return 1;
 }
 
-static const String& GetResourceRefClassName(Context* context, const ResourceRef& ref)
-{
-    const HashMap<StringHash, SharedPtr<ObjectFactory>>& factories = context->GetObjectFactories();
-
-    HashMap<StringHash, SharedPtr<ObjectFactory>>::ConstIterator itr = factories.Begin();
-
-    while (itr != factories.End())
-    {
-        if (itr->first_ == ref.type_)
-        {
-            return itr->second_->GetTypeName();
-        }
-
-        itr++;
-    }
-
-    return String::EMPTY;
-}
-
 static void GetDynamicAttributes(duk_context* ctx, unsigned& count, const VariantMap& defaultFieldValues,
                                  const FieldMap& fields,
                                  const EnumMap& enums)
@@ -275,7 +258,7 @@ static void GetDynamicAttributes(duk_context* ctx, unsigned& count, const Varian
                 if (defaultFieldValues[itr->first_]->GetType() == VAR_RESOURCEREF)
                 {
                     const ResourceRef& ref = defaultFieldValues[itr->first_]->GetResourceRef();
-                    const String& typeName = GetResourceRefClassName(JSVM::GetJSVM(ctx)->GetContext(), ref);
+                    const String& typeName = JSVM::GetJSVM(ctx)->GetContext()->GetTypeName(ref.type_);
 
                     if (typeName.Length())
                     {
@@ -296,7 +279,7 @@ static void GetDynamicAttributes(duk_context* ctx, unsigned& count, const Varian
             duk_put_prop_string(ctx, -2, "defaultValue");
 
             duk_push_boolean(ctx, 1);
-            duk_put_prop_string(ctx, -2, "field");
+            duk_put_prop_string(ctx, -2, "dynamic");
 
             duk_push_array(ctx);
 
@@ -367,7 +350,7 @@ static int Serializable_GetAttributes(duk_context* ctx)
                 if (attr->defaultValue_.GetType() == VAR_RESOURCEREF)
                 {
                     const ResourceRef& ref = attr->defaultValue_.GetResourceRef();
-                    const String& typeName = GetResourceRefClassName(serial->GetContext(), ref);
+                    const String& typeName = serial->GetContext()->GetTypeName(ref.type_);
 
                     if (typeName.Length())
                     {
@@ -376,8 +359,27 @@ static int Serializable_GetAttributes(duk_context* ctx)
 
                     }
                 }
+
             }
 
+            if (attr->type_ == VAR_RESOURCEREFLIST)
+            {
+                if (attr->defaultValue_.GetType() == VAR_RESOURCEREFLIST)
+                {
+                    const ResourceRefList& ref = attr->defaultValue_.GetResourceRefList();
+                    const String& typeName = serial->GetContext()->GetTypeName(ref.type_);
+
+                    if (typeName.Length())
+                    {
+                        duk_push_string(ctx, typeName.CString());
+                        duk_put_prop_string(ctx, -2, "resourceTypeName");
+
+                    }
+                }
+
+            }
+
+
             duk_push_string(ctx, attr->name_.CString());
             duk_put_prop_string(ctx, -2, "name");
 
@@ -388,7 +390,7 @@ static int Serializable_GetAttributes(duk_context* ctx)
             duk_put_prop_string(ctx, -2, "defaultValue");
 
             duk_push_boolean(ctx, 0);
-            duk_put_prop_string(ctx, -2, "field");
+            duk_put_prop_string(ctx, -2, "dynamic");
 
             duk_push_array(ctx);
 

+ 3 - 1
Source/ThirdParty/TurboBadger/tb_editfield.cpp

@@ -371,7 +371,9 @@ void TBEditField::OnFocusChanged(bool focused)
             if (!curText.Equals(m_initial_edit_text))
             {
                 TBWidgetEvent ev(EVENT_TYPE_CUSTOM);
-                ev.ref_id = TBIDC("edit_complete");
+                // TBIDC does not register the TBID with the UI system, so do it this way
+                TBID refid("edit_complete");
+                ev.ref_id = refid;
                 // forward to delegate
                 TBWidget::OnEvent(ev);
             }

+ 19 - 14
Source/ThirdParty/TurboBadger/tb_inline_select.cpp

@@ -39,7 +39,8 @@ TBInlineSelect::TBInlineSelect()
 	m_buttons[1].SetID(TBIDC("inc"));
 	m_buttons[0].SetAutoRepeat(true);
 	m_buttons[1].SetAutoRepeat(true);
-	m_editfield.SetTextAlign(TB_TEXT_ALIGN_CENTER);
+    m_editfield.SetID(TBIDC("edit"));
+    m_editfield.SetTextAlign(TB_TEXT_ALIGN_CENTER);
 	m_editfield.SetEditType(EDIT_TYPE_NUMBER);
 	m_editfield.SetText("0");
 
@@ -119,11 +120,23 @@ bool TBInlineSelect::OnEvent(const TBWidgetEvent &ev)
 	else if (ev.type == EVENT_TYPE_CLICK && ev.target->GetID() == TBIDC("dec"))
 	{
         SetValueDouble(GetValueDouble() - 1);
+        if (!ev.target->IsCaptured()) {
+
+            InvokeModifiedEvent();
+
+        }
 		return true;
 	}
 	else if (ev.type == EVENT_TYPE_CLICK && ev.target->GetID() == TBIDC("inc"))
 	{
         SetValueDouble(GetValueDouble() + 1);
+
+        if (!ev.target->IsCaptured()) {
+
+            InvokeModifiedEvent();
+
+        }
+
 		return true;
 	}
 	else if (ev.type == EVENT_TYPE_CHANGED && ev.target == &m_editfield)
@@ -132,19 +145,9 @@ bool TBInlineSelect::OnEvent(const TBWidgetEvent &ev)
 		m_editfield.GetText(text);
         SetValueInternal((double) atof(text), false);
 	}
-
-    // catch mouse up/mouse down on buttons for modifications
-    if (ev.target == &m_buttons[0] || ev.target == &m_buttons[1])
+    else if (ev.type == EVENT_TYPE_CHANGED && ev.target == this)
     {
-        if (ev.type == EVENT_TYPE_POINTER_DOWN)
-        {
-            m_modified = true;
-        }
-        else if (ev.type == EVENT_TYPE_POINTER_UP)
-        {
-            if (m_modified)
-                InvokeModifiedEvent();
-        }
+        return TBWidget::OnEvent(ev);
     }
 
 	return false;
@@ -153,7 +156,9 @@ bool TBInlineSelect::OnEvent(const TBWidgetEvent &ev)
 void TBInlineSelect::InvokeModifiedEvent()
 {
     TBWidgetEvent ev(EVENT_TYPE_CUSTOM);
-    ev.ref_id = TBIDC("edit_complete");
+    // TBIDC does not register the TBID with the UI system, so do it this way
+    TBID refid("edit_complete");
+    ev.ref_id = refid;
     // forward to delegate
     TBWidget::OnEvent(ev);
     m_modified = false;

+ 3 - 1
Source/ThirdParty/TurboBadger/tb_scroll_container.cpp

@@ -226,6 +226,8 @@ bool TBScrollContainer::OnEvent(const TBWidgetEvent &ev)
 	}
     else if (ev.type == EVENT_TYPE_KEY_DOWN && !m_ignore_scroll_events)
 	{
+        // ATOMIC: Disabling arrow key scroll
+        /*
 		if (ev.special_key == TB_KEY_LEFT && m_scrollbar_x.CanScrollNegative())
 			ScrollBySmooth(-TBSystem::GetPixelsPerLine(), 0);
 		else if (ev.special_key == TB_KEY_RIGHT && m_scrollbar_x.CanScrollPositive())
@@ -234,7 +236,7 @@ bool TBScrollContainer::OnEvent(const TBWidgetEvent &ev)
 			ScrollBySmooth(0, -TBSystem::GetPixelsPerLine());
 		else if (ev.special_key == TB_KEY_DOWN && m_scrollbar_y.CanScrollPositive())
 			ScrollBySmooth(0, TBSystem::GetPixelsPerLine());
-		else if (ev.special_key == TB_KEY_PAGE_UP && m_scrollbar_y.CanScrollNegative())
+        else*/ if (ev.special_key == TB_KEY_PAGE_UP && m_scrollbar_y.CanScrollNegative())
 			ScrollBySmooth(0, -GetPaddingRect().h);
 		else if (ev.special_key == TB_KEY_PAGE_DOWN && m_scrollbar_y.CanScrollPositive())
 			ScrollBySmooth(0, GetPaddingRect().h);

+ 75 - 6
Source/ThirdParty/TurboBadger/tb_select.cpp

@@ -28,6 +28,7 @@ TBSelectList::TBSelectList()
 	, m_scroll_to_current(false)
     , m_header_lng_string_id(TBIDC("TBList.header"))
     , m_sort_callback(select_list_sort_cb)
+    , m_ui_list_view(false)
 {
 	SetSource(&m_default_source);
 	SetIsFocusable(true);
@@ -187,6 +188,13 @@ void TBSelectList::ValidateList()
 
 	// FIX: Should not scroll just because we update the list. Only automatically first time!
 	m_scroll_to_current = true;
+
+    TBWidgetEvent ev(EVENT_TYPE_CUSTOM);
+    // TBIDC does not register the string with the UI system
+    TBID refid("select_list_validation_end");
+    ev.ref_id = refid;
+    InvokeEvent(ev);
+
 }
 
 TBWidget *TBSelectList::CreateAndAddItemAfter(int index, TBWidget *reference)
@@ -206,7 +214,9 @@ void TBSelectList::SetValue(int value)
 	if (value == m_value)
 		return;
 
-	SelectItem(m_value, false);
+    if (!m_ui_list_view)
+        SelectItem(m_value, false);
+
 	m_value = value;
 	SelectItem(m_value, true);
 	ScrollToSelectedItem();
@@ -217,6 +227,23 @@ void TBSelectList::SetValue(int value)
 	InvokeEvent(ev);
 }
 
+TBID TBSelectList::GetItemID(int index) const
+{
+    if (!m_source)
+        return TBID();
+
+    return m_source->GetItemID(index);
+
+}
+
+int TBSelectList::GetNumItems() const
+{
+    if (!m_source)
+        return 0;
+
+    return m_source->GetNumItems();
+}
+
 TBID TBSelectList::GetSelectedItemID()
 {
 	if (m_source && m_value >= 0 && m_value < m_source->GetNumItems())
@@ -226,8 +253,11 @@ TBID TBSelectList::GetSelectedItemID()
 
 void TBSelectList::SelectItem(int index, bool selected)
 {
-	if (TBWidget *widget = GetItemWidget(index))
-		widget->SetState(WIDGET_STATE_SELECTED, selected);
+    if (!m_ui_list_view)
+    {
+        if (TBWidget *widget = GetItemWidget(index))
+            widget->SetState(WIDGET_STATE_SELECTED, selected);
+    }
 }
 
 TBWidget *TBSelectList::GetItemWidget(int index)
@@ -279,7 +309,22 @@ bool TBSelectList::OnEvent(const TBWidgetEvent &ev)
 		TBWidgetSafePointer this_widget(this);
 
 		int index = ev.target->data.GetInt();
-		SetValue(index);
+
+        if (!m_ui_list_view)
+            SetValue(index);
+        else
+        {
+            if (TBWidget *widget = GetItemWidget(index))
+            {
+                TBWidgetEvent change_ev(EVENT_TYPE_CUSTOM);
+                // TBIDC does not register the string with the UI system
+                TBID refid("select_list_selection_changed");
+                change_ev.ref_id = refid;
+                change_ev.modifierkeys = ev.modifierkeys;
+                // forward to delegate
+                widget->InvokeEvent(change_ev);
+            }
+        }
 
 		// If we're still around, invoke the click event too.
 		if (this_widget.Get())
@@ -322,7 +367,7 @@ bool TBSelectList::OnEvent(const TBWidgetEvent &ev)
 
 bool TBSelectList::ChangeValue(SPECIAL_KEY key)
 {
-	if (!m_source || !m_layout.GetContentRoot()->GetFirstChild())
+    if (m_ui_list_view || !m_source || !m_layout.GetContentRoot()->GetFirstChild())
 		return false;
 
 	bool forward;
@@ -358,6 +403,30 @@ bool TBSelectList::ChangeValue(SPECIAL_KEY key)
 	return false;
 }
 
+bool TBSelectList::GetItemSelected(int index)
+{
+    if (TBWidget *widget = GetItemWidget(index))
+        return widget->GetState(WIDGET_STATE_SELECTED);
+
+    return false;
+
+}
+
+void TBSelectList::SelectAllItems(bool select)
+{
+    if (!m_source)
+        return;
+
+    for (int i = 0; i < m_source->GetNumItems(); i++)
+    {
+        SelectItem(i, select);
+    }
+
+    if (!select)
+        m_value = -1;
+
+}
+
 // == TBSelectDropdown ==========================================
 
 TBSelectDropdown::TBSelectDropdown()
@@ -470,4 +539,4 @@ bool TBSelectDropdown::OnEvent(const TBWidgetEvent &ev)
 	return false;
 }
 
-}; // namespace tb
+} // namespace tb

+ 11 - 0
Source/ThirdParty/TurboBadger/tb_select.h

@@ -76,6 +76,7 @@ public:
 		to unselect the previously selected item, use SetValue. */
 	void SelectItem(int index, bool selected);
 	TBWidget *GetItemWidget(int index);
+    bool GetItemSelected(int index);
 
 	/** Scroll to the current selected item. The scroll may be delayed until
 		the items has been layouted if the layout is currently invalid. */
@@ -84,6 +85,11 @@ public:
 	/** Return the scrollcontainer used in this list. */
 	TBScrollContainer *GetScrollContainer() { return &m_container; }
 
+    TBID GetItemID(int index) const;
+    int GetNumItems() const;
+
+    void SetUIListView(bool value) { m_ui_list_view = value; }
+
 	virtual void OnInflate(const INFLATE_INFO &info);
 	virtual void OnSkinChanged();
 	virtual void OnProcess();
@@ -104,10 +110,15 @@ protected:
 	TBStr m_filter;
 	bool m_list_is_invalid;
 	bool m_scroll_to_current;
+    bool m_ui_list_view;
 	TBID m_header_lng_string_id;
 private:
     TBSelectListSortCallback m_sort_callback;
 	TBWidget *CreateAndAddItemAfter(int index, TBWidget *reference);
+
+    void SelectAllItems(bool select = false);
+
+
 };
 
 /** TBSelectDropdown shows a button that opens a popup with a TBSelectList with items

+ 1 - 0
Source/ToolCore/Assets/AssetDatabase.cpp

@@ -530,6 +530,7 @@ String AssetDatabase::GetResourceImporterName(const String& resourceTypeName)
     {
         resourceTypeToImporterType_["Sound"] = "AudioImporter";
         resourceTypeToImporterType_["Model"] = "ModelImporter";
+        resourceTypeToImporterType_["Material"] = "MaterialImporter";
         resourceTypeToImporterType_["Texture2D"] = "TextureImporter";
         resourceTypeToImporterType_["Sprite2D"] = "TextureImporter";
         resourceTypeToImporterType_["AnimatedSprite2D"] = "SpriterImporter";

+ 11 - 0
Source/ToolCore/Assets/MaterialImporter.cpp

@@ -70,5 +70,16 @@ bool MaterialImporter::SaveSettingsInternal(JSONValue& jsonRoot)
     return true;
 }
 
+Resource* MaterialImporter::GetResource(const String& typeName)
+{
+    if (!typeName.Length())
+        return 0;
+
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    return cache->GetResource(typeName, asset_->GetPath());
+
+}
+
+
 
 }

+ 2 - 0
Source/ToolCore/Assets/MaterialImporter.h

@@ -25,6 +25,8 @@ public:
 
     void SaveMaterial();
 
+    Resource* GetResource(const String& typeName = String::EMPTY);
+
 protected:
 
     bool Import();

+ 5 - 1
Source/ToolCore/JSBind/JSBFunction.h

@@ -118,7 +118,8 @@ public:
     JSBFunction(JSBClass* klass) : class_(klass), returnType_(0),
                                    isConstructor_(false), isDestructor_(false),
                                    isGetter_(false), isSetter_(false),
-                                   isOverload_(false), skip_(false), isVirtual_(false)
+                                   isOverload_(false), skip_(false), 
+                                   isVirtual_(false), isStatic_(false)
     {
 
     }
@@ -133,6 +134,7 @@ public:
     bool IsGetter() { return isGetter_; }
     bool IsOverload() { return isOverload_; }
     bool IsVirtual() { return isVirtual_; }
+    bool IsStatic() { return isStatic_; }
     bool Skip() { return skip_; }
 
     JSBClass* GetClass() { return class_; }
@@ -153,6 +155,7 @@ public:
     void SetGetter(bool value = true) { isGetter_ = value; }
     void SetOverload(bool value = true) { isOverload_ = value; }
     void SetVirtual(bool value = true) { isVirtual_ = value; }
+    void SetStatic(bool value = true) { isStatic_ = value; }
     void SetSkip(bool value) { skip_ = value; }
     void SetReturnType(JSBFunctionType* retType) { returnType_ = retType; }
     void SetDocString(const String& docString) { docString_ = docString; }
@@ -224,6 +227,7 @@ private:
     bool isSetter_;
     bool isOverload_;
     bool isVirtual_;
+    bool isStatic_;
     bool skip_;
 
 };

+ 3 - 0
Source/ToolCore/JSBind/JSBHeaderVisitor.h

@@ -281,6 +281,9 @@ public:
         if (function->isVirtual())
             jfunction->SetVirtual(true);
 
+        if (function->isStatic())
+            jfunction->SetStatic(true);
+
         // see if we support return type
         if (function->hasReturnType() && !function->returnType().type()->isVoidType())
         {

+ 6 - 1
Source/ToolCore/JSBind/JSBTypeScript.cpp

@@ -108,7 +108,12 @@ void JSBTypeScript::ExportFunction(JSBFunction* function)
     if (function->GetDocString().Length())
         source_ += "      //" + function->GetDocString() + "\n";
 
-    source_ += "      " + scriptName + "(";
+    source_ += "      ";
+
+    if (function->IsStatic())
+        source_ += "static ";
+
+    source_ += scriptName + "(";
 
     Vector<JSBFunctionType*>& parameters = function->GetParameters();
 

+ 51 - 7
Source/ToolCore/JSBind/JavaScript/JSClassWriter.cpp

@@ -57,7 +57,20 @@ void JSClassWriter::GenerateSource(String& sourceOut)
 
     source.AppendWithFormat("static void jsb_class_define_%s(JSVM* vm)\n{\n", klass_->GetName().CString());
     source.Append("duk_context* ctx = vm->GetJSContext();\n");
-    source.AppendWithFormat("js_class_get_prototype(ctx, \"%s\", \"%s\");\n", packageName.CString(), klass_->GetName().CString());
+
+    GenerateStaticFunctionsSource(source, packageName);
+
+    GenerateNonStaticFunctionsSource(source, packageName);
+
+    source.Append("}\n");
+
+    sourceOut += source;
+
+}
+
+void JSClassWriter::GenerateStaticFunctionsSource(String& source, String& packageName)
+{
+    source.AppendWithFormat("js_class_get_constructor(ctx, \"%s\", \"%s\");\n", packageName.CString(), klass_->GetName().CString());
 
     for (unsigned i = 0; i < klass_->functions_.Size(); i++)
     {
@@ -69,27 +82,58 @@ void JSClassWriter::GenerateSource(String& sourceOut)
         if (function->IsConstructor() || function->IsDestructor())
             continue;
 
+        if (!function->IsStatic())
+            continue;
+
         if (function->FirstDefaultParameter() != -1)
         {
             source.AppendWithFormat("duk_push_c_function(ctx, jsb_class_%s_%s, DUK_VARARGS);\n", klass_->GetName().CString(), function->GetName().CString());
         }
         else
         {
-            source.AppendWithFormat("duk_push_c_function(ctx, jsb_class_%s_%s, %i);\n", klass_->GetName().CString(), function->GetName().CString(), (int) function->GetParameters().Size());
+            source.AppendWithFormat("duk_push_c_function(ctx, jsb_class_%s_%s, %i);\n", klass_->GetName().CString(), function->GetName().CString(), (int)function->GetParameters().Size());
         }
 
-        String scriptName =  function->GetName();
+        String scriptName = function->GetName();
         scriptName[0] = tolower(scriptName[0]);
         source.AppendWithFormat("duk_put_prop_string(ctx, -2, \"%s\");\n", scriptName.CString());
-
     }
-
     source.Append("duk_pop(ctx);\n");
-    source.Append("}\n");
+}
 
+void JSClassWriter::GenerateNonStaticFunctionsSource(String& source, String& packageName)
+{
+    source.AppendWithFormat("js_class_get_prototype(ctx, \"%s\", \"%s\");\n", packageName.CString(), klass_->GetName().CString());
 
-    sourceOut += source;
+    for (unsigned i = 0; i < klass_->functions_.Size(); i++)
+    {
+        JSBFunction* function = klass_->functions_.At(i);
 
+        if (function->Skip())
+            continue;
+
+        if (function->IsConstructor() || function->IsDestructor())
+            continue;
+
+        if (function->IsStatic())
+            continue;
+
+        if (function->FirstDefaultParameter() != -1)
+        {
+            source.AppendWithFormat("duk_push_c_function(ctx, jsb_class_%s_%s, DUK_VARARGS);\n", klass_->GetName().CString(), function->GetName().CString());
+        }
+        else
+        {
+            source.AppendWithFormat("duk_push_c_function(ctx, jsb_class_%s_%s, %i);\n", klass_->GetName().CString(), function->GetName().CString(), (int)function->GetParameters().Size());
+        }
+
+        String scriptName = function->GetName();
+        scriptName[0] = tolower(scriptName[0]);
+        source.AppendWithFormat("duk_put_prop_string(ctx, -2, \"%s\");\n", scriptName.CString());
+
+    }
+    source.Append("duk_pop(ctx);\n");
 }
 
+
 }

+ 2 - 0
Source/ToolCore/JSBind/JavaScript/JSClassWriter.h

@@ -31,6 +31,8 @@ public:
 private:
 
     void WriteFunctions(String& source);
+    void GenerateStaticFunctionsSource(String& source, String& packageName);
+    void GenerateNonStaticFunctionsSource(String& source, String& packageName);
 
 };
 

+ 13 - 3
Source/ToolCore/JSBind/JavaScript/JSFunctionWriter.cpp

@@ -349,8 +349,11 @@ void JSFunctionWriter::WriteFunction(String& source)
 
     WriteParameterMarshal(source);
 
-    source.Append("duk_push_this(ctx);\n");
-    source.AppendWithFormat("%s* native = js_to_class_instance<%s>(ctx, -1, 0);\n", klass->GetNativeName().CString(), klass->GetNativeName().CString());
+    if (!function_->IsStatic())
+    {
+        source.Append("duk_push_this(ctx);\n");
+        source.AppendWithFormat("%s* native = js_to_class_instance<%s>(ctx, -1, 0);\n", klass->GetNativeName().CString(), klass->GetNativeName().CString());
+    }
 
     // declare return value;
     bool returnDeclared = false;
@@ -420,7 +423,14 @@ void JSFunctionWriter::WriteFunction(String& source)
 
     }
 
-    source.AppendWithFormat("native->%s(", function_->name_.CString());
+    if (function_->IsStatic())
+    {
+        source.AppendWithFormat("%s::%s(", klass->GetNativeName().CString(), function_->name_.CString());
+    }
+    else
+    {
+        source.AppendWithFormat("native->%s(", function_->name_.CString());
+    }
 
     Vector<JSBFunctionType*>& parameters = function_->GetParameters();