Browse Source

Merge remote-tracking branch 'upstream/master'

Eugene 10 years ago
parent
commit
05ddcba8d2
100 changed files with 6502 additions and 2815 deletions
  1. 1 1
      Build/Scripts/BuildWindows.js
  2. 1 1
      Build/Scripts/Windows/GenerateVS2015.bat
  3. 4 1
      Resources/EditorData/AtomicEditor/editor/skin/skin.tb.txt
  4. BIN
      Resources/EditorData/AtomicEditor/editor/skin/stop.png
  5. 3 3
      Resources/EditorData/AtomicEditor/editor/ui/maintoolbar.tb.txt
  6. BIN
      Resources/EditorData/AtomicEditor/resources/default_skin/checkbox_mark_nonuniform.png
  7. 12 0
      Resources/EditorData/AtomicEditor/resources/default_skin/skin.tb.txt
  8. 9 5
      Script/AtomicEditor/editor/Editor.ts
  9. 16 2
      Script/AtomicEditor/editor/EditorEvents.ts
  10. 35 3
      Script/AtomicEditor/editor/Preferences.ts
  11. 5 1
      Script/AtomicEditor/ui/EditorStrings.ts
  12. 15 3
      Script/AtomicEditor/ui/MainToolbar.ts
  13. 52 25
      Script/AtomicEditor/ui/Shortcuts.ts
  14. 123 184
      Script/AtomicEditor/ui/frames/HierarchyFrame.ts
  15. 22 55
      Script/AtomicEditor/ui/frames/ProjectFrame.ts
  16. 9 3
      Script/AtomicEditor/ui/frames/ResourceFrame.ts
  17. 895 0
      Script/AtomicEditor/ui/frames/inspector/AttributeInfoEdit.ts
  18. 104 0
      Script/AtomicEditor/ui/frames/inspector/ComponentAttributeUI.ts
  19. 0 620
      Script/AtomicEditor/ui/frames/inspector/ComponentInspector.ts
  20. 8 16
      Script/AtomicEditor/ui/frames/inspector/CreateComponentButton.ts
  21. 0 538
      Script/AtomicEditor/ui/frames/inspector/DataBinding.ts
  22. 91 58
      Script/AtomicEditor/ui/frames/inspector/InspectorFrame.ts
  23. 0 450
      Script/AtomicEditor/ui/frames/inspector/NodeInspector.ts
  24. 38 0
      Script/AtomicEditor/ui/frames/inspector/SelectionEditTypes.ts
  25. 728 0
      Script/AtomicEditor/ui/frames/inspector/SelectionInspector.ts
  26. 144 0
      Script/AtomicEditor/ui/frames/inspector/SelectionPrefabWidget.ts
  27. 193 0
      Script/AtomicEditor/ui/frames/inspector/SelectionSection.ts
  28. 121 0
      Script/AtomicEditor/ui/frames/inspector/SelectionSectionCoreUI.ts
  29. 26 0
      Script/AtomicEditor/ui/frames/inspector/SelectionSectionUI.ts
  30. 150 0
      Script/AtomicEditor/ui/frames/inspector/SerializableEditType.ts
  31. 2 7
      Script/AtomicEditor/ui/frames/menus/HierarchyFrameMenu.ts
  32. 13 5
      Script/AtomicEditor/ui/frames/menus/MainFrameMenu.ts
  33. 4 1
      Script/Packages/Atomic/Graphics.json
  34. 1 1
      Script/Packages/Atomic/IO.json
  35. 6 3
      Script/Packages/Editor/Editor.json
  36. 69 9
      Script/TypeScript/AtomicWork.d.ts
  37. 12 3
      Script/tsconfig.json
  38. 1 1
      Source/Atomic/Container/Vector.h
  39. 12 0
      Source/Atomic/Graphics/Direct3D11/D3D11Graphics.cpp
  40. 7 0
      Source/Atomic/Graphics/Direct3D11/D3D11Graphics.h
  41. 12 0
      Source/Atomic/Graphics/Direct3D9/D3D9Graphics.cpp
  42. 7 0
      Source/Atomic/Graphics/Direct3D9/D3D9Graphics.h
  43. 12 0
      Source/Atomic/Graphics/OpenGL/OGLGraphics.cpp
  44. 7 0
      Source/Atomic/Graphics/OpenGL/OGLGraphics.h
  45. 127 0
      Source/Atomic/IO/BufferQueue.cpp
  46. 72 0
      Source/Atomic/IO/BufferQueue.h
  47. 3 0
      Source/Atomic/Scene/PrefabComponent.cpp
  48. 28 1
      Source/Atomic/UI/UI.cpp
  49. 8 0
      Source/Atomic/UI/UI.h
  50. 3 1
      Source/Atomic/UI/UIEditField.cpp
  51. 8 0
      Source/Atomic/UI/UIEvents.h
  52. 4 1
      Source/Atomic/UI/UIInlineSelect.cpp
  53. 1 1
      Source/Atomic/UI/UIInput.cpp
  54. 409 53
      Source/Atomic/UI/UIListView.cpp
  55. 22 1
      Source/Atomic/UI/UIListView.h
  56. 50 0
      Source/Atomic/UI/UISelectList.cpp
  57. 10 0
      Source/Atomic/UI/UISelectList.h
  58. 27 2
      Source/Atomic/UI/UIWidget.cpp
  59. 2 1
      Source/Atomic/Web/Web.h
  60. 135 120
      Source/Atomic/Web/WebSocket.cpp
  61. 28 3
      Source/Atomic/Web/WebSocket.h
  62. 16 2
      Source/AtomicEditor/Application/AEEditorApp.cpp
  63. 38 0
      Source/AtomicEditor/Application/AEEditorCommon.cpp
  64. 3 1
      Source/AtomicEditor/Application/AEEditorCommon.h
  65. 31 0
      Source/AtomicEditor/Application/AEPlayerApp.cpp
  66. 2 0
      Source/AtomicEditor/Application/AEPlayerApp.h
  67. 1 0
      Source/AtomicEditor/CMakeLists.txt
  68. 324 0
      Source/AtomicEditor/Components/CubemapGenerator.cpp
  69. 88 0
      Source/AtomicEditor/Components/CubemapGenerator.h
  70. 45 0
      Source/AtomicEditor/Components/EditorComponent.cpp
  71. 43 0
      Source/AtomicEditor/Components/EditorComponent.h
  72. 15 0
      Source/AtomicEditor/Components/EditorComponents.cpp
  73. 21 0
      Source/AtomicEditor/Components/EditorComponents.h
  74. 4 9
      Source/AtomicEditor/EditorMode/AEEditorEvents.h
  75. 26 5
      Source/AtomicEditor/EditorMode/AEEditorMode.cpp
  76. 5 2
      Source/AtomicEditor/EditorMode/AEEditorMode.h
  77. 1 1
      Source/AtomicEditor/Editors/ResourceEditor.h
  78. 30 25
      Source/AtomicEditor/Editors/SceneEditor3D/Gizmo3D.cpp
  79. 5 2
      Source/AtomicEditor/Editors/SceneEditor3D/Gizmo3D.h
  80. 189 64
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditHistory.cpp
  81. 25 15
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditHistory.h
  82. 429 0
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditOp.cpp
  83. 109 0
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditOp.h
  84. 0 109
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditOps.cpp
  85. 0 118
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditOps.h
  86. 213 96
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditor3D.cpp
  87. 29 9
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditor3D.h
  88. 92 14
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditor3DEvents.h
  89. 383 0
      Source/AtomicEditor/Editors/SceneEditor3D/SceneSelection.cpp
  90. 72 0
      Source/AtomicEditor/Editors/SceneEditor3D/SceneSelection.h
  91. 25 73
      Source/AtomicEditor/Editors/SceneEditor3D/SceneView3D.cpp
  92. 5 7
      Source/AtomicEditor/Editors/SceneEditor3D/SceneView3D.h
  93. 36 0
      Source/AtomicEditor/PlayerMode/AEPlayerEvents.h
  94. 17 0
      Source/AtomicEditor/PlayerMode/AEPlayerMode.cpp
  95. 1 0
      Source/AtomicEditor/PlayerMode/AEPlayerMode.h
  96. 168 40
      Source/AtomicJS/Javascript/JSAPI.cpp
  97. 4 1
      Source/AtomicJS/Javascript/JSAPI.h
  98. 69 12
      Source/AtomicJS/Javascript/JSIO.cpp
  99. 28 26
      Source/AtomicJS/Javascript/JSSceneSerializable.cpp
  100. 3 1
      Source/ThirdParty/TurboBadger/tb_editfield.cpp

+ 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

+ 4 - 1
Resources/EditorData/AtomicEditor/editor/skin/skin.tb.txt

@@ -78,6 +78,9 @@ elements
 	PlayButton
 		bitmap play.png
 
+	StopButton
+		bitmap stop.png
+
 	PowerOffButton
 		bitmap power_off.png
 
@@ -171,7 +174,7 @@ elements
 	InspectorVectorAttrName
 		text-color #aaaaaa
 		min-width 140
-		max-width 140		
+		max-width 140
 
 	InspectorTextAttrName
 		padding 4

BIN
Resources/EditorData/AtomicEditor/editor/skin/stop.png


+ 3 - 3
Resources/EditorData/AtomicEditor/editor/ui/maintoolbar.tb.txt

@@ -1,11 +1,11 @@
 definitions
-	menubutton		
+	menubutton
 		lp: height: 28, width: 28
 		skin TBButton.uniformflat
 TBLayout: distribution: gravity, spacing: 4
-	TBButton 
+	TBButton
 		@include definitions>menubutton
-		TBSkinImage: skin: PlayButton
+		TBSkinImage: skin: PlayButton, id: skin_image
 		id maintoolbar_play
 	TBButton: toggle-mode: 1
 		@include definitions>menubutton

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

+ 9 - 5
Script/AtomicEditor/editor/Editor.ts

@@ -34,12 +34,12 @@ class Editor extends Atomic.ScriptObject {
 
         Editor.instance = this;
 
+        Preferences.getInstance().read();
+
         this.initUI();
 
         this.editorLicense = new EditorLicense();
 
-        Preferences.getInstance().read();
-
         EditorUI.initialize();
 
         this.playMode = new PlayMode();
@@ -53,6 +53,10 @@ class Editor extends Atomic.ScriptObject {
             this.handleProjectUnloaded(data)
         });
 
+        this.subscribeToEvent("IPCPlayerWindowChanged", (data) => {
+            Preferences.getInstance().savePlayerWindowData(data.posX, data.posY, data.width, data.height, data.monitor);
+        });
+
         this.subscribeToEvent("ExitRequested", (data) => this.handleExitRequested(data));
 
         this.subscribeToEvent("ProjectLoaded", (data) => {
@@ -118,6 +122,7 @@ class Editor extends Atomic.ScriptObject {
     }
 
     closeProject() {
+        this.sendEvent("IPCPlayerExitRequest");
         var system = ToolCore.getToolSystem();
 
         if (system.project) {
@@ -129,9 +134,7 @@ class Editor extends Atomic.ScriptObject {
 
     handleProjectUnloaded(event) {
 
-        this.sendEvent(EditorEvents.ActiveSceneChange, { scene: null });
-
-
+        this.sendEvent(EditorEvents.ActiveSceneEditorChange, { sceneEditor: null });
 
     }
 
@@ -157,6 +160,7 @@ class Editor extends Atomic.ScriptObject {
 
     // event handling
     handleExitRequested(data) {
+        this.sendEvent("IPCPlayerExitRequest");
         this.exitRequested = true;
         this.closeAllResourceEditors();
     }

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

@@ -14,6 +14,7 @@ export interface ModalErrorEvent {
 }
 
 export const PlayerStarted = "EditorPlayerStarted";
+export const PlayerStopped = "EditorPlayerStopped";
 export const PlayerLog = "EditorPlayerLog";
 export interface PlayerLogEvent {
 
@@ -23,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 {
 
@@ -75,3 +82,10 @@ export interface EditResourceEvent {
   path: string;
 
 }
+
+export const SceneEditStateChange = "SceneEditStateChange";
+export interface SceneEditStateChangeEvent {
+
+  serializable: Atomic.Serializable;
+
+}

+ 35 - 3
Script/AtomicEditor/editor/Preferences.ts

@@ -68,7 +68,14 @@ class Preferences {
         }
         //Read file
         jsonFile = new Atomic.File(filePath, Atomic.FILE_READ);
-        var prefs = <PreferencesFormat>JSON.parse(jsonFile.readText());
+        var prefs;
+        try {
+          prefs = <PreferencesFormat>JSON.parse(jsonFile.readText());
+        } catch (e){
+          console.log("Editor preference file invalid, regenerating default configuration");
+          prefs = null;
+          this.useDefaultConfig();
+        }
         if (prefs) {
             if (!prefs.recentProjects) prefs.recentProjects = [""];
             this._prefs = prefs;
@@ -87,10 +94,26 @@ class Preferences {
             width = graphics.getWidth();
             height = graphics.getHeight();
         }
-        this._prefs.window = { x: pos[0], y: pos[1], width: width, height: height };
+        this._prefs.editorWindow = { x: pos[0], y: pos[1], width: width, height: height, monitor: graphics.getCurrentMonitor() };
         jsonFile.writeString(JSON.stringify(this._prefs, null, 2));
     }
 
+    savePlayerWindowData(x, y, width, height, monitor) {
+        this._prefs.playerWindow = { x: x, y: y, width: width, height: height, monitor: monitor };
+    }
+
+    useDefaultConfig():void {
+        this._prefs = new PreferencesFormat();
+    }
+
+    get editorWindow():WindowData {
+        return this._prefs.editorWindow;
+    }
+
+    get playerWindow():WindowData {
+        return this._prefs.playerWindow;
+    }
+
     get recentProjects(): [string] {
         return this._prefs.recentProjects;
     }
@@ -100,9 +123,18 @@ class Preferences {
     }
 }
 
+interface WindowData {
+    x: number;
+    y: number;
+    width: number;
+    height: number;
+    monitor: number;
+}
+
 class PreferencesFormat {
     recentProjects: [string];
-    window: { x: number, y: number, width: number, height: number };
+    editorWindow: WindowData;
+    playerWindow: WindowData;
 }
 
 export = Preferences;

+ 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";

+ 15 - 3
Script/AtomicEditor/ui/MainToolbar.ts

@@ -6,6 +6,7 @@
 //
 
 import EditorUI = require("./EditorUI");
+import EditorEvents = require("../editor/EditorEvents");
 
 class MainToolbar extends Atomic.UIWidget {
 
@@ -13,6 +14,7 @@ class MainToolbar extends Atomic.UIWidget {
     rotateButton: Atomic.UIButton;
     scaleButton: Atomic.UIButton;
     axisButton: Atomic.UIButton;
+    playButton: Atomic.UIButton;
 
     constructor(parent: Atomic.UIWidget) {
 
@@ -26,13 +28,25 @@ class MainToolbar extends Atomic.UIWidget {
 
         this.axisButton = <Atomic.UIButton>this.getWidget("3d_axismode");
 
+        this.playButton = <Atomic.UIButton>this.getWidget("maintoolbar_play");
+
         this.translateButton.value = 1;
 
         parent.addChild(this);
 
         this.subscribeToEvent("GizmoAxisModeChanged", (ev) => this.handleGizmoAxisModeChanged(ev));
         this.subscribeToEvent("GizmoEditModeChanged", (ev) => this.handleGizmoEditModeChanged(ev));
+
         this.subscribeToEvent(this, "WidgetEvent", (data) => this.handleWidgetEvent(data));
+
+        this.subscribeToEvent(EditorEvents.PlayerStarted, (data) => {
+            var skin = <Atomic.UISkinImage> this.playButton.getWidget("skin_image");
+            skin.setSkinBg("StopButton");
+        });
+        this.subscribeToEvent(EditorEvents.PlayerStopped, (data) => {
+            var skin = <Atomic.UISkinImage> this.playButton.getWidget("skin_image");
+            skin.setSkinBg("PlayButton");
+        });
     }
 
     handleGizmoAxisModeChanged(ev: Editor.GizmoAxisModeChangedEvent) {
@@ -91,10 +105,8 @@ class MainToolbar extends Atomic.UIWidget {
                 return true;
 
             } else if (ev.target.id == "maintoolbar_play") {
-
-                EditorUI.getShortcuts().invokePlay();
+                EditorUI.getShortcuts().invokePlayOrStopPlayer();
                 return true;
-
             }
 
         }

+ 52 - 25
Script/AtomicEditor/ui/Shortcuts.ts

@@ -7,6 +7,7 @@
 
 import EditorEvents = require("../editor/EditorEvents");
 import EditorUI = require("./EditorUI");
+import Preferences = require("editor/Preferences")
 
 class Shortcuts extends Atomic.ScriptObject {
 
@@ -21,21 +22,28 @@ class Shortcuts extends Atomic.ScriptObject {
 
     }
 
-    invokePlay() {
-
+    //this should be moved somewhere else...
+    invokePlayOrStopPlayer(debug:boolean = false) {
         this.sendEvent(EditorEvents.SaveAllResources);
-        Atomic.editorMode.playProject();
-
-    }
-
-    invokePlayDebug() {
-
-        this.sendEvent(EditorEvents.SaveAllResources);
-        Atomic.editorMode.playProjectDebug();
-
+        if (Atomic.editorMode.isPlayerEnabled()) {
+            this.sendEvent("IPCPlayerExitRequest");
+        } else {
+            var playerWindow = Preferences.getInstance().playerWindow;
+            if (playerWindow) {
+                if ((playerWindow.monitor + 1) > Atomic.graphics.getMonitorsNumber()) {
+                    //will use default settings if monitor is not available
+                    var args = "--resizable";
+                    Atomic.editorMode.playProject(args, debug);
+                } else {
+                    var args = "--windowposx " + playerWindow.x + " --windowposy " + playerWindow.y + " --windowwidth " + playerWindow.width + " --windowheight " + playerWindow.height + " --resizable";
+                    Atomic.editorMode.playProject(args, debug);
+                }
+            } else {
+                Atomic.editorMode.playProject("", debug);
+            }
+        }
     }
 
-
     invokeFormatCode() {
 
         var editor = EditorUI.getMainFrame().resourceframe.currentResourceEditor;
@@ -76,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 });
 
@@ -107,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") {
@@ -131,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) {
@@ -143,7 +170,7 @@ class Shortcuts extends Atomic.ScriptObject {
                 this.invokeFormatCode();
             }
             else if (ev.key == Atomic.KEY_P) {
-                this.invokePlay();
+                this.invokePlayOrStopPlayer();
                 //if shift is pressed
             } else if (ev.qualifiers & Atomic.QUAL_SHIFT) {
                 if (ev.key == Atomic.KEY_B) {

+ 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;

+ 8 - 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"));
@@ -74,6 +72,11 @@ subsystemCreateSource.addItem(new Atomic.UIMenuItem("DebugRenderer", "create com
 subsystemCreateSource.addItem(new Atomic.UIMenuItem("Octree", "create component"));
 subsystemCreateSource.addItem(new Atomic.UIMenuItem("PhysicsWorld", "create component"));
 
+var editorCreateSource = new Atomic.UIMenuItemSource();
+
+editorCreateSource.addItem(new Atomic.UIMenuItem("CubemapGenerator", "CubemapGenerator"));
+
+
 var componentCreateSource = new Atomic.UIMenuItemSource();
 
 var sources = {
@@ -86,6 +89,7 @@ var sources = {
     Physics: physicsCreateSource,
     Scene: sceneCreateSource,
     SubSystem: subsystemCreateSource,
+    Editor : editorCreateSource
 }
 
 for (var sub in sources) {
@@ -99,12 +103,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 +131,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 +139,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;

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

@@ -0,0 +1,121 @@
+//
+// 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");
+
+import ProgressModal = require("ui/modal/ProgressModal");
+
+
+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);
+
+    }
+
+}
+
+class CubemapGeneratorSectionUI 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 = "Render Cubemap";
+
+        button.onClick = () => {
+
+            var scene = null;
+            var count = 0;
+            var progressModal = new ProgressModal("Rendering Cubemaps", "Rendering Cubemaps, please wait...");
+
+            for (var i in this.editType.objects) {
+
+                var gen = <Editor.CubemapGenerator>this.editType.objects[i];
+
+                if (!scene) {
+
+                    scene = gen.scene;
+
+                    this.subscribeToEvent(scene, "CubemapRenderBegin", () => {
+
+                        count++;
+
+                    })
+
+                    this.subscribeToEvent(scene, "CubemapRenderEnd", () => {
+
+                        count--;
+
+                        if (!count)
+                            progressModal.hide();
+
+                    })
+
+
+                }
+
+                if (!gen.render()) {
+
+                    //TODO: Cancel other renders if any and report better error information
+
+                    EditorUI.showModalError("Render Cubemaps",
+                        "There was an error rendering cubemaps, please see the application log");
+
+                    scene = null;
+                    break;
+
+                }
+            }
+
+            if (scene) {
+
+                progressModal.show();
+
+            }
+
+        };
+
+        this.addChild(button);
+
+    }
+
+}
+
+SelectionSection.registerCustomSectionUI("CollisionShape", CollisionShapeSectionUI);
+SelectionSection.registerCustomSectionUI("CubemapGenerator", CubemapGeneratorSectionUI);

+ 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;

+ 13 - 5
Script/AtomicEditor/ui/frames/menus/MainFrameMenu.ts

@@ -54,12 +54,12 @@ class MainFrameMenu extends Atomic.ScriptObject {
         } else if (target.id == "menu edit popup") {
 
             if (refid == "edit play") {
-                EditorUI.getShortcuts().invokePlay();
+                EditorUI.getShortcuts().invokePlayOrStopPlayer();
                 return true;
             }
 
             if (refid == "edit play debug") {
-                EditorUI.getShortcuts().invokePlayDebug();
+                EditorUI.getShortcuts().invokePlayOrStopPlayer(true);
                 return true;
             }
 
@@ -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"]
 
 };

+ 4 - 1
Script/Packages/Atomic/Graphics.json

@@ -15,6 +15,9 @@
 		},
 		"Camera" : {
 			"SetOrthoSize" : ["float"]
+		},
+		"Graphics" : {
+			"SetWindowPosition" : ["int", "int"]
 		}
 
 	},
@@ -31,7 +34,7 @@
 		]
 
 	},
-	
+
 	"haxe_decl" : {
 		"Light" : [
 			"function getShadowCascade():Array<Float>;",

+ 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"]

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

@@ -1,7 +1,10 @@
 {
 	"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"]
+							 "Source/AtomicEditor/EditorMode", "Source/AtomicEditor/PlayerMode",
+							 "Source/AtomicEditor/Editors", "Source/AtomicEditor/Editors/SceneEditor3D",
+						   "Source/AtomicEditor/Components"],
+	"classes" : ["EditorMode", "PlayerMode", "FileUtils", "ResourceEditor", "JSResourceEditor",
+							 "SceneEditor3D", "SceneView3D", "SceneSelection", "EditorComponent", "CubemapGenerator"]
 }

+ 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)
     {

+ 12 - 0
Source/Atomic/Graphics/Direct3D11/D3D11Graphics.cpp

@@ -2781,4 +2781,16 @@ void RegisterGraphicsLibrary(Context* context)
     Zone::RegisterObject(context);
 }
 
+// ATOMIC BEGIN
+int Graphics::GetCurrentMonitor()
+{
+    return SDL_GetWindowDisplayIndex((SDL_Window*) this->GetSDLWindow());
+}
+
+int Graphics::GetMonitorsNumber()
+{
+    return SDL_GetNumVideoDisplays();
+}
+// ATOMIC END
+
 }

+ 7 - 0
Source/Atomic/Graphics/Direct3D11/D3D11Graphics.h

@@ -499,6 +499,13 @@ public:
     /// Return maximum number of supported bones for skinning.
     static unsigned GetMaxBones() { return 128; }
 
+    // ATOMIC BEGIN
+    /// Return the current monitor number
+    int GetCurrentMonitor();
+    /// Return the available monitors number
+    int GetMonitorsNumber();
+    // ATOMIC END
+
 private:
     /// Create the application window.
     bool OpenWindow(int width, int height, bool resizable, bool borderless);

+ 12 - 0
Source/Atomic/Graphics/Direct3D9/D3D9Graphics.cpp

@@ -2761,4 +2761,16 @@ void RegisterGraphicsLibrary(Context* context)
     Zone::RegisterObject(context);
 }
 
+// ATOMIC BEGIN
+int Graphics::GetCurrentMonitor()
+{
+    return SDL_GetWindowDisplayIndex((SDL_Window*) this->GetSDLWindow());
+}
+
+int Graphics::GetMonitorsNumber()
+{
+    return SDL_GetNumVideoDisplays();
+}
+// ATOMIC END
+
 }

+ 7 - 0
Source/Atomic/Graphics/Direct3D9/D3D9Graphics.h

@@ -493,6 +493,13 @@ public:
     /// Return maximum number of supported bones for skinning.
     static unsigned GetMaxBones() { return 64; }
 
+    // ATOMIC BEGIN
+    /// Return the current monitor number
+    int GetCurrentMonitor();
+    /// Return the available monitors number
+    int GetMonitorsNumber();
+    // ATOMIC END
+
 private:
     /// Set vertex buffer stream frequency.
     void SetStreamFrequency(unsigned index, unsigned frequency);

+ 12 - 0
Source/Atomic/Graphics/OpenGL/OGLGraphics.cpp

@@ -3296,4 +3296,16 @@ void RegisterGraphicsLibrary(Context* context)
     Zone::RegisterObject(context);
 }
 
+// ATOMIC BEGIN
+int Graphics::GetCurrentMonitor()
+{
+    return SDL_GetWindowDisplayIndex((SDL_Window*) this->GetSDLWindow());
+}
+
+int Graphics::GetMonitorsNumber()
+{
+    return SDL_GetNumVideoDisplays();
+}
+// ATOMIC END
+
 }

+ 7 - 0
Source/Atomic/Graphics/OpenGL/OGLGraphics.h

@@ -525,6 +525,13 @@ public:
     /// Return whether is using an OpenGL 3 context.
     static bool GetGL3Support() { return gl3Support; }
 
+    // ATOMIC BEGIN
+    /// Return the current monitor number
+    int GetCurrentMonitor();
+    /// Return the available monitors number
+    int GetMonitorsNumber();
+    // ATOMIC END
+
 private:
     /// Create the application window icon.
     void CreateWindowIcon();

+ 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();

+ 28 - 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>();
@@ -194,6 +220,7 @@ void UI::Initialize(const String& languageFile)
     SubscribeToEvent(E_KEYUP, HANDLER(UI, HandleKeyUp));
     SubscribeToEvent(E_TEXTINPUT, HANDLER(UI, HandleTextInput));
     SubscribeToEvent(E_UPDATE, HANDLER(UI, HandleUpdate));
+    SubscribeToEvent(E_SCREENMODE, HANDLER(UI, HandleScreenMode));
     SubscribeToEvent(SystemUI::E_CONSOLECLOSED, HANDLER(UI, HandleConsoleClosed));
 
     SubscribeToEvent(E_TOUCHBEGIN, HANDLER(UI, HandleTouchBegin));

+ 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_;
 };
 
 }

+ 16 - 2
Source/AtomicEditor/Application/AEEditorApp.cpp

@@ -92,7 +92,7 @@ void AEEditorApp::Setup()
 
 #ifdef ATOMIC_DEV_BUILD
     engineParameters_["ResourcePrefixPath"] = "";
-    String resourcePaths = env->GetCoreDataDir() + ";" +  env->GetEditorDataDir();
+    String resourcePaths = env->GetCoreDataDir() + ";" + env->GetEditorDataDir();
     // for dev builds, add the compile editor scripts from artifacts
     resourcePaths += ";" + env->GetRootSourceDir() + "Artifacts/Build/Resources/EditorData/";
     engineParameters_["ResourcePaths"] = resourcePaths;
@@ -102,14 +102,28 @@ void AEEditorApp::Setup()
     engineParameters_["ResourcePrefixPath"] = "../Resources";
 
 #else
-	engineParameters_["ResourcePrefixPath"] = filesystem->GetProgramDir() + "Resources";
+    engineParameters_["ResourcePrefixPath"] = filesystem->GetProgramDir() + "Resources";
 #endif
 
     engineParameters_["ResourcePaths"] = "CoreData;EditorData";
 
 #endif // ATOMIC_DEV_BUILD
 
+    String prefsPath = filesystem->GetAppPreferencesDir("AtomicEditor", "Preferences");
+    prefsPath += "prefs.json";
 
+    JSONValue editorWindow;
+
+    if (ReadPreferences(prefsPath, editorWindow, "editorWindow"))
+    {
+        if (editorWindow.IsObject())
+        {
+            engineParameters_["WindowPositionX"] = editorWindow.Get("x").GetUInt();
+            engineParameters_["WindowPositionY"] = editorWindow.Get("y").GetUInt();
+            engineParameters_["WindowWidth"] = editorWindow.Get("width").GetUInt();
+            engineParameters_["WindowHeight"] = editorWindow.Get("height").GetUInt();
+        }
+    }
 }
 
 void AEEditorApp::Stop()

+ 38 - 0
Source/AtomicEditor/Application/AEEditorCommon.cpp

@@ -19,12 +19,16 @@
 #include <ToolCore/ToolSystem.h>
 #include <ToolCore/ToolEnvironment.h>
 
+#include <Atomic/IO/File.h>
+
 #ifdef ATOMIC_DOTNET
 #include <AtomicNET/NETCore/NETHost.h>
 #include <AtomicNET/NETCore/NETCore.h>
 #include <AtomicNET/NETScript/NETScript.h>
 #endif
 
+#include "../Components/EditorComponents.h"
+
 #include "AEEditorCommon.h"
 
 namespace Atomic
@@ -73,6 +77,8 @@ void AEEditorCommon::Setup()
     RegisterEnvironmentLibrary(context_);
 #endif
 
+    RegisterEditorComponentLibrary(context_);
+
 #ifdef ATOMIC_DOTNET
     RegisterNETScriptLibrary(context_);
 #endif
@@ -150,4 +156,36 @@ void AEEditorCommon::Stop()
 #endif
 }
 
+
+bool AEEditorCommon::ReadPreferences(String& path, JSONValue& prefs, const String& propertyName)
+{
+    if (!path.EndsWith(".json"))
+        path.Append(".json");
+
+    SharedPtr<File> file(new File(context_, path, FILE_READ));
+
+    SharedPtr<JSONFile> jsonFile(new JSONFile(context_));
+
+    if (!jsonFile->BeginLoad(*file))
+        return false;
+
+    JSONValue root = jsonFile->GetRoot();
+
+    if (propertyName.Length() > 0)
+    {
+        if (root.Contains(propertyName))
+        {
+            prefs = root.Get(propertyName);
+        }
+    }
+    else
+    {
+        prefs = root;
+    }
+
+    file->Close();
+
+    return true;
+}
+
 }

+ 3 - 1
Source/AtomicEditor/Application/AEEditorCommon.h

@@ -8,6 +8,7 @@
 #pragma once
 
 #include <Atomic/Engine/Application.h>
+#include <Atomic/Resource/JSONFile.h>
 
 using namespace Atomic;
 
@@ -36,11 +37,12 @@ public:
 
 protected:
 
+    bool ReadPreferences(String& path, JSONValue& prefs, const String& propertyName = "");
+    
     SharedPtr<JSVM> vm_;
 
 private:
 
-
 };
 
 }

+ 31 - 0
Source/AtomicEditor/Application/AEPlayerApp.cpp

@@ -31,6 +31,8 @@
 #include "../PlayerMode/AEPlayerMode.h"
 #include <AtomicPlayer/Player.h>
 
+#include "../PlayerMode/AEPlayerEvents.h"
+
 #include "AEPlayerApp.h"
 
 #include <Atomic/DebugNew.h>
@@ -57,6 +59,8 @@ void AEPlayerApplication::Setup()
 {
     AEEditorCommon::Setup();
 
+    engine_->SetAutoExit(false);
+
     FileSystem* filesystem = GetSubsystem<FileSystem>();
 
     engineParameters_["WindowTitle"] = "AtomicPlayer";
@@ -150,6 +154,26 @@ void AEPlayerApplication::Setup()
 #endif
 
             }
+            else if (argument == "--windowposx" && value.Length()) 
+            {
+                engineParameters_["WindowPositionX"] = atoi(value.CString());
+            }
+            else if (argument == "--windowposy" && value.Length())
+            {
+                engineParameters_["WindowPositionY"] = atoi(value.CString());
+            }
+            else if (argument == "--windowwidth" && value.Length())
+            {
+                engineParameters_["WindowWidth"] = atoi(value.CString());
+            }
+            else if (argument == "--windowheight" && value.Length())
+            {
+                engineParameters_["WindowHeight"] = atoi(value.CString());
+            }
+            else if (argument == "--resizable") 
+            {
+                engineParameters_["WindowResizable"] = true;
+            }
         }
     }
 
@@ -204,9 +228,16 @@ void AEPlayerApplication::Start()
         }
     }
 
+    SubscribeToEvent(E_PLAYERQUIT, HANDLER(AEPlayerApplication, HandleQuit));
+
     return;
 }
 
+void AEPlayerApplication::HandleQuit(StringHash eventType, VariantMap& eventData)
+{
+    engine_->Exit();
+}
+
 void AEPlayerApplication::Stop()
 {
     AEEditorCommon::Stop();

+ 2 - 0
Source/AtomicEditor/Application/AEPlayerApp.h

@@ -41,6 +41,8 @@ private:
 
     void HandleLogMessage(StringHash eventType, VariantMap& eventData);
 
+    void HandleQuit(StringHash eventType, VariantMap& eventData);
+
     bool debugPlayer_;
 
 };

+ 1 - 0
Source/AtomicEditor/CMakeLists.txt

@@ -70,3 +70,4 @@ GroupSources("Editors")
 GroupSources("Javascript")
 GroupSources("PlayerMode")
 GroupSources("Utils")
+GroupSources("Components")

+ 324 - 0
Source/AtomicEditor/Components/CubemapGenerator.cpp

@@ -0,0 +1,324 @@
+// Portions Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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/Core/Context.h>
+#include <Atomic/Core/CoreEvents.h>
+
+#include <Atomic/IO/Log.h>
+#include <Atomic/IO/FileSystem.h>
+#include <Atomic/IO/File.h>
+
+#include <Atomic/Graphics/Graphics.h>
+#include <Atomic/Graphics/Camera.h>
+#include <Atomic/Graphics/Viewport.h>
+#include <Atomic/Graphics/Texture2D.h>
+#include <Atomic/Graphics/TextureCube.h>
+
+#include <Atomic/Resource/ResourceCache.h>
+#include <Atomic/Resource/XMLFile.h>
+
+#include <Atomic/Scene/Node.h>
+
+#include <ToolCore/ToolSystem.h>
+#include <ToolCore/Project/Project.h>
+
+#include "../Editors/SceneEditor3D/SceneEditor3DEvents.h"
+#include "../Editors/SceneEditor3D/SceneEditor3D.h"
+
+#include "CubemapGenerator.h"
+
+namespace AtomicEditor
+{
+
+CubemapGenerator::CubemapGenerator(Context *context) : EditorComponent(context),
+    updateCycle_(0),
+    imageSize_(512),
+    namePrefix_("Cubemap")
+{
+
+}
+
+CubemapGenerator::~CubemapGenerator()
+{
+
+}
+
+bool CubemapGenerator::Render()
+{
+
+    if(!InitRender())
+    {
+        LOGERRORF("Unable to init render");
+        return false;
+    }
+
+    GetScene()->SendEvent(E_CUBEMAPRENDERBEGIN);    
+    SubscribeToEvent(E_BEGINFRAME, HANDLER(CubemapGenerator, HandleBeginFrame));
+    SubscribeToEvent(E_ENDFRAME, HANDLER(CubemapGenerator, HandleEndFrame));
+
+    return true;
+
+}
+
+bool CubemapGenerator::InitPaths()
+{
+
+    String scenePath = sceneEditor_->GetFullPath();
+
+    String pathName;
+    String fileName;
+    String ext;
+
+    SplitPath(scenePath, pathName, fileName, ext);
+
+    outputPathAbsolute_ = pathName + "Cubemaps/" + fileName + "/";
+
+    FileSystem* fileSystem = GetSubsystem<FileSystem>();
+
+    if (!fileSystem->DirExists(outputPathAbsolute_))
+    {
+        if (!fileSystem->CreateDirs(pathName,  "Cubemaps/" + fileName + "/"))
+        {
+            LOGERRORF("CubemapGenerator::InitRender - Unable to create path: %s", outputPathAbsolute_.CString());
+            return false;
+        }
+    }
+
+    // TODO: There should be a better way of getting the resource path
+    ToolSystem* tsystem = GetSubsystem<ToolSystem>();
+    Project* project = tsystem->GetProject();
+
+    resourcePath_ = outputPathAbsolute_;
+    resourcePath_.Replace(project->GetResourcePath(), "");
+    resourcePath_ = AddTrailingSlash(resourcePath_);
+
+    return true;
+
+}
+
+bool CubemapGenerator::InitRender()
+{
+
+    sceneEditor_ = GetSceneEditor();
+
+    if (sceneEditor_.Null())
+    {
+        LOGERROR("CubemapGenerator::InitRender - unable to get scene editor");
+        return false;
+    }
+
+    if (!InitPaths())
+        return false;
+
+    cameraNode_ = node_->CreateChild("CubeMapRenderCamera");
+    cameraNode_->SetTemporary(true);
+
+    camera_ = cameraNode_->CreateComponent<Camera>();
+    camera_->SetTemporary(true);
+    camera_->SetFov(90.0f);
+    camera_->SetNearClip(0.0001f);
+    camera_->SetAspectRatio(1.0f);
+
+    RenderPath* renderPath = sceneEditor_->GetSceneView3D()->GetViewport()->GetRenderPath();
+    viewport_ = new Viewport(context_, GetScene(), camera_, renderPath);
+
+    renderImage_ = new Texture2D(context_);
+    renderImage_->SetSize(imageSize_, imageSize_, Graphics::GetRGBAFormat(), TEXTURE_RENDERTARGET);
+
+    renderSurface_ = renderImage_->GetRenderSurface();
+    renderSurface_->SetViewport(0, viewport_);
+    renderSurface_->SetUpdateMode(SURFACE_UPDATEALWAYS);
+
+    return true;
+
+}
+
+void CubemapGenerator::SaveCubemapXML()
+{
+    SharedPtr<XMLFile> xmlFile(new XMLFile(context_));
+    XMLElement rootElem = xmlFile->CreateRoot("cubemap");
+
+    String prefix = resourcePath_ + namePrefix_ + "_";
+
+    String name = prefix + GetFaceName(FACE_POSITIVE_X) + ".png";
+    rootElem.CreateChild("face").SetAttribute("name", name);
+    name = prefix + GetFaceName(FACE_NEGATIVE_X) + ".png";
+    rootElem.CreateChild("face").SetAttribute("name", name);
+    name = prefix + GetFaceName(FACE_POSITIVE_Y) + ".png";
+    rootElem.CreateChild("face").SetAttribute("name", name);
+    name = prefix + GetFaceName(FACE_NEGATIVE_Y) + ".png";
+    rootElem.CreateChild("face").SetAttribute("name", name);
+    name = prefix + GetFaceName(FACE_POSITIVE_Z) + ".png";
+    rootElem.CreateChild("face").SetAttribute("name", name);
+    name = prefix + GetFaceName(FACE_NEGATIVE_Z) + ".png";
+    rootElem.CreateChild("face").SetAttribute("name", name);
+
+    String xmlPath = outputPathAbsolute_ + namePrefix_ + ".xml";
+
+    SharedPtr<File> file(new File(context_, xmlPath, FILE_WRITE));
+    xmlFile->Save(*file, "    ");
+    file->Close();
+
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    TextureCube* texcube = cache->GetResource<TextureCube>(resourcePath_ + namePrefix_ + ".xml");
+    if (texcube)
+        cache->ReloadResource(texcube);
+
+}
+
+void CubemapGenerator::EndRender()
+{
+    UnsubscribeFromEvent(E_BEGINFRAME);
+    UnsubscribeFromEvent(E_ENDFRAME);
+
+    SaveCubemapXML();
+
+    cameraNode_->Remove();
+
+    cameraNode_ = 0;
+    camera_ = 0;
+    viewport_ = 0;
+    renderImage_ = 0;
+    assert(renderSurface_->Refs() == 1);
+    renderSurface_ = 0;
+    updateCycle_ = 0;
+
+    GetScene()->SendEvent(E_CUBEMAPRENDEREND);
+
+}
+
+void CubemapGenerator::HandleBeginFrame(StringHash eventType, VariantMap& eventData)
+{
+    updateCycle_++;
+    if (updateCycle_ < 7)
+        cameraNode_->SetWorldRotation(RotationOf(GetFaceForCycle(updateCycle_)));
+    else
+    {
+        EndRender();
+    }
+}
+
+void CubemapGenerator::HandleEndFrame(StringHash eventType, VariantMap& eventData)
+{
+    SharedPtr<Image> image(GetImage(renderImage_));
+
+    String path = outputPathAbsolute_;
+
+    if (namePrefix_.Length())
+        path += namePrefix_;
+
+    path.AppendWithFormat("_%s.png", GetFaceName(GetFaceForCycle(updateCycle_)).CString());
+
+    image->SavePNG(path);
+
+}
+
+SharedPtr<Image> CubemapGenerator::GetImage(Texture2D* tex2d)
+{
+    Image* rawImage = new Image(tex2d->GetContext());
+
+    const unsigned format = tex2d->GetFormat();
+
+    if (format == Graphics::GetRGBAFormat() || format == Graphics::GetRGBA16Format() || format == Graphics::GetRGBAFloat32Format())
+        rawImage->SetSize(tex2d->GetWidth(), tex2d->GetHeight(), 4);
+    else if (format == Graphics::GetRGBFormat())
+        rawImage->SetSize(tex2d->GetWidth(), tex2d->GetHeight(), 3);
+    else
+        return SharedPtr<Image>();
+
+    tex2d->GetData(0, rawImage->GetData());
+
+    return SharedPtr<Image>(rawImage);
+}
+
+
+CubeMapFace CubemapGenerator::GetFaceForCycle(int cycle)
+{
+    switch (cycle)
+    {
+    case 1:
+        return FACE_POSITIVE_X;
+    case 2:
+        return FACE_POSITIVE_Y;
+    case 3:
+        return FACE_POSITIVE_Z;
+    case 4:
+        return FACE_NEGATIVE_X;
+    case 5:
+        return FACE_NEGATIVE_Y;
+    case 6:
+        return FACE_NEGATIVE_Z;
+    }
+    return FACE_POSITIVE_X;
+
+}
+
+String CubemapGenerator::GetFaceName(CubeMapFace face)
+{
+    switch (face)
+    {
+    case FACE_POSITIVE_X:
+        return "PosX";
+    case FACE_POSITIVE_Y:
+        return "PosY";
+    case FACE_POSITIVE_Z:
+        return "PosZ";
+    case FACE_NEGATIVE_X:
+        return "NegX";
+    case FACE_NEGATIVE_Y:
+        return "NegY";
+    case FACE_NEGATIVE_Z:
+        return "NegZ";
+    default:
+        break;
+
+    }
+    return "PosX";
+}
+
+Quaternion CubemapGenerator::RotationOf(CubeMapFace face)
+{
+    Quaternion result;
+    switch (face)
+    {
+    //  Rotate camera according to probe rotation
+    case FACE_POSITIVE_X:
+        result = Quaternion(0, 90, 0);
+        break;
+    case FACE_NEGATIVE_X:
+        result = Quaternion(0, -90, 0);
+        break;
+    case FACE_POSITIVE_Y:
+        result = Quaternion(-90, 0, 0);
+        break;
+    case FACE_NEGATIVE_Y:
+        result = Quaternion(90, 0, 0);
+        break;
+    case FACE_POSITIVE_Z:
+        result = Quaternion(0, 0, 0);
+        break;
+    case FACE_NEGATIVE_Z:
+        result = Quaternion(0, 180, 0);
+        break;
+    default:
+        break;
+    }
+    return result;
+}
+
+void CubemapGenerator::RegisterObject(Context* context)
+{
+    context->RegisterFactory<CubemapGenerator>();
+
+    ATTRIBUTE("Name Prefix", String, namePrefix_, "Cubemap", AM_DEFAULT);
+    ATTRIBUTE("Image Size", int, imageSize_, 512, AM_DEFAULT);
+
+}
+
+
+}

+ 88 - 0
Source/AtomicEditor/Components/CubemapGenerator.h

@@ -0,0 +1,88 @@
+//
+// 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/Graphics/GraphicsDefs.h>
+
+#include "EditorComponent.h"
+
+namespace Atomic
+{
+class Node;
+class Zone;
+class Camera;
+class Viewport;
+class Texture2D;
+class RenderSurface;
+class RenderPath;
+class Image;
+}
+
+using namespace Atomic;
+
+namespace AtomicEditor
+{
+
+class CubemapGenerator : public EditorComponent
+{
+    OBJECT(CubemapGenerator);
+
+public:
+    /// Construct.
+    CubemapGenerator(Context* context);
+
+    /// Destruct.
+    virtual ~CubemapGenerator();
+
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+
+    bool Render();
+
+    int GetImageSize() const { return imageSize_; }
+    void SetImageSize(int size) { imageSize_ = size; }
+
+protected:
+
+private:
+
+    void HandleBeginFrame(StringHash eventType, VariantMap& eventData);
+    void HandleEndFrame(StringHash eventType, VariantMap& eventData);
+
+    bool InitRender();
+    bool InitPaths();
+    void EndRender();
+
+    void SaveCubemapXML();
+
+    SharedPtr<Image> GetImage(Texture2D* tex2d);
+
+    CubeMapFace GetFaceForCycle(int cycle);
+    String GetFaceName(CubeMapFace face);
+    Quaternion RotationOf(CubeMapFace face);
+
+    SharedPtr<Node> cameraNode_;
+    SharedPtr<Camera> camera_;
+    SharedPtr<Viewport> viewport_;
+    SharedPtr<Texture2D> renderImage_;
+    SharedPtr<RenderSurface> renderSurface_;
+
+    int updateCycle_;
+    int imageSize_;
+
+    String namePrefix_;
+
+    String outputPathAbsolute_;
+    String resourcePath_;
+
+    WeakPtr<SceneEditor3D> sceneEditor_;
+
+};
+
+
+}

+ 45 - 0
Source/AtomicEditor/Components/EditorComponent.cpp

@@ -0,0 +1,45 @@
+//
+// 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/Core/Context.h>
+
+#include <Atomic/Graphics/Camera.h>
+#include <Atomic/Graphics/Viewport.h>
+#include <Atomic/Graphics/Texture2D.h>
+
+#include "../Editors/SceneEditor3D/SceneEditor3D.h"
+
+#include "EditorComponent.h"
+
+namespace AtomicEditor
+{
+
+EditorComponent::EditorComponent(Context *context) : Component(context)
+{
+
+}
+
+EditorComponent::~EditorComponent()
+{
+
+}
+
+void EditorComponent::RegisterObject(Context* context)
+{
+    context->RegisterFactory<EditorComponent>();
+}
+
+SceneEditor3D* EditorComponent::GetSceneEditor()
+{
+    if (!GetScene())
+        return NULL;
+
+    return SceneEditor3D::GetSceneEditor(GetScene());
+
+}
+
+}

+ 43 - 0
Source/AtomicEditor/Components/EditorComponent.h

@@ -0,0 +1,43 @@
+//
+// 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/Scene/Component.h>
+
+using namespace Atomic;
+
+namespace AtomicEditor
+{
+
+class SceneEditor3D;
+
+/// A component that requires the editor, for example CubemapGenerator requires a renderer
+/// IMPORTANT: Other tool related components, which don't require the editor, should be in ToolCore
+class EditorComponent : public Component
+{
+    OBJECT(EditorComponent);
+
+public:
+
+    /// Construct.
+    EditorComponent(Context* context);
+
+    /// Destruct.
+    virtual ~EditorComponent();
+
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+
+protected:
+
+    SceneEditor3D* GetSceneEditor();
+
+
+};
+
+}

+ 15 - 0
Source/AtomicEditor/Components/EditorComponents.cpp

@@ -0,0 +1,15 @@
+#include "EditorComponents.h"
+
+#include "EditorComponent.h"
+#include "CubemapGenerator.h"
+
+namespace AtomicEditor
+{
+
+void RegisterEditorComponentLibrary(Atomic::Context* context)
+{
+    EditorComponent::RegisterObject(context);
+    CubemapGenerator::RegisterObject(context);
+}
+
+}

+ 21 - 0
Source/AtomicEditor/Components/EditorComponents.h

@@ -0,0 +1,21 @@
+//
+// 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
+
+namespace Atomic
+{
+class Context;
+}
+
+namespace AtomicEditor
+{
+
+void RegisterEditorComponentLibrary(Atomic::Context* context);
+
+}
+

+ 4 - 9
Source/AtomicEditor/EditorMode/AEEditorEvents.h

@@ -63,13 +63,13 @@ EVENT(E_EDITORRESOURCEEDITORCHANGED, EditorResourceEditorChanged)
 
 
 // emitted once play has started
-EVENT(E_EDITORPLAYSTARTED, EditorPlayStarted)
+EVENT(E_EDITORPLAYERSTARTED, EditorPlayerStarted)
 {
     PARAM(P_MODE, Mode);    // uint (AEPlayerMode)
 }
 
 // emitted once play has stopped
-EVENT(E_EDITORPLAYSTOPPED, EditorPlayStopped)
+EVENT(E_EDITORPLAYERSTOPPED, EditorPlayerStopped)
 {
 
 }
@@ -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);
 }
 
 

+ 26 - 5
Source/AtomicEditor/EditorMode/AEEditorMode.cpp

@@ -11,6 +11,8 @@
 #include <Atomic/IPC/IPCEvents.h>
 #include <Atomic/IPC/IPCBroker.h>
 
+#include <Atomic/Input/InputEvents.h>
+
 #include <ToolCore/ToolEnvironment.h>
 #include <ToolCore/ToolSystem.h>
 #include <ToolCore/License/LicenseSystem.h>
@@ -20,6 +22,8 @@
 
 #include <Atomic/UI/SystemUI/DebugHud.h>
 
+#include "../PlayerMode/AEPlayerEvents.h"
+
 #include "AEEditorMode.h"
 
 using namespace ToolCore;
@@ -31,6 +35,7 @@ EditorMode::EditorMode(Context* context) :
     Object(context)
 {
     SubscribeToEvent(E_IPCWORKERSTART, HANDLER(EditorMode, HandleIPCWorkerStarted));
+    SubscribeToEvent(E_IPCPLAYEREXITREQUEST, HANDLER(EditorMode, HandleIPCPlayerExitRequest));
 }
 
 EditorMode::~EditorMode()
@@ -55,7 +60,9 @@ void EditorMode::HandleIPCWorkerStarted(StringHash eventType, VariantMap& eventD
 
     playerBroker_->PostMessage(E_IPCINITIALIZE, startupData);
 
-    SendEvent("EditorPlayerStarted");
+    SendEvent(E_EDITORPLAYERSTARTED);
+
+    playerEnabled_ = true;
 
 }
 
@@ -63,8 +70,12 @@ void EditorMode::HandleIPCWorkerExit(StringHash eventType, VariantMap& eventData
 {
     //SendEvent(E_EDITORPLAYSTOP);
 
-    if ( eventData[IPCWorkerExit::P_BROKER] == playerBroker_)
+    if (eventData[IPCWorkerExit::P_BROKER] == playerBroker_) 
+    {
         playerBroker_ = 0;
+        playerEnabled_ = false;
+        SendEvent(E_EDITORPLAYERSTOPPED);
+    }
 }
 
 void EditorMode::HandleIPCWorkerLog(StringHash eventType, VariantMap& eventData)
@@ -87,7 +98,7 @@ void EditorMode::HandleIPCJSError(StringHash eventType, VariantMap& eventData)
 
 }
 
-bool EditorMode::PlayProject(bool debug)
+bool EditorMode::PlayProject(String addArgs, bool debug)
 {
     ToolEnvironment* env = GetSubsystem<ToolEnvironment>();
     ToolSystem* tsystem = GetSubsystem<ToolSystem>();
@@ -120,6 +131,9 @@ bool EditorMode::PlayProject(bool debug)
     if (debug)
         vargs.Insert(0, "--debug");
 
+    if (addArgs.Length() > 0)
+        vargs.Insert(0, addArgs.Split(' '));
+
     String dump;
     dump.Join(vargs, " ");
     LOGINFOF("Launching Broker %s %s", editorBinary.CString(), dump.CString());
@@ -138,9 +152,16 @@ bool EditorMode::PlayProject(bool debug)
 
 }
 
-bool EditorMode::PlayProjectDebug()
+void EditorMode::HandleIPCPlayerExitRequest(StringHash eventType, VariantMap& eventData)
+{
+    if (!playerBroker_) return;
+    VariantMap noEventData;
+    playerBroker_->PostMessage(E_EXITREQUESTED, noEventData);
+}
+
+bool EditorMode::IsPlayerEnabled()
 {
-    return PlayProject(true);
+    return playerEnabled_;
 }
 
 }

+ 5 - 2
Source/AtomicEditor/EditorMode/AEEditorMode.h

@@ -32,8 +32,8 @@ public:
     /// Destruct.
     virtual ~EditorMode();
 
-    bool PlayProject(bool debug = false);
-    bool PlayProjectDebug();
+    bool PlayProject(String addArgs = "", bool debug = false);
+    bool IsPlayerEnabled();
 
 private:
 
@@ -41,9 +41,12 @@ private:
     void HandleIPCJSError(StringHash eventType, VariantMap& eventData);
     void HandleIPCWorkerLog(StringHash eventType, VariantMap& eventData);
     void HandleIPCWorkerExit(StringHash eventType, VariantMap& eventData);
+    void HandleIPCPlayerExitRequest(StringHash eventType, VariantMap& eventData);
 
     SharedPtr<IPCBroker> playerBroker_;
 
+    bool playerEnabled_ = false;
+
 };
 
 }

+ 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();
-};
-
-
-}

+ 213 - 96
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"
 
@@ -42,8 +43,11 @@ using namespace ToolCore;
 namespace AtomicEditor
 {
 
-SceneEditor3D ::SceneEditor3D(Context* context, const String &fullpath, UITabContainer *container) :
-    ResourceEditor(context, fullpath, container)
+Vector<WeakPtr<SceneEditor3D>> SceneEditor3D::sceneEditors_;
+
+SceneEditor3D::SceneEditor3D(Context* context, const String &fullpath, UITabContainer *container) :
+    ResourceEditor(context, fullpath, container),
+    cubemapRenderCount_(0)
 {
 
     // store a local reference to user project prefs
@@ -63,7 +67,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 +101,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,15 +112,16 @@ SceneEditor3D ::SceneEditor3D(Context* context, const String &fullpath, UITabCon
 
     SubscribeToEvent(E_PROJECTUSERPREFSAVED, HANDLER(SceneEditor3D, HandleUserPrefSaved));
 
-    SubscribeToEvent(E_EDITORPLAYSTARTED, HANDLER(SceneEditor3D, HandlePlayStarted));
-    SubscribeToEvent(E_EDITORPLAYSTOPPED, HANDLER(SceneEditor3D, HandlePlayStopped));
-
-    SubscribeToEvent(scene_, E_NODEADDED, HANDLER(SceneEditor3D, HandleNodeAdded));
-    SubscribeToEvent(scene_, E_NODEREMOVED, HANDLER(SceneEditor3D, HandleNodeRemoved));
+    SubscribeToEvent(scene_, E_SCENEEDITNODECREATED, HANDLER(SceneEditor3D, HandleSceneEditNodeCreated));
 
+    SubscribeToEvent(E_EDITORPLAYERSTARTED, HANDLER(SceneEditor3D, HandlePlayStarted));
+    SubscribeToEvent(E_EDITORPLAYERSTOPPED, HANDLER(SceneEditor3D, HandlePlayStopped));
     SubscribeToEvent(scene_, E_SCENEEDITSCENEMODIFIED, HANDLER(SceneEditor3D, HandleSceneEditSceneModified));
 
-    editHistory_ = new SceneEditHistory(context_, scene_);
+    SubscribeToEvent(scene_, E_CUBEMAPRENDERBEGIN, HANDLER(SceneEditor3D, HandleCubemapRenderBegin));
+    SubscribeToEvent(scene_, E_CUBEMAPRENDEREND, HANDLER(SceneEditor3D, HandleCubemapRenderEnd));
+
+    RegisterSceneEditor();
 
 }
 
@@ -130,17 +136,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 +145,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 +202,10 @@ 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);
+    if (!cubemapRenderCount_)
+        gizmo3D_->Update();
 }
 
 void SceneEditor3D::HandlePlayStarted(StringHash eventType, VariantMap& eventData)
@@ -321,6 +267,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,9 +319,32 @@ 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);    
+    SetModified(true);
 }
 
 void SceneEditor3D::HandleUserPrefSaved(StringHash eventType, VariantMap& eventData)
@@ -341,42 +352,148 @@ 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;
+    }
 
-    // TODO: Adjust once multiple selection is in
-    if (selectedNode_.Null())
+    ResourceEditor::InvokeShortcut(shortcut);
+}
+
+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
+
+    Node* parent = newParent;
 
-    // Calculate the combined bounding box of all drawables
-    for (unsigned i = 0; i < drawables.Size(); i++  )
+    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()
+void SceneEditor3D::RegisterSceneEditor()
 {
-    gizmo3D_->SetSnapTranslationX(userPrefs_->GetSnapTranslationX());
-    gizmo3D_->SetSnapTranslationY(userPrefs_->GetSnapTranslationY());
-    gizmo3D_->SetSnapTranslationZ(userPrefs_->GetSnapTranslationZ());
-    gizmo3D_->SetSnapRotation(userPrefs_->GetSnapRotation());
-    gizmo3D_->SetSnapScale(userPrefs_->GetSnapScale());
+    // prune released scene editors
+    for (Vector<WeakPtr<SceneEditor3D> >::Iterator i = sceneEditors_.Begin(); i != sceneEditors_.End();)
+    {
+        if (*i)
+        {
+            ++i;
+        }
+        else
+        {
+            i = sceneEditors_.Erase(i);
+        }
+    }
+
+    sceneEditors_.Push(WeakPtr<SceneEditor3D>(this));
 
 }
 
+SceneEditor3D* SceneEditor3D::GetSceneEditor(Scene* scene)
+{
+
+    for (Vector<WeakPtr<SceneEditor3D> >::Iterator i = sceneEditors_.Begin(); i != sceneEditors_.End();)
+    {
+        if (*i && scene == (*i)->GetScene())
+            return *i;
+
+        i++;
+    }
+
+    return NULL;
+
+}
+
+void SceneEditor3D::HandleCubemapRenderBegin(StringHash eventType, VariantMap& eventData)
+{
+    cubemapRenderCount_++;
+
+    if (cubemapRenderCount_ == 1)
+    {
+        // first time
+        sceneView_->GetDebugRenderer()->SetEnabled(false);
+        gizmo3D_->Hide();
+    }
+
+}
+
+void SceneEditor3D::HandleCubemapRenderEnd(StringHash eventType, VariantMap& eventData)
+{
+    cubemapRenderCount_--;
+
+    if (cubemapRenderCount_ == 0)
+    {
+        // last one
+        sceneView_->GetDebugRenderer()->SetEnabled(true);
+        gizmo3D_->Show();
+    }
+
+}
+
+
 }

+ 29 - 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,19 @@ public:
 
     void Undo();
     void Redo();
+    void Cut();
+    void Copy();
+    void Paste();
 
     ProjectUserPrefs* GetUserPrefs();
 
+    void InvokeShortcut(const String& shortcut);
+
+    static SceneEditor3D* GetSceneEditor(Scene* scene);
+
 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 +93,11 @@ 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 HandleCubemapRenderBegin(StringHash eventType, VariantMap& eventData);
+    void HandleCubemapRenderEnd(StringHash eventType, VariantMap& eventData);
 
     void UpdateGizmoSnapSettings();
 
@@ -94,13 +108,19 @@ private:
 
     SharedPtr<Gizmo3D> gizmo3D_;
 
-    WeakPtr<Node> selectedNode_;
-    SharedPtr<Node> clipboardNode_;
-
+    SharedPtr<SceneSelection> selection_;
     SharedPtr<SceneEditHistory> editHistory_;
 
+    SharedPtr<Node> clipboardNode_;
+
     WeakPtr<ProjectUserPrefs> userPrefs_;
 
+    void RegisterSceneEditor();
+
+    static Vector<WeakPtr<SceneEditor3D>> sceneEditors_;
+
+    int cubemapRenderCount_;
+
 };
 
 }

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

@@ -30,37 +30,115 @@ 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)
+{
+
+}
+
+EVENT(E_CUBEMAPRENDERBEGIN, CubemapRenderBegin)
+{
+
+}
+
+EVENT(E_CUBEMAPRENDEREND, CubemapRenderEnd)
+{
+
+}
 
 }

+ 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_;
+
+};
+
+}

+ 25 - 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;
@@ -695,4 +646,5 @@ void SceneView3D::FrameSelection()
 
 }
 
+
 }

+ 5 - 7
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,14 +70,10 @@ 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 MoveCamera(float timeStep);
 
     WeakPtr<SceneEditor3D> sceneEditor_;
@@ -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_;

+ 36 - 0
Source/AtomicEditor/PlayerMode/AEPlayerEvents.h

@@ -0,0 +1,36 @@
+//
+// 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 AtomicEditor
+{
+
+EVENT(E_IPCPLAYEREXITREQUEST, IPCPlayerExitRequest)
+{
+
+}
+
+EVENT(E_IPCPLAYERWINDOWCHANGED, IPCPlayerWindowChanged)
+{
+    PARAM(P_POSX, PosX);
+    PARAM(P_POSY, PosY);
+    PARAM(P_WIDTH, Width);
+    PARAM(P_HEIGHT, Height);
+    PARAM(P_MONITOR, Monitor);
+}
+
+EVENT(E_PLAYERQUIT, PlayerQuit)
+{
+
+}
+
+}

+ 17 - 0
Source/AtomicEditor/PlayerMode/AEPlayerMode.cpp

@@ -10,6 +10,7 @@
 #include <Atomic/Input/InputEvents.h>
 #include <Atomic/Core/ProcessUtils.h>
 #include <Atomic/Graphics/GraphicsEvents.h>
+#include <Atomic/Graphics/Graphics.h>
 #include <Atomic/Graphics/Camera.h>
 #include <Atomic/UI/SystemUI/DebugHud.h>
 #include <Atomic/UI/SystemUI/SystemUIEvents.h>
@@ -21,6 +22,8 @@
 #include <AtomicJS/Javascript/JSEvents.h>
 #include <AtomicJS/Javascript/JSIPCEvents.h>
 
+#include "AEPlayerEvents.h"
+
 #include "AEPlayerMode.h"
 
 #ifdef ATOMIC_PLATFORM_WINDOWS
@@ -43,6 +46,7 @@ PlayerMode::PlayerMode(Context* context) :
 
     SubscribeToEvent(E_LOGMESSAGE, HANDLER(PlayerMode, HandleLogMessage));
     SubscribeToEvent(E_JSERROR, HANDLER(PlayerMode, HandleJSError));
+    SubscribeToEvent(E_EXITREQUESTED, HANDLER(PlayerMode, HandleExitRequest));
 
     // BEGIN LICENSE MANAGEMENT
     SubscribeToEvent(E_BEGINVIEWRENDER, HANDLER(PlayerMode, HandleViewRender));
@@ -235,5 +239,18 @@ void PlayerMode::HandleViewRender(StringHash eventType, VariantMap& eventData)
 
 }
 
+void PlayerMode::HandleExitRequest(StringHash eventType, VariantMap& eventData)
+{
+    Graphics* graphics = GetSubsystem<Graphics>();
+    using namespace IPCPlayerWindowChanged;
+    VariantMap data;
+    data[P_POSX] = graphics->GetWindowPosition().x_;
+    data[P_POSY] = graphics->GetWindowPosition().y_;
+    data[P_WIDTH] = graphics->GetWidth();
+    data[P_HEIGHT] = graphics->GetHeight();
+    data[P_MONITOR] = graphics->GetCurrentMonitor();
+    ipc_->SendEventToBroker(E_IPCPLAYERWINDOWCHANGED, data);
+    SendEvent(E_PLAYERQUIT);
+}
 
 }

+ 1 - 0
Source/AtomicEditor/PlayerMode/AEPlayerMode.h

@@ -41,6 +41,7 @@ private:
     void HandleLogMessage(StringHash eventType, VariantMap& eventData);
     void HandleIPCInitialize(StringHash eventType, VariantMap& eventData);
     void HandleViewRender(StringHash eventType, VariantMap& eventData);
+    void HandleExitRequest(StringHash eventType, VariantMap& eventData);
 
 // BEGIN LICENSE MANAGEMENT
     void HandleMessageAck(StringHash eventType, VariantMap& eventData);

+ 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);
             }

Some files were not shown because too many files changed in this diff