Browse Source

Merge remote-tracking branch 'upstream/master'

Matt Benic 10 years ago
parent
commit
d58c32938d
90 changed files with 2952 additions and 408 deletions
  1. 10 0
      Build/Scripts/BuildCommon.js
  2. 1 1
      Data/AtomicEditor/ProjectTemplates/Project3D/Resources/Scenes/Scene.scene
  3. 2 2
      Licenses/LICENSE_THIRDPARTY.md
  4. BIN
      Resources/EditorData/AtomicEditor/editor/skin/3d_scale.png
  5. 30 0
      Resources/EditorData/AtomicEditor/editor/ui/snapsettings.tb.txt
  6. 42 9
      Script/AtomicEditor/editor/Editor.ts
  7. 2 4
      Script/AtomicEditor/editor/EditorEvents.ts
  8. 4 0
      Script/AtomicEditor/ui/EditorUI.ts
  9. 20 4
      Script/AtomicEditor/ui/MainToolbar.ts
  10. 2 3
      Script/AtomicEditor/ui/ScriptWidget.ts
  11. 5 8
      Script/AtomicEditor/ui/Shortcuts.ts
  12. 40 3
      Script/AtomicEditor/ui/frames/HierarchyFrame.ts
  13. 50 0
      Script/AtomicEditor/ui/frames/ProjectFrame.ts
  14. 29 18
      Script/AtomicEditor/ui/frames/ResourceFrame.ts
  15. 3 1
      Script/AtomicEditor/ui/frames/inspector/AssemblyInspector.ts
  16. 15 2
      Script/AtomicEditor/ui/frames/inspector/ComponentInspector.ts
  17. 43 5
      Script/AtomicEditor/ui/frames/inspector/DataBinding.ts
  18. 22 19
      Script/AtomicEditor/ui/frames/inspector/InspectorFrame.ts
  19. 3 1
      Script/AtomicEditor/ui/frames/inspector/InspectorWidget.ts
  20. 3 1
      Script/AtomicEditor/ui/frames/inspector/ModelInspector.ts
  21. 127 3
      Script/AtomicEditor/ui/frames/inspector/NodeInspector.ts
  22. 3 1
      Script/AtomicEditor/ui/frames/inspector/PrefabInspector.ts
  23. 1 0
      Script/AtomicEditor/ui/frames/menus/HierarchyFrameMenu.ts
  24. 9 2
      Script/AtomicEditor/ui/frames/menus/MainFrameMenu.ts
  25. 16 0
      Script/AtomicEditor/ui/modal/ModalOps.ts
  26. 79 0
      Script/AtomicEditor/ui/modal/SnapSettingsWindow.ts
  27. 1 1
      Script/AtomicEditor/ui/modal/license/ActivationWindow.ts
  28. 1 1
      Script/AtomicEditor/ui/modal/license/EULAWindow.ts
  29. 5 0
      Script/Packages/Atomic/Core.json
  30. 2 1
      Script/Packages/ToolCore/ToolCore.json
  31. 7 1
      Script/TypeScript/AtomicWork.d.ts
  32. 1 0
      Script/tsconfig.json
  33. 1 1
      Source/Atomic/Core/StringUtils.cpp
  34. 1 1
      Source/Atomic/Core/StringUtils.h
  35. 21 2
      Source/Atomic/UI/UI.cpp
  36. 4 1
      Source/Atomic/UI/UI.h
  37. 3 0
      Source/Atomic/UI/UIDragDrop.h
  38. 6 0
      Source/Atomic/UI/UIEditField.cpp
  39. 24 0
      Source/Atomic/UI/UIEvents.h
  40. 5 0
      Source/Atomic/UI/UIInlineSelect.cpp
  41. 54 37
      Source/Atomic/UI/UIInput.cpp
  42. 28 0
      Source/Atomic/UI/UIListView.cpp
  43. 4 0
      Source/Atomic/UI/UIListView.h
  44. 82 0
      Source/Atomic/UI/UISelectList.cpp
  45. 7 0
      Source/Atomic/UI/UISelectList.h
  46. 32 1
      Source/Atomic/UI/UIWidget.cpp
  47. 2 2
      Source/Atomic/UI/UIWidget.h
  48. 0 6
      Source/AtomicEditor/Application/AEEditorApp.cpp
  49. 7 0
      Source/AtomicEditor/Application/AEPlayerApp.cpp
  50. 3 45
      Source/AtomicEditor/Editors/JSResourceEditor.cpp
  51. 1 5
      Source/AtomicEditor/Editors/JSResourceEditor.h
  52. 69 8
      Source/AtomicEditor/Editors/ResourceEditor.cpp
  53. 10 1
      Source/AtomicEditor/Editors/ResourceEditor.h
  54. 26 0
      Source/AtomicEditor/Editors/ResourceEditorEvents.h
  55. 113 27
      Source/AtomicEditor/Editors/SceneEditor3D/Gizmo3D.cpp
  56. 21 0
      Source/AtomicEditor/Editors/SceneEditor3D/Gizmo3D.h
  57. 138 0
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditHistory.cpp
  58. 56 0
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditHistory.h
  59. 109 0
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditOps.cpp
  60. 118 0
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditOps.h
  61. 135 11
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditor3D.cpp
  62. 27 0
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditor3D.h
  63. 37 2
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditor3DEvents.h
  64. 150 15
      Source/AtomicEditor/Editors/SceneEditor3D/SceneView3D.cpp
  65. 14 1
      Source/AtomicEditor/Editors/SceneEditor3D/SceneView3D.h
  66. 5 1
      Source/AtomicEditor/PlayerMode/AEPlayerMode.cpp
  67. 1 1
      Source/AtomicJS/Javascript/JSPlugin.cpp
  68. 74 18
      Source/ThirdParty/Duktape/duk_config.h
  69. 512 96
      Source/ThirdParty/Duktape/duktape.c
  70. 60 16
      Source/ThirdParty/Duktape/duktape.h
  71. 21 0
      Source/ThirdParty/TurboBadger/tb_editfield.cpp
  72. 2 0
      Source/ThirdParty/TurboBadger/tb_editfield.h
  73. 57 3
      Source/ThirdParty/TurboBadger/tb_inline_select.cpp
  74. 10 1
      Source/ThirdParty/TurboBadger/tb_inline_select.h
  75. 2 1
      Source/ThirdParty/TurboBadger/tb_select.cpp
  76. 7 0
      Source/ThirdParty/TurboBadger/tb_widgets_reader.cpp
  77. 5 0
      Source/ToolCore/Assets/Asset.cpp
  78. 41 3
      Source/ToolCore/Assets/AssetDatabase.cpp
  79. 4 0
      Source/ToolCore/Assets/AssetDatabase.h
  80. 76 0
      Source/ToolCore/Assets/JSONImporter.cpp
  81. 37 0
      Source/ToolCore/Assets/JSONImporter.h
  82. 1 1
      Source/ToolCore/NETTools/NETToolSystem.cpp
  83. 12 0
      Source/ToolCore/Platform/PlatformAndroid.cpp
  84. 1 0
      Source/ToolCore/Project/Project.cpp
  85. 32 0
      Source/ToolCore/Project/ProjectEvents.h
  86. 82 1
      Source/ToolCore/Project/ProjectUserPrefs.cpp
  87. 18 0
      Source/ToolCore/Project/ProjectUserPrefs.h
  88. 0 9
      Source/ToolCore/ToolEvents.h
  89. 5 0
      Source/ToolCore/ToolPrefs.cpp
  90. 1 0
      Source/ToolCore/ToolSystem.cpp

+ 10 - 0
Build/Scripts/BuildCommon.js

@@ -10,23 +10,33 @@ namespace('build', function() {
     async: true
     async: true
   }, function(platform) {
   }, function(platform) {
 
 
+        console.log("1");
+        console.log(atomicRoot);
         process.chdir(atomicRoot);
         process.chdir(atomicRoot);
 
 
+        console.log("2");
         var modules = host.getScriptModules(platform);
         var modules = host.getScriptModules(platform);
         var bindCmd = host.atomicTool + " bind \"" + atomicRoot + "\" ";
         var bindCmd = host.atomicTool + " bind \"" + atomicRoot + "\" ";
 
 
+        console.log("3");
         var cmds = [];
         var cmds = [];
         for (var pkgName in modules) {
         for (var pkgName in modules) {
             cmds.push(bindCmd + "Script/Packages/" + pkgName + "/ " + platform);
             cmds.push(bindCmd + "Script/Packages/" + pkgName + "/ " + platform);
+            console.log(bindCmd + "Script/Packages/" + pkgName + "/ " + platform);
         }
         }
 
 
+        console.log("4");
         // Compile the Editor Scripts
         // Compile the Editor Scripts
+        console.log("os.platform=", os.platform());
+        console.log(atomicRoot + "Build/Windows/node/node.exe " + atomicRoot + "Build/TypeScript/tsc.js -p " + atomicRoot + "Script");
         if (os.platform() == "win32")
         if (os.platform() == "win32")
           cmds.push(atomicRoot + "Build/Windows/node/node.exe " + atomicRoot + "Build/TypeScript/tsc.js -p " + atomicRoot + "Script");
           cmds.push(atomicRoot + "Build/Windows/node/node.exe " + atomicRoot + "Build/TypeScript/tsc.js -p " + atomicRoot + "Script");
         else if (os.platform() == "darwin")
         else if (os.platform() == "darwin")
           cmds.push(atomicRoot + "Build/Mac/node/node " + atomicRoot + "Build/TypeScript/tsc.js -p " + atomicRoot + "Script");
           cmds.push(atomicRoot + "Build/Mac/node/node " + atomicRoot + "Build/TypeScript/tsc.js -p " + atomicRoot + "Script");
 
 
+        console.log("5");
         jake.exec(cmds, function() {
         jake.exec(cmds, function() {
+        console.log("6");
 
 
           complete();
           complete();
 
 

+ 1 - 1
Data/AtomicEditor/ProjectTemplates/Project3D/Resources/Scenes/Scene.scene

@@ -61,7 +61,7 @@
 		<attribute name="Scale" value="1 1 1" />
 		<attribute name="Scale" value="1 1 1" />
 		<attribute name="Variables" />
 		<attribute name="Variables" />
 		<component type="StaticModel" id="1975">
 		<component type="StaticModel" id="1975">
-			<attribute name="Model" value="Model;Cache/2788364e8ff6f81f4b566a9d58e3fb5b.mdl" />
+			<attribute name="Model" value="Model;2788364e8ff6f81f4b566a9d58e3fb5b.mdl" />
 			<attribute name="Material" value="Material;Models/Materials/DefaultMaterial.material" />
 			<attribute name="Material" value="Material;Models/Materials/DefaultMaterial.material" />
 		</component>
 		</component>
 		<component type="JSComponent" id="1976">
 		<component type="JSComponent" id="1976">

+ 2 - 2
Licenses/LICENSE_THIRDPARTY.md

@@ -5,9 +5,9 @@ This file contains information for third party technology used in the Atomic Gam
 
 
 Please see the The Atomic Game Engine™ Runtime EULA & Atomic Game Engine™ Editor and Tools EULA for additional licensing information.
 Please see the The Atomic Game Engine™ Runtime EULA & Atomic Game Engine™ Editor and Tools EULA for additional licensing information.
 
 
-#### [The Atomic Game Engine™ Runtime EULA (MIT)](https://github.com/AtomicGameEngine/AtomicRuntime/blob/master/LICENSE_ATOMIC_RUNTIME.md)
+#### [The Atomic Game Engine™ Runtime EULA (MIT)](https://github.com/AtomicGameEngine/AtomicRuntime/blob/master/Licenses/LICENSE_ATOMIC_RUNTIME.md)
 
 
-#### [The Atomic Game Engine™ Editor and Tools EULA](https://github.com/AtomicGameEngine/AtomicGameEngine/blob/master/LICENSE_ATOMIC_EDITOR_AND_TOOLS.md)
+#### [The Atomic Game Engine™ Editor and Tools EULA](https://github.com/AtomicGameEngine/AtomicGameEngine/blob/master/Licenses/LICENSE_ATOMIC_EDITOR_AND_TOOLS.md)
 
 
 ### Third Party Licenses
 ### Third Party Licenses
 -------------------------------------------------------------------
 -------------------------------------------------------------------

BIN
Resources/EditorData/AtomicEditor/editor/skin/3d_scale.png


+ 30 - 0
Resources/EditorData/AtomicEditor/editor/ui/snapsettings.tb.txt

@@ -0,0 +1,30 @@
+TBLayout: axis: y, distribution: gravity, position: left
+	TBLayout: distribution: gravity
+		TBTextField: text: "Translate X:"
+		TBLayout: gravity: left right, distribution-position: right bottom
+			TBEditField: id: trans_x, autofocus: 1, text-align: center
+				lp: min-width: 100
+	TBLayout: distribution: gravity
+		TBTextField: text: "Translate Y:"
+		TBLayout: gravity: left right, distribution-position: right bottom
+			TBEditField: id: trans_y, text-align: center
+				lp: min-width: 100
+	TBLayout: distribution: gravity
+		TBTextField: text: "Translate Z:"
+		TBLayout: gravity: left right, distribution-position: right bottom
+			TBEditField: id: trans_z, text-align: center
+				lp: min-width: 100
+	TBLayout: distribution: gravity
+		TBTextField: text: "Rotation:"
+		TBLayout: gravity: left right, distribution-position: right bottom
+			TBEditField: id: rotation, text-align: center
+				lp: min-width: 100
+	TBLayout: distribution: gravity
+		TBTextField: text: "Scale:"
+		TBLayout: gravity: left right, distribution-position: right bottom
+			TBEditField: id: scale, text-align: center
+				lp: min-width: 100
+	TBSeparator: gravity: left right, skin: AESeparator
+	TBLayout: 
+		TBButton: text: Apply, id: apply
+		TBButton: text: Cancel, id: cancel

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

@@ -20,6 +20,9 @@ class Editor extends Atomic.ScriptObject {
 
 
     static instance: Editor;
     static instance: Editor;
 
 
+    projectCloseRequested: boolean;
+    exitRequested: boolean;
+
     constructor() {
     constructor() {
 
 
         super();
         super();
@@ -27,6 +30,8 @@ class Editor extends Atomic.ScriptObject {
         // limit the framerate to limit CPU usage
         // limit the framerate to limit CPU usage
         Atomic.getEngine().maxFps = 60;
         Atomic.getEngine().maxFps = 60;
 
 
+        Atomic.getEngine().autoExit = false;
+
         Editor.instance = this;
         Editor.instance = this;
 
 
         this.initUI();
         this.initUI();
@@ -47,7 +52,7 @@ class Editor extends Atomic.ScriptObject {
             Atomic.graphics.windowTitle = "AtomicEditor";
             Atomic.graphics.windowTitle = "AtomicEditor";
             this.handleProjectUnloaded(data)
             this.handleProjectUnloaded(data)
         });
         });
-        this.subscribeToEvent(EditorEvents.Quit, (data) => this.handleEditorEventQuit(data));
+
         this.subscribeToEvent("ExitRequested", (data) => this.handleExitRequested(data));
         this.subscribeToEvent("ExitRequested", (data) => this.handleExitRequested(data));
 
 
         this.subscribeToEvent("ProjectLoaded", (data) => {
         this.subscribeToEvent("ProjectLoaded", (data) => {
@@ -55,6 +60,14 @@ class Editor extends Atomic.ScriptObject {
             Preferences.getInstance().registerRecentProject(data.projectPath);
             Preferences.getInstance().registerRecentProject(data.projectPath);
         });
         });
 
 
+        this.subscribeToEvent("EditorResourceCloseCanceled", (data) => {
+            //if user canceled closing the resource, then user has changes that he doesn't want to lose
+            //so cancel exit/project close request and unsubscribe from event to avoid closing all the editors again
+            this.exitRequested = false;
+            this.projectCloseRequested = false;
+            this.unsubscribeFromEvent(EditorEvents.EditorResourceClose);
+        });
+
         this.parseArguments();
         this.parseArguments();
     }
     }
 
 
@@ -82,7 +95,29 @@ class Editor extends Atomic.ScriptObject {
         return system.loadProject(event.path);
         return system.loadProject(event.path);
     }
     }
 
 
+    closeAllResourceEditors() {
+        var editor = EditorUI.getCurrentResourceEditor();
+        if (!editor) {
+          if (this.exitRequested) {
+              this.exit();
+          } else if (this.projectCloseRequested) {
+              this.closeProject();
+          }
+          return;
+        }
+        //wait when we close resource editor to check another resource editor for unsaved changes and close it
+        this.subscribeToEvent(EditorEvents.EditorResourceClose, (data) => {
+            this.closeAllResourceEditors();
+        });
+        editor.requestClose();
+    }
+
     handleEditorCloseProject(event) {
     handleEditorCloseProject(event) {
+        this.projectCloseRequested = true;
+        this.closeAllResourceEditors();
+    }
+
+    closeProject() {
         var system = ToolCore.getToolSystem();
         var system = ToolCore.getToolSystem();
 
 
         if (system.project) {
         if (system.project) {
@@ -90,7 +125,6 @@ class Editor extends Atomic.ScriptObject {
             system.closeProject();
             system.closeProject();
 
 
         }
         }
-
     }
     }
 
 
     handleProjectUnloaded(event) {
     handleProjectUnloaded(event) {
@@ -123,15 +157,14 @@ class Editor extends Atomic.ScriptObject {
 
 
     // event handling
     // event handling
     handleExitRequested(data) {
     handleExitRequested(data) {
-        Preferences.getInstance().write();
-        EditorUI.shutdown();
-
+        this.exitRequested = true;
+        this.closeAllResourceEditors();
     }
     }
 
 
-    handleEditorEventQuit(data) {
-
-        this.sendEvent("ExitRequested");
-
+    exit() {
+        Preferences.getInstance().write();
+        EditorUI.shutdown();
+        Atomic.getEngine().exit();
     }
     }
 
 
 
 

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

@@ -5,8 +5,6 @@
 // license information: https://github.com/AtomicGameEngine/AtomicGameEngine
 // license information: https://github.com/AtomicGameEngine/AtomicGameEngine
 //
 //
 
 
-export const Quit = "EditorEventQuit";
-
 export const ModalError = "ModalError";
 export const ModalError = "ModalError";
 export interface ModalErrorEvent {
 export interface ModalErrorEvent {
 
 
@@ -62,8 +60,8 @@ export interface SaveResourceEvent {
 
 
 }
 }
 
 
-export const CloseResource = "EditorCloseResource";
-export interface CloseResourceEvent {
+export const EditorResourceClose = "EditorResourceClose";
+export interface EditorCloseResourceEvent {
 
 
   editor:Editor.ResourceEditor;
   editor:Editor.ResourceEditor;
   navigateToAvailableResource:boolean;
   navigateToAvailableResource:boolean;

+ 4 - 0
Script/AtomicEditor/ui/EditorUI.ts

@@ -48,6 +48,10 @@ export function showModalError(windowText:string, message:string) {
   editorUI.showModalError(windowText, message);
   editorUI.showModalError(windowText, message);
 }
 }
 
 
+export function getCurrentResourceEditor():Editor.ResourceEditor {
+    return getMainFrame().resourceframe.currentResourceEditor;
+}
+
 
 
 class EditorUI extends Atomic.ScriptObject {
 class EditorUI extends Atomic.ScriptObject {
 
 

+ 20 - 4
Script/AtomicEditor/ui/MainToolbar.ts

@@ -12,6 +12,7 @@ class MainToolbar extends Atomic.UIWidget {
     translateButton: Atomic.UIButton;
     translateButton: Atomic.UIButton;
     rotateButton: Atomic.UIButton;
     rotateButton: Atomic.UIButton;
     scaleButton: Atomic.UIButton;
     scaleButton: Atomic.UIButton;
+    axisButton: Atomic.UIButton;
 
 
     constructor(parent: Atomic.UIWidget) {
     constructor(parent: Atomic.UIWidget) {
 
 
@@ -23,15 +24,32 @@ class MainToolbar extends Atomic.UIWidget {
         this.rotateButton = <Atomic.UIButton>this.getWidget("3d_rotate");
         this.rotateButton = <Atomic.UIButton>this.getWidget("3d_rotate");
         this.scaleButton = <Atomic.UIButton>this.getWidget("3d_scale");
         this.scaleButton = <Atomic.UIButton>this.getWidget("3d_scale");
 
 
+        this.axisButton = <Atomic.UIButton>this.getWidget("3d_axismode");
+
         this.translateButton.value = 1;
         this.translateButton.value = 1;
 
 
         parent.addChild(this);
         parent.addChild(this);
 
 
+        this.subscribeToEvent("GizmoAxisModeChanged", (ev) => this.handleGizmoAxisModeChanged(ev));
         this.subscribeToEvent("GizmoEditModeChanged", (ev) => this.handleGizmoEditModeChanged(ev));
         this.subscribeToEvent("GizmoEditModeChanged", (ev) => this.handleGizmoEditModeChanged(ev));
         this.subscribeToEvent(this, "WidgetEvent", (data) => this.handleWidgetEvent(data));
         this.subscribeToEvent(this, "WidgetEvent", (data) => this.handleWidgetEvent(data));
     }
     }
 
 
-    handleGizmoEditModeChanged(ev) {
+    handleGizmoAxisModeChanged(ev: Editor.GizmoAxisModeChangedEvent) {
+
+        if (ev.toggle) return;
+
+        if (ev.mode == Editor.AXIS_WORLD) {
+            this.axisButton.value = 1;
+            this.axisButton.text = "World";
+        } else {
+            this.axisButton.value = 0;
+            this.axisButton.text = "Local";
+        }
+
+    }
+
+    handleGizmoEditModeChanged(ev: Editor.GizmoEditModeChangedEvent) {
 
 
         this.translateButton.value = 0;
         this.translateButton.value = 0;
         this.rotateButton.value = 0;
         this.rotateButton.value = 0;
@@ -69,9 +87,7 @@ class MainToolbar extends Atomic.UIWidget {
 
 
             } else if (ev.target.id == "3d_axismode") {
             } else if (ev.target.id == "3d_axismode") {
 
 
-                ev.target.text = ev.target.value ? "World" : "Local";
-
-                EditorUI.getShortcuts().invokeGizmoAxisModeChanged( ev.target.value ? Editor.AXIS_WORLD :  Editor.AXIS_LOCAL);
+                EditorUI.getShortcuts().invokeGizmoAxisModeChanged(ev.target.value ? Editor.AXIS_WORLD : Editor.AXIS_LOCAL);
                 return true;
                 return true;
 
 
             } else if (ev.target.id == "maintoolbar_play") {
             } else if (ev.target.id == "maintoolbar_play") {

+ 2 - 3
Script/AtomicEditor/ui/ScriptWidget.ts

@@ -25,12 +25,11 @@ class ScriptWidget extends Atomic.UIWidget {
 
 
     }
     }
 
 
-    handleWidgetEvent(ev: Atomic.UIWidgetEvent): void {
+    handleWidgetEvent(ev: Atomic.UIWidgetEvent): boolean {
 
 
         if (ev.type == Atomic.UI_EVENT_TYPE_CLICK) {
         if (ev.type == Atomic.UI_EVENT_TYPE_CLICK) {
 
 
-            this.onEventClick(ev.target, ev.refid);
-            return;
+            return this.onEventClick(ev.target, ev.refid);
 
 
         }
         }
 
 

+ 5 - 8
Script/AtomicEditor/ui/Shortcuts.ts

@@ -49,12 +49,7 @@ class Shortcuts extends Atomic.ScriptObject {
     }
     }
 
 
     invokeFileClose() {
     invokeFileClose() {
-
-        // pretty gross
-        var editor = EditorUI.getMainFrame().resourceframe.currentResourceEditor;
-        if (!editor) return;
-        editor.close(true);
-
+        this.invokeResourceFrameShortcut("close");
     }
     }
 
 
     invokeFileSave() {
     invokeFileSave() {
@@ -91,9 +86,9 @@ class Shortcuts extends Atomic.ScriptObject {
 
 
     }
     }
 
 
-    invokeGizmoAxisModeChanged(mode:Editor.AxisMode) {
+    invokeGizmoAxisModeChanged(mode:Editor.AxisMode, toggle:boolean = false) {
 
 
-        this.sendEvent("GizmoAxisModeChanged", { mode: mode });
+        this.sendEvent("GizmoAxisModeChanged", { mode: mode, toggle: toggle });
 
 
     }
     }
 
 
@@ -118,6 +113,8 @@ class Shortcuts extends Atomic.ScriptObject {
               this.invokeGizmoEditModeChanged(Editor.EDIT_ROTATE);
               this.invokeGizmoEditModeChanged(Editor.EDIT_ROTATE);
             } else if (ev.key == Atomic.KEY_R) {
             } else if (ev.key == Atomic.KEY_R) {
                 this.invokeGizmoEditModeChanged(Editor.EDIT_SCALE);
                 this.invokeGizmoEditModeChanged(Editor.EDIT_SCALE);
+            } else if (ev.key == Atomic.KEY_X) {
+                this.invokeGizmoAxisModeChanged(Editor.AXIS_WORLD, true);
             }
             }
 
 
         }
         }

+ 40 - 3
Script/AtomicEditor/ui/frames/HierarchyFrame.ts

@@ -8,6 +8,7 @@
 import HierarchyFrameMenu = require("./menus/HierarchyFrameMenu");
 import HierarchyFrameMenu = require("./menus/HierarchyFrameMenu");
 import MenuItemSources = require("./menus/MenuItemSources");
 import MenuItemSources = require("./menus/MenuItemSources");
 import EditorEvents = require("editor/EditorEvents");
 import EditorEvents = require("editor/EditorEvents");
+import EditorUI = require("ui/EditorUI");
 
 
 var IconTemporary = "ComponentBitmap";
 var IconTemporary = "ComponentBitmap";
 
 
@@ -136,7 +137,6 @@ class HierarchyFrame extends Atomic.UIWidget {
         var childItemID = this.recursiveAddNode(parentID, node);
         var childItemID = this.recursiveAddNode(parentID, node);
 
 
         this.nodeIDToItemID[node.id] = childItemID;
         this.nodeIDToItemID[node.id] = childItemID;
-
     }
     }
 
 
     handleNodeRemoved(ev: Atomic.NodeRemovedEvent) {
     handleNodeRemoved(ev: Atomic.NodeRemovedEvent) {
@@ -197,6 +197,44 @@ class HierarchyFrame extends Atomic.UIWidget {
 
 
         if (data.type == Atomic.UI_EVENT_TYPE_KEY_UP) {
         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];
+
+                if (!this.hierList.getExpanded(itemNodeId) && this.hierList.getExpandable(itemNodeId)) {
+                    this.hierList.setExpanded(itemNodeId, true);
+                    this.hierList.rootList.invalidateList();
+                } 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
             // node deletion
             if (data.key == Atomic.KEY_DELETE || data.key == Atomic.KEY_BACKSPACE) {
             if (data.key == Atomic.KEY_DELETE || data.key == Atomic.KEY_BACKSPACE) {
 
 
@@ -204,8 +242,7 @@ class HierarchyFrame extends Atomic.UIWidget {
 
 
                 var node = this.scene.getNode(selectedId);
                 var node = this.scene.getNode(selectedId);
                 if (node) {
                 if (node) {
-
-                    node.removeAllComponents();
+                    this.scene.sendEvent("SceneEditNodeAddedRemoved", { scene:this.scene, node:node, added:false});
                     node.remove();
                     node.remove();
 
 
                 }
                 }

+ 50 - 0
Script/AtomicEditor/ui/frames/ProjectFrame.ts

@@ -136,6 +136,56 @@ class ProjectFrame extends ScriptWidget {
 
 
     handleWidgetEvent(data: Atomic.UIWidgetEvent): boolean {
     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) {
         if (data.type == Atomic.UI_EVENT_TYPE_RIGHT_POINTER_UP) {
 
 
             var id = data.target.id;
             var id = data.target.id;

+ 29 - 18
Script/AtomicEditor/ui/frames/ResourceFrame.ts

@@ -21,6 +21,7 @@ class ResourceFrame extends ScriptWidget {
     resourceLayout: Atomic.UILayout;
     resourceLayout: Atomic.UILayout;
     resourceViewContainer: Atomic.UILayout;
     resourceViewContainer: Atomic.UILayout;
     currentResourceEditor: Editor.ResourceEditor;
     currentResourceEditor: Editor.ResourceEditor;
+    wasClosed: boolean;
 
 
     // editors have a rootCotentWidget which is what is a child of the tab container
     // editors have a rootCotentWidget which is what is a child of the tab container
 
 
@@ -37,8 +38,9 @@ class ResourceFrame extends ScriptWidget {
 
 
     handleSaveResource(ev: EditorEvents.SaveResourceEvent) {
     handleSaveResource(ev: EditorEvents.SaveResourceEvent) {
 
 
-        if (this.currentResourceEditor)
+        if (this.currentResourceEditor){
             this.currentResourceEditor.save();
             this.currentResourceEditor.save();
+        }
 
 
     }
     }
 
 
@@ -66,7 +68,7 @@ class ResourceFrame extends ScriptWidget {
 
 
         var editor: Editor.ResourceEditor = null;
         var editor: Editor.ResourceEditor = null;
 
 
-        if (ext == ".js" || ext == ".txt") {
+        if (ext == ".js" || ext == ".txt" || ext == ".json") {
 
 
             editor = new Editor.JSResourceEditor(path, this.tabcontainer);
             editor = new Editor.JSResourceEditor(path, this.tabcontainer);
 
 
@@ -93,6 +95,7 @@ class ResourceFrame extends ScriptWidget {
     }
     }
 
 
     navigateToResource(fullpath: string, lineNumber = -1, tokenPos: number = -1) {
     navigateToResource(fullpath: string, lineNumber = -1, tokenPos: number = -1) {
+        if (this.wasClosed) return;
 
 
         if (!this.editors[fullpath]) {
         if (!this.editors[fullpath]) {
             return;
             return;
@@ -132,19 +135,20 @@ class ResourceFrame extends ScriptWidget {
 
 
     }
     }
 
 
-    handleCloseResource(ev: EditorEvents.CloseResourceEvent) {
-
+    handleCloseResource(ev: EditorEvents.EditorCloseResourceEvent) {
+        this.wasClosed = false;
         var editor = ev.editor;
         var editor = ev.editor;
         var navigate = ev.navigateToAvailableResource;
         var navigate = ev.navigateToAvailableResource;
 
 
         if (!editor)
         if (!editor)
             return;
             return;
 
 
-        if (this.currentResourceEditor == editor)
-            this.currentResourceEditor = null;
-
         editor.unsubscribeFromAllEvents();
         editor.unsubscribeFromAllEvents();
 
 
+        var editors = Object.keys(this.editors);
+
+        var closedIndex = editors.indexOf(editor.fullPath);
+
         // remove from lookup
         // remove from lookup
         delete this.editors[editor.fullPath];
         delete this.editors[editor.fullPath];
 
 
@@ -152,18 +156,21 @@ class ResourceFrame extends ScriptWidget {
 
 
         root.removeChild(editor.rootContentWidget);
         root.removeChild(editor.rootContentWidget);
 
 
-        this.tabcontainer.currentPage = -1;
+        if (editor != this.currentResourceEditor) {
+            this.wasClosed = true;
+            return;
+        } else {
+            this.currentResourceEditor = null;
+            this.tabcontainer.currentPage = -1;
+        }
 
 
         if (navigate) {
         if (navigate) {
-
-            var keys = Object.keys(this.editors);
-
-            if (keys.length) {
-
-                this.navigateToResource(keys[keys.length - 1]);
-
+            var nextEditor = editors[closedIndex+1];
+            if (nextEditor) {
+                this.navigateToResource(nextEditor);
+            } else {
+                this.navigateToResource(editors[closedIndex-1]);
             }
             }
-
         }
         }
 
 
     }
     }
@@ -198,6 +205,10 @@ class ResourceFrame extends ScriptWidget {
 
 
         }
         }
 
 
+        if (ev.type == Atomic.UI_EVENT_TYPE_POINTER_UP) {
+            this.wasClosed = false;
+        }
+
         // bubble
         // bubble
         return false;
         return false;
 
 
@@ -208,7 +219,7 @@ class ResourceFrame extends ScriptWidget {
         // on exit close all open editors
         // on exit close all open editors
         for (var path in this.editors) {
         for (var path in this.editors) {
 
 
-            this.sendEvent(EditorEvents.CloseResource, { editor: this.editors[path], navigateToAvailableResource: false });
+            this.sendEvent(EditorEvents.EditorResourceClose, { editor: this.editors[path], navigateToAvailableResource: false });
 
 
         }
         }
 
 
@@ -240,7 +251,7 @@ class ResourceFrame extends ScriptWidget {
         this.subscribeToEvent(EditorEvents.EditResource, (data) => this.handleEditResource(data));
         this.subscribeToEvent(EditorEvents.EditResource, (data) => this.handleEditResource(data));
         this.subscribeToEvent(EditorEvents.SaveResource, (data) => this.handleSaveResource(data));
         this.subscribeToEvent(EditorEvents.SaveResource, (data) => this.handleSaveResource(data));
         this.subscribeToEvent(EditorEvents.SaveAllResources, (data) => this.handleSaveAllResources(data));
         this.subscribeToEvent(EditorEvents.SaveAllResources, (data) => this.handleSaveAllResources(data));
-        this.subscribeToEvent(EditorEvents.CloseResource, (ev: EditorEvents.CloseResourceEvent) => this.handleCloseResource(ev));
+        this.subscribeToEvent(EditorEvents.EditorResourceClose, (ev: EditorEvents.EditorCloseResourceEvent) => this.handleCloseResource(ev));
 
 
         this.subscribeToEvent(UIEvents.ResourceEditorChanged, (data) => this.handleResourceEditorChanged(data));
         this.subscribeToEvent(UIEvents.ResourceEditorChanged, (data) => this.handleResourceEditorChanged(data));
 
 

+ 3 - 1
Script/AtomicEditor/ui/frames/inspector/AssemblyInspector.ts

@@ -19,7 +19,9 @@ class AssemblyInspector extends InspectorWidget {
 
 
     }
     }
 
 
-    handleWidgetEvent(ev: Atomic.UIWidgetEvent) {
+    handleWidgetEvent(ev: Atomic.UIWidgetEvent):boolean {
+
+      return false;
 
 
     }
     }
 
 

+ 15 - 2
Script/AtomicEditor/ui/frames/inspector/ComponentInspector.ts

@@ -18,7 +18,6 @@ class ComponentInspector extends Atomic.UISection {
         super();
         super();
 
 
         this.subscribeToEvent("WidgetEvent", (data) => this.handleWidgetEvent(data));
         this.subscribeToEvent("WidgetEvent", (data) => this.handleWidgetEvent(data));
-
     }
     }
 
 
     handleWidgetEvent(ev: Atomic.UIWidgetEvent) {
     handleWidgetEvent(ev: Atomic.UIWidgetEvent) {
@@ -94,8 +93,12 @@ class ComponentInspector extends Atomic.UISection {
     inspect(component: Atomic.Component) {
     inspect(component: Atomic.Component) {
 
 
         this.component = component;
         this.component = component;
+
         this.text = component.typeName;
         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
         // For JSComponents append the filename
         if (component.typeName == "JSComponent") {
         if (component.typeName == "JSComponent") {
 
 
@@ -211,7 +214,7 @@ class ComponentInspector extends Atomic.UISection {
 
 
             // refresh entire inspector, fix this...
             // refresh entire inspector, fix this...
             this.sendEvent("EditorActiveNodeChange", { node: node });
             this.sendEvent("EditorActiveNodeChange", { node: node });
-
+            
             return true;
             return true;
 
 
         }
         }
@@ -293,6 +296,16 @@ class ComponentInspector extends Atomic.UISection {
 
 
     }
     }
 
 
+    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
     // Move these to a mixing class
 
 
     addPrefabUI(layout: Atomic.UILayout) {
     addPrefabUI(layout: Atomic.UILayout) {

+ 43 - 5
Script/AtomicEditor/ui/frames/inspector/DataBinding.ts

@@ -26,6 +26,8 @@ class DataBinding {
         fd.id = "Vera";
         fd.id = "Vera";
         fd.size = 11;
         fd.size = 11;
 
 
+        var editWidgets: Array<Atomic.UIWidget> = [];
+
         var enumSource = null;
         var enumSource = null;
 
 
         if (attrInfo.type == Atomic.VAR_BOOL) {
         if (attrInfo.type == Atomic.VAR_BOOL) {
@@ -65,7 +67,7 @@ class DataBinding {
                 var lp = new Atomic.UILayoutParams();
                 var lp = new Atomic.UILayoutParams();
                 lp.width = 140;
                 lp.width = 140;
                 field.layoutParams = lp;
                 field.layoutParams = lp;
-
+                editWidgets.push(field);
                 widget = field;
                 widget = field;
             }
             }
 
 
@@ -79,6 +81,7 @@ class DataBinding {
             var lp = new Atomic.UILayoutParams();
             var lp = new Atomic.UILayoutParams();
             lp.width = 140;
             lp.width = 140;
             field.layoutParams = lp;
             field.layoutParams = lp;
+            editWidgets.push(field);
 
 
             widget = field;
             widget = field;
 
 
@@ -92,7 +95,7 @@ class DataBinding {
             var lp = new Atomic.UILayoutParams();
             var lp = new Atomic.UILayoutParams();
             lp.width = 140;
             lp.width = 140;
             field.layoutParams = lp;
             field.layoutParams = lp;
-
+            editWidgets.push(field);
             widget = field;
             widget = field;
         }
         }
         else if (attrInfo.type == Atomic.VAR_VECTOR3 || attrInfo.type == Atomic.VAR_QUATERNION) {
         else if (attrInfo.type == Atomic.VAR_VECTOR3 || attrInfo.type == Atomic.VAR_QUATERNION) {
@@ -105,6 +108,7 @@ class DataBinding {
 
 
             for (var i: any = 0; i < 3; i++) {
             for (var i: any = 0; i < 3; i++) {
                 var select = new Atomic.UIInlineSelect();
                 var select = new Atomic.UIInlineSelect();
+                editWidgets.push(select);
                 select.id = String(i + 1);
                 select.id = String(i + 1);
                 select.fontDescription = fd;
                 select.fontDescription = fd;
                 select.skinBg = "InspectorVectorAttrName";
                 select.skinBg = "InspectorVectorAttrName";
@@ -127,6 +131,7 @@ class DataBinding {
             for (var i: any = 0; i < 4; i++) {
             for (var i: any = 0; i < 4; i++) {
 
 
                 var select = new Atomic.UIInlineSelect();
                 var select = new Atomic.UIInlineSelect();
+                editWidgets.push(select);
                 select.id = String(i + 1);
                 select.id = String(i + 1);
                 select.fontDescription = fd;
                 select.fontDescription = fd;
                 select.setLimits(-10000000, 10000000);
                 select.setLimits(-10000000, 10000000);
@@ -144,6 +149,7 @@ class DataBinding {
 
 
             for (var i: any = 0; i < 2; i++) {
             for (var i: any = 0; i < 2; i++) {
                 var select = new Atomic.UIInlineSelect();
                 var select = new Atomic.UIInlineSelect();
+                editWidgets.push(select);
                 select.id = String(i + 1);
                 select.id = String(i + 1);
                 select.fontDescription = fd;
                 select.fontDescription = fd;
                 select.skinBg = "InspectorVectorAttrName";
                 select.skinBg = "InspectorVectorAttrName";
@@ -214,7 +220,7 @@ class DataBinding {
 
 
                         if (dragObject.object && dragObject.object.typeName == "Asset") {
                         if (dragObject.object && dragObject.object.typeName == "Asset") {
 
 
-                            var asset = <ToolCore.Asset> dragObject.object;
+                            var asset = <ToolCore.Asset>dragObject.object;
 
 
                             if (asset.importerTypeName == importerName) {
                             if (asset.importerTypeName == importerName) {
                                 importer = asset.importer;
                                 importer = asset.importer;
@@ -250,6 +256,11 @@ class DataBinding {
 
 
             var binding = new DataBinding(object, attrInfo, widget);
             var binding = new DataBinding(object, attrInfo, widget);
             binding.enumSource = enumSource;
             binding.enumSource = enumSource;
+
+            for (var i in editWidgets) {
+                editWidgets[i].subscribeToEvent(editWidgets[i], "UIWidgetEditComplete", (ev) => binding.handleUIWidgetEditCompleteEvent(ev));
+            }
+
             return binding;
             return binding;
 
 
         }
         }
@@ -340,13 +351,13 @@ class DataBinding {
         } else if (attrInfo.type == Atomic.VAR_RESOURCEREF && attrInfo.resourceTypeName) {
         } else if (attrInfo.type == Atomic.VAR_RESOURCEREF && attrInfo.resourceTypeName) {
 
 
             // for cached resources, use the asset name, otherwise use the resource path name
             // for cached resources, use the asset name, otherwise use the resource path name
-            var resource = <Atomic.Resource> object.getAttribute(attrInfo.name);
+            var resource = <Atomic.Resource>object.getAttribute(attrInfo.name);
             var text = "";
             var text = "";
             if (resource) {
             if (resource) {
                 text = resource.name;
                 text = resource.name;
                 var asset = ToolCore.assetDatabase.getAssetByCachePath(resource.name);
                 var asset = ToolCore.assetDatabase.getAssetByCachePath(resource.name);
                 if (asset)
                 if (asset)
-                  text = asset.name;
+                    text = asset.name;
             }
             }
 
 
             widget["editField"].text = text;
             widget["editField"].text = text;
@@ -445,6 +456,17 @@ class DataBinding {
 
 
     }
     }
 
 
+    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 {
     handleWidgetEvent(ev: Atomic.UIWidgetEvent): boolean {
 
 
@@ -459,6 +481,10 @@ class DataBinding {
 
 
                 this.object.setAttribute(this.attrInfo.name, Number(ev.refid) - 1);
                 this.object.setAttribute(this.attrInfo.name, Number(ev.refid) - 1);
                 this.setWidgetValueFromObject();
                 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 });
+
 
 
             }
             }
 
 
@@ -478,8 +504,20 @@ class DataBinding {
         }
         }
 
 
         if (ev.type == Atomic.UI_EVENT_TYPE_CHANGED) {
         if (ev.type == Atomic.UI_EVENT_TYPE_CHANGED) {
+
             if (this.widget == ev.target || this.widget.isAncestorOf(ev.target)) {
             if (this.widget == ev.target || this.widget.isAncestorOf(ev.target)) {
+
                 this.setObjectValueFromWidget(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 true;
             }
             }
         }
         }

+ 22 - 19
Script/AtomicEditor/ui/frames/inspector/InspectorFrame.ts

@@ -19,7 +19,7 @@ import PrefabInspector = require("./PrefabInspector");
 
 
 class InspectorFrame extends ScriptWidget {
 class InspectorFrame extends ScriptWidget {
 
 
-    inspectingNode: Atomic.Node;
+    nodeInspector: NodeInspector;
 
 
     constructor() {
     constructor() {
 
 
@@ -41,10 +41,9 @@ class InspectorFrame extends ScriptWidget {
 
 
     handleProjectUnloaded(data) {
     handleProjectUnloaded(data) {
 
 
-        this.inspectingNode = null;
+        this.closeNodeInspector();
         var container = this.getWidget("inspectorcontainer");
         var container = this.getWidget("inspectorcontainer");
         container.deleteAllChildren();
         container.deleteAllChildren();
-
     }
     }
 
 
 
 
@@ -69,14 +68,7 @@ class InspectorFrame extends ScriptWidget {
 
 
         if (!node) {
         if (!node) {
 
 
-            if (this.inspectingNode) {
-
-                this.inspectingNode = null;
-                var container = this.getWidget("inspectorcontainer");
-                container.deleteAllChildren();
-
-            }
-
+            this.closeNodeInspector();
             return;
             return;
         }
         }
 
 
@@ -84,10 +76,22 @@ class InspectorFrame extends ScriptWidget {
 
 
     }
     }
 
 
+    closeNodeInspector() {
+
+      if (this.nodeInspector) {
+          this.nodeInspector.saveState();
+          var container = this.getWidget("inspectorcontainer");
+          container.deleteAllChildren();
+          this.nodeInspector = null;
+      }
+
+    }
+
 
 
     inspectAsset(asset: ToolCore.Asset) {
     inspectAsset(asset: ToolCore.Asset) {
 
 
-        this.inspectingNode = null;
+        this.sendEvent(EditorEvents.ActiveNodeChange, {node:null});
+
         var container = this.getWidget("inspectorcontainer");
         var container = this.getWidget("inspectorcontainer");
         container.deleteAllChildren();
         container.deleteAllChildren();
 
 
@@ -129,7 +133,7 @@ class InspectorFrame extends ScriptWidget {
 
 
             var prefabInspector = new PrefabInspector();
             var prefabInspector = new PrefabInspector();
             container.addChild(prefabInspector);
             container.addChild(prefabInspector);
-            
+
             prefabInspector.inspect(asset);
             prefabInspector.inspect(asset);
         }
         }
 
 
@@ -137,13 +141,10 @@ class InspectorFrame extends ScriptWidget {
 
 
     handleNodeRemoved(ev: Atomic.NodeRemovedEvent) {
     handleNodeRemoved(ev: Atomic.NodeRemovedEvent) {
 
 
-        if (this.inspectingNode != ev.node)
+        if (this.nodeInspector && this.nodeInspector.node != ev.node)
             return;
             return;
 
 
-        this.inspectingNode = null;
-
-        var container = this.getWidget("inspectorcontainer");
-        container.deleteAllChildren();
+        this.closeNodeInspector();
 
 
     }
     }
 
 
@@ -152,6 +153,8 @@ class InspectorFrame extends ScriptWidget {
 
 
         if (!node) return;
         if (!node) return;
 
 
+        this.closeNodeInspector();
+
         var container = this.getWidget("inspectorcontainer");
         var container = this.getWidget("inspectorcontainer");
         container.deleteAllChildren();
         container.deleteAllChildren();
 
 
@@ -160,7 +163,7 @@ class InspectorFrame extends ScriptWidget {
 
 
         inspector.inspect(node);
         inspector.inspect(node);
 
 
-        this.inspectingNode = node;
+        this.nodeInspector = inspector;
 
 
     }
     }
 
 

+ 3 - 1
Script/AtomicEditor/ui/frames/inspector/InspectorWidget.ts

@@ -133,7 +133,9 @@ class InspectorWidget extends ScriptWidget {
 
 
     }
     }
 
 
-    handleWidgetEvent(ev: Atomic.UIWidgetEvent) {
+    handleWidgetEvent(ev: Atomic.UIWidgetEvent):boolean {
+
+      return false;
 
 
     }
     }
 
 

+ 3 - 1
Script/AtomicEditor/ui/frames/inspector/ModelInspector.ts

@@ -19,7 +19,9 @@ class ModelInspector extends InspectorWidget {
 
 
     }
     }
 
 
-    handleWidgetEvent(ev: Atomic.UIWidgetEvent) {
+    handleWidgetEvent(ev: Atomic.UIWidgetEvent):boolean {
+
+      return false;
 
 
     }
     }
 
 

+ 127 - 3
Script/AtomicEditor/ui/frames/inspector/NodeInspector.ts

@@ -10,6 +10,19 @@ import ComponentInspector = require("./ComponentInspector");
 import DataBinding = require("./DataBinding");
 import DataBinding = require("./DataBinding");
 import CreateComponentButton = require("./CreateComponentButton");
 import CreateComponentButton = require("./CreateComponentButton");
 
 
+interface ComponentState {
+
+    expanded: boolean;
+
+}
+
+interface NodeState {
+
+    expanded: boolean;
+    componentStates: { [id: number]: ComponentState };
+
+}
+
 class NodeInspector extends ScriptWidget {
 class NodeInspector extends ScriptWidget {
 
 
     constructor() {
     constructor() {
@@ -94,7 +107,7 @@ class NodeInspector extends ScriptWidget {
     getPrefabComponent(node: Atomic.Node): Atomic.PrefabComponent {
     getPrefabComponent(node: Atomic.Node): Atomic.PrefabComponent {
 
 
         if (node.getComponent("PrefabComponent"))
         if (node.getComponent("PrefabComponent"))
-            return <Atomic.PrefabComponent> node.getComponent("PrefabComponent");
+            return <Atomic.PrefabComponent>node.getComponent("PrefabComponent");
 
 
         if (node.parent)
         if (node.parent)
             return this.getPrefabComponent(node.parent);
             return this.getPrefabComponent(node.parent);
@@ -115,13 +128,15 @@ class NodeInspector extends ScriptWidget {
 
 
     }
     }
 
 
-
     inspect(node: Atomic.Node) {
     inspect(node: Atomic.Node) {
 
 
         this.bindings = new Array();
         this.bindings = new Array();
 
 
         this.node = node;
         this.node = node;
 
 
+        node.scene.sendEvent("SceneEditSerializable", { serializable: node, operation: 0 });
+        this.subscribeToEvent(node, "SceneEditSerializableUndoRedo", (data) => this.handleSceneEditSerializableUndoRedoEvent(data));
+
         this.isPrefab = this.detectPrefab(node);
         this.isPrefab = this.detectPrefab(node);
 
 
         var fd = new Atomic.UIFontDescription();
         var fd = new Atomic.UIFontDescription();
@@ -142,6 +157,7 @@ class NodeInspector extends ScriptWidget {
         // node attr layout
         // node attr layout
 
 
         var nodeSection = new Atomic.UISection();
         var nodeSection = new Atomic.UISection();
+        nodeSection.id = "node_section";
         nodeSection.text = "Node";
         nodeSection.text = "Node";
         nodeSection.value = 1;
         nodeSection.value = 1;
         nodeLayout.addChild(nodeSection);
         nodeLayout.addChild(nodeSection);
@@ -157,7 +173,7 @@ class NodeInspector extends ScriptWidget {
 
 
         for (var i in attrs) {
         for (var i in attrs) {
 
 
-            var attr = <Atomic.AttributeInfo> attrs[i];
+            var attr = <Atomic.AttributeInfo>attrs[i];
 
 
             if (attr.mode & Atomic.AM_NOEDIT)
             if (attr.mode & Atomic.AM_NOEDIT)
                 continue;
                 continue;
@@ -266,6 +282,8 @@ class NodeInspector extends ScriptWidget {
 
 
                     prefabComponent.breakPrefab();
                     prefabComponent.breakPrefab();
 
 
+                    this.sendEvent("EditorActiveNodeChange", { node: this.node });
+
                     return true;
                     return true;
 
 
                 }
                 }
@@ -294,6 +312,7 @@ class NodeInspector extends ScriptWidget {
             //  continue;
             //  continue;
 
 
             var ci = new ComponentInspector();
             var ci = new ComponentInspector();
+            ci.id = "component_section_" + component.id;
 
 
             ci.inspect(component);
             ci.inspect(component);
 
 
@@ -312,8 +331,111 @@ class NodeInspector extends ScriptWidget {
             this.bindings[i].objectLocked = false;
             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;
     isPrefab: boolean;
     node: Atomic.Node;
     node: Atomic.Node;
     nodeLayout: Atomic.UILayout;
     nodeLayout: Atomic.UILayout;
@@ -321,6 +443,8 @@ class NodeInspector extends ScriptWidget {
     gizmoMoved = false;
     gizmoMoved = false;
     updateDelta = 0;
     updateDelta = 0;
 
 
+    static nodeStates: { [sceneID: number]: { [nodeId: number]: NodeState } } = {};
+
 }
 }
 
 
 export = NodeInspector;
 export = NodeInspector;

+ 3 - 1
Script/AtomicEditor/ui/frames/inspector/PrefabInspector.ts

@@ -22,7 +22,9 @@ class PrefabInspector extends InspectorWidget {
 
 
     }
     }
 
 
-    handleWidgetEvent(ev: Atomic.UIWidgetEvent) {
+    handleWidgetEvent(ev: Atomic.UIWidgetEvent):boolean {
+
+      return false;
 
 
     }
     }
 
 

+ 1 - 0
Script/AtomicEditor/ui/frames/menus/HierarchyFrameMenu.ts

@@ -38,6 +38,7 @@ class HierarchyFrameMenus extends Atomic.ScriptObject {
                 if (node) {
                 if (node) {
 
 
                     child = node.createChild("Node");
                     child = node.createChild("Node");
+                    node.scene.sendEvent("SceneEditNodeAddedRemoved", { scene:node.scene, node:node, added:true});
 
 
                 }
                 }
 
 

+ 9 - 2
Script/AtomicEditor/ui/frames/menus/MainFrameMenu.ts

@@ -46,7 +46,7 @@ class MainFrameMenu extends Atomic.ScriptObject {
 
 
             if (refid == "quit") {
             if (refid == "quit") {
 
 
-                Atomic.ui.requestExit();
+                this.sendEvent("ExitRequested");
                 return true;
                 return true;
 
 
             }
             }
@@ -98,6 +98,11 @@ class MainFrameMenu extends Atomic.ScriptObject {
                 return true;
                 return true;
             }
             }
 
 
+            if (refid == "edit snap settings") {
+                EditorUI.getModelOps().showSnapSettings();
+                return true;
+            }
+
             return false;
             return false;
 
 
         } else if (target.id == "menu file popup") {
         } else if (target.id == "menu file popup") {
@@ -250,7 +255,9 @@ var editItems = {
     "Format Code": ["edit format code", StringID.ShortcutBeautify],
     "Format Code": ["edit format code", StringID.ShortcutBeautify],
     "-4": null,
     "-4": null,
     "Play": ["edit play", StringID.ShortcutPlay],
     "Play": ["edit play", StringID.ShortcutPlay],
-    "Debug (C# Project)": ["edit play debug", StringID.ShortcutPlayDebug]
+    "Debug (C# Project)": ["edit play debug", StringID.ShortcutPlayDebug],
+    "-5": null,
+    "Snap Settings": ["edit snap settings"]
 
 
 };
 };
 
 

+ 16 - 0
Script/AtomicEditor/ui/modal/ModalOps.ts

@@ -26,6 +26,8 @@ import ResourceSelection = require("./ResourceSelection");
 
 
 import UIResourceOps = require("./UIResourceOps");
 import UIResourceOps = require("./UIResourceOps");
 
 
+import SnapSettingsWindow = require("./SnapSettingsWindow");
+
 class ModalOps extends Atomic.ScriptObject {
 class ModalOps extends Atomic.ScriptObject {
 
 
     constructor() {
     constructor() {
@@ -246,6 +248,20 @@ class ModalOps extends Atomic.ScriptObject {
 
 
     }
     }
 
 
+    showSnapSettings() {
+
+        // only show snap settings if we have a project loaded
+        if (!ToolCore.toolSystem.project)
+          return;
+
+        if (this.show()) {
+
+            this.opWindow = new SnapSettingsWindow();
+
+        }
+
+    }
+
     private show(): boolean {
     private show(): boolean {
 
 
         if (this.dimmer.parent) {
         if (this.dimmer.parent) {

+ 79 - 0
Script/AtomicEditor/ui/modal/SnapSettingsWindow.ts

@@ -0,0 +1,79 @@
+import ModalWindow = require("./ModalWindow");
+
+class SnapSettingsWindow extends ModalWindow {
+
+    constructor() {
+
+        super();
+
+        this.init("Snap Settings", "AtomicEditor/editor/ui/snapsettings.tb.txt");
+
+        this.transXEditField = <Atomic.UIEditField>this.getWidget("trans_x");
+        this.transYEditField = <Atomic.UIEditField>this.getWidget("trans_y");
+        this.transZEditField = <Atomic.UIEditField>this.getWidget("trans_z");
+        this.rotateEditField = <Atomic.UIEditField>this.getWidget("rotation");
+        this.scaleEditField = <Atomic.UIEditField>this.getWidget("scale");
+
+        this.refreshWidgets();
+
+    }
+
+    apply() {
+
+        var userPrefs = ToolCore.toolSystem.project.userPrefs;
+        userPrefs.snapTranslationX = Number(this.transXEditField.text);
+        userPrefs.snapTranslationY = Number(this.transYEditField.text);
+        userPrefs.snapTranslationZ = Number(this.transZEditField.text);
+        userPrefs.snapRotation = Number(this.rotateEditField.text);
+        userPrefs.snapScale = Number(this.scaleEditField.text);
+
+        ToolCore.toolSystem.project.saveUserPrefs();
+
+    }
+
+    refreshWidgets() {
+
+        var userPrefs = ToolCore.toolSystem.project.userPrefs;
+
+        this.transXEditField.text = parseFloat(userPrefs.snapTranslationX.toFixed(5)).toString();
+        this.transYEditField.text = parseFloat(userPrefs.snapTranslationY.toFixed(5)).toString();
+        this.transZEditField.text = parseFloat(userPrefs.snapTranslationZ.toFixed(5)).toString();
+        this.rotateEditField.text = parseFloat(userPrefs.snapRotation.toFixed(5)).toString();
+        this.scaleEditField.text = parseFloat(userPrefs.snapScale.toFixed(5)).toString();
+
+    }
+
+    handleWidgetEvent(ev: Atomic.UIWidgetEvent) {
+
+        if (ev.type == Atomic.UI_EVENT_TYPE_CLICK) {
+
+            var id = ev.target.id;
+
+            if (id == "apply") {
+
+                this.apply();
+                this.hide();
+                return true;
+
+            }
+
+            if (id == "cancel") {
+
+                this.hide();
+
+                return true;
+            }
+
+        }
+
+    }
+
+    transXEditField: Atomic.UIEditField;
+    transYEditField: Atomic.UIEditField;
+    transZEditField: Atomic.UIEditField;
+    rotateEditField: Atomic.UIEditField;
+    scaleEditField: Atomic.UIEditField;
+
+}
+
+export = SnapSettingsWindow;

+ 1 - 1
Script/AtomicEditor/ui/modal/license/ActivationWindow.ts

@@ -63,7 +63,7 @@ class ActivationWindow extends ModalWindow {
 
 
             if (id == "quit") {
             if (id == "quit") {
 
 
-                this.sendEvent(EditorEvents.Quit);
+                this.sendEvent("ExitRequested");
                 return true;
                 return true;
 
 
             } else if (id == "get_key") {
             } else if (id == "get_key") {

+ 1 - 1
Script/AtomicEditor/ui/modal/license/EULAWindow.ts

@@ -52,7 +52,7 @@ class EULAWindow extends ModalWindow {
 
 
             if (id == "quit") {
             if (id == "quit") {
 
 
-                this.sendEvent(EditorEvents.Quit);
+                this.sendEvent("ExitRequested");
                 return true;
                 return true;
 
 
             } else if (id == "ok") {
             } else if (id == "ok") {

+ 5 - 0
Script/Packages/Atomic/Core.json

@@ -10,6 +10,11 @@
 			"SendEvent" : ["StringHash"]
 			"SendEvent" : ["StringHash"]
 		}
 		}
 	},
 	},
+	"overloads" : {
+		"Object" : {
+			"UnsubscribeFromEvent" : ["StringHash"]
+		}
+	},
 	"typescript_decl" : {
 	"typescript_decl" : {
 
 
 		"Object" : [
 		"Object" : [

+ 2 - 1
Script/Packages/ToolCore/ToolCore.json

@@ -7,7 +7,8 @@
 								"Project", "ProjectFile", "Platform", "PlatformMac", "PlatformWeb",
 								"Project", "ProjectFile", "Platform", "PlatformMac", "PlatformWeb",
 							 "PlatformWindows", "PlatformAndroid", "PlatformIOS", "Command", "PlayCmd", "OpenAssetImporter",
 							 "PlatformWindows", "PlatformAndroid", "PlatformIOS", "Command", "PlayCmd", "OpenAssetImporter",
 							 "Asset", "AssetDatabase", "AssetImporter", "AudioImporter", "ModelImporter", "MaterialImporter", "AnimationImportInfo",
 							 "Asset", "AssetDatabase", "AssetImporter", "AudioImporter", "ModelImporter", "MaterialImporter", "AnimationImportInfo",
-							 "PrefabImporter", "JavascriptImporter", "TextureImporter", "SpriterImporter", "PEXImporter", "NETAssemblyImporter",
+							 "PrefabImporter", "JavascriptImporter", "JSONImporter",
+							 "TextureImporter", "SpriterImporter", "PEXImporter", "NETAssemblyImporter",
 							 "LicenseSystem",
 							 "LicenseSystem",
 						 	 "ProjectUserPrefs", "ProjectBuildSettings",
 						 	 "ProjectUserPrefs", "ProjectBuildSettings",
 						 	 "BuildBase", "BuildSystem", "BuildMac", "BuildWeb", "BuildWindows", "BuildAndroid", "BuildIOS",
 						 	 "BuildBase", "BuildSystem", "BuildMac", "BuildWeb", "BuildWindows", "BuildAndroid", "BuildIOS",

+ 7 - 1
Script/TypeScript/AtomicWork.d.ts

@@ -116,6 +116,11 @@ declare module Atomic {
         touch: boolean;
         touch: boolean;
     }
     }
 
 
+    export interface UIWidgetFocusChangedEvent {
+        widget: UIWidget;
+        focused: boolean;
+    }
+
     export interface UIWidgetDeletedEvent {
     export interface UIWidgetDeletedEvent {
 
 
         widget: UIWidget;
         widget: UIWidget;
@@ -250,8 +255,9 @@ declare module Editor {
 
 
   export interface GizmoAxisModeChangedEvent {
   export interface GizmoAxisModeChangedEvent {
     mode:AxisMode;
     mode:AxisMode;
+    toggle:boolean;
   }
   }
-  
+
 }
 }
 
 
 declare module ToolCore {
 declare module ToolCore {

+ 1 - 0
Script/tsconfig.json

@@ -71,6 +71,7 @@
         "./AtomicEditor/ui/modal/NewProject.ts",
         "./AtomicEditor/ui/modal/NewProject.ts",
         "./AtomicEditor/ui/modal/ProgressModal.ts",
         "./AtomicEditor/ui/modal/ProgressModal.ts",
         "./AtomicEditor/ui/modal/ResourceSelection.ts",
         "./AtomicEditor/ui/modal/ResourceSelection.ts",
+        "./AtomicEditor/ui/modal/SnapSettingsWindow.ts",
         "./AtomicEditor/ui/modal/UIResourceOps.ts",
         "./AtomicEditor/ui/modal/UIResourceOps.ts",
         "./AtomicEditor/ui/playmode/PlayerOutput.ts",
         "./AtomicEditor/ui/playmode/PlayerOutput.ts",
         "./AtomicEditor/ui/playmode/PlayMode.ts",
         "./AtomicEditor/ui/playmode/PlayMode.ts",

+ 1 - 1
Source/Atomic/Core/StringUtils.cpp

@@ -647,7 +647,7 @@ String ToString(const char* formatString, ...)
     return ret;
     return ret;
 }
 }
 
 
-String ToString(const char* formatString, va_list args)
+String ToStringVariadic(const char* formatString, va_list args)
 {
 {
     String ret;
     String ret;
     ret.AppendWithFormatArgs(formatString, args);
     ret.AppendWithFormatArgs(formatString, args);

+ 1 - 1
Source/Atomic/Core/StringUtils.h

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

+ 21 - 2
Source/Atomic/UI/UI.cpp

@@ -150,7 +150,7 @@ void UI::HandleExitRequested(StringHash eventType, VariantMap& eventData)
 
 
 void UI::Shutdown()
 void UI::Shutdown()
 {
 {
-    SetInputDisabled(true);
+
 }
 }
 
 
 void UI::Initialize(const String& languageFile)
 void UI::Initialize(const String& languageFile)
@@ -508,6 +508,8 @@ void UI::HandleUpdate(StringHash eventType, VariantMap& eventData)
         exitRequested_ = false;
         exitRequested_ = false;
         return;
         return;
     }
     }
+
+    SendEvent(E_UIUPDATE);
     TBMessageHandler::ProcessMessages();
     TBMessageHandler::ProcessMessages();
 }
 }
 
 
@@ -750,6 +752,18 @@ bool UI::OnWidgetDying(tb::TBWidget *widget)
     return false;
     return false;
 }
 }
 
 
+void UI::OnWidgetFocusChanged(TBWidget *widget, bool focused)
+{
+    if (widgetWrap_.Contains(widget))
+    {
+        VariantMap evData;
+        UIWidget* uiWidget = widgetWrap_[widget];
+        evData[UIWidgetFocusChanged::P_WIDGET]  = uiWidget;
+        evData[UIWidgetFocusChanged::P_FOCUSED]  = focused;
+        uiWidget->SendEvent(E_UIWIDGETFOCUSCHANGED, evData);
+    }
+}
+
 void UI::ShowDebugHud(bool value)
 void UI::ShowDebugHud(bool value)
 {
 {
     SystemUI::DebugHud* hud = GetSubsystem<SystemUI::DebugHud>();
     SystemUI::DebugHud* hud = GetSubsystem<SystemUI::DebugHud>();
@@ -813,10 +827,15 @@ SystemUI::MessageBox* UI::ShowSystemMessageBox(const String& title, const String
 
 
 }
 }
 
 
-
 UIWidget* UI::GetWidgetAt(int x, int y, bool include_children)
 UIWidget* UI::GetWidgetAt(int x, int y, bool include_children)
 {
 {
     return WrapWidget(rootWidget_->GetWidgetAt(x, y, include_children));
     return WrapWidget(rootWidget_->GetWidgetAt(x, y, include_children));
 }
 }
 
 
+bool UI::OnWidgetInvokeEvent(tb::TBWidget *widget, const tb::TBWidgetEvent &ev)
+{
+    return false;
+}
+
+
 }
 }

+ 4 - 1
Source/Atomic/UI/UI.h

@@ -119,7 +119,10 @@ private:
 
 
     // TBWidgetListener
     // TBWidgetListener
     void OnWidgetDelete(tb::TBWidget *widget);
     void OnWidgetDelete(tb::TBWidget *widget);
-    bool OnWidgetDying(tb::TBWidget *widget);
+    bool OnWidgetDying(tb::TBWidget *widget);    
+    void OnWidgetFocusChanged(tb::TBWidget *widget, bool focused);
+    bool OnWidgetInvokeEvent(tb::TBWidget *widget, const tb::TBWidgetEvent &ev);
+
 
 
     tb::TBWidget* rootWidget_;
     tb::TBWidget* rootWidget_;
     UIRenderer* renderer_;
     UIRenderer* renderer_;

+ 3 - 0
Source/Atomic/UI/UIDragDrop.h

@@ -48,6 +48,9 @@ public:
     void FileDragAddFile(const String& filename);
     void FileDragAddFile(const String& filename);
     void FileDragConclude();
     void FileDragConclude();
 
 
+    /// Returns true when dragging an object
+    bool GetDraggingObject() { return dragObject_.NotNull(); }
+
 private:
 private:
 
 
     void HandleMouseDown(StringHash eventType, VariantMap& eventData);
     void HandleMouseDown(StringHash eventType, VariantMap& eventData);

+ 6 - 0
Source/Atomic/UI/UIEditField.cpp

@@ -196,6 +196,12 @@ void UIEditField::SetTextAlign(UI_TEXT_ALIGN align)
 
 
 bool UIEditField::OnEvent(const tb::TBWidgetEvent &ev)
 bool UIEditField::OnEvent(const tb::TBWidgetEvent &ev)
 {
 {
+    if (ev.type == EVENT_TYPE_CUSTOM && ev.ref_id == TBIDC("edit_complete"))
+    {
+        SendEvent(E_UIWIDGETEDITCOMPLETE);
+        return true;
+    }
+
     return UIWidget::OnEvent(ev);
     return UIWidget::OnEvent(ev);
 }
 }
 
 

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

@@ -27,6 +27,12 @@
 namespace Atomic
 namespace Atomic
 {
 {
 
 
+// UIUpdate event
+EVENT(E_UIUPDATE, UIUpdate)
+{
+
+}
+
 EVENT(E_WIDGETEVENT, WidgetEvent)
 EVENT(E_WIDGETEVENT, WidgetEvent)
 {
 {
     PARAM(P_HANDLER, Handler);           // UIWidget pointer of widget's OnEvent we are in
     PARAM(P_HANDLER, Handler);           // UIWidget pointer of widget's OnEvent we are in
@@ -103,4 +109,22 @@ EVENT(E_UISHORTCUT, UIShortcut)
 
 
 }
 }
 
 
+EVENT(E_UIWIDGETFOCUSCHANGED, UIWidgetFocusChanged)
+{
+    PARAM(P_WIDGET, Widget);             // UIWidget pointer
+    PARAM(P_FOCUSED, Focused);             // bool
+}
+EVENT(E_UIWIDGETFOCUSESCAPED, UIWidgetFocusEscaped)
+{
+}
+
+EVENT(E_UIWIDGETEDITCOMPLETE, UIWidgetEditComplete)
+{
+}
+
+EVENT(E_UIUNHANDLEDSHORTCUT, UIUnhandledShortcut)
+{
+    PARAM(P_REFID, RefID); // string tbid
+}
+
 }
 }

+ 5 - 0
Source/Atomic/UI/UIInlineSelect.cpp

@@ -70,6 +70,11 @@ void UIInlineSelect::SetLimits(double minimum, double maximum)
 
 
 bool UIInlineSelect::OnEvent(const tb::TBWidgetEvent &ev)
 bool UIInlineSelect::OnEvent(const tb::TBWidgetEvent &ev)
 {
 {
+    if (ev.type == EVENT_TYPE_CUSTOM && ev.ref_id == TBIDC("edit_complete"))
+    {
+        SendEvent(E_UIWIDGETEDITCOMPLETE);
+        return true;
+    }
     return UIWidget::OnEvent(ev);
     return UIWidget::OnEvent(ev);
 }
 }
 
 

+ 54 - 37
Source/Atomic/UI/UIInput.cpp

@@ -213,7 +213,7 @@ void UI::HandleTouchEnd(StringHash eventType, VariantMap& eventData)
     rootWidget_->InvokePointerUp(px, py, TB_MODIFIER_NONE, true, touchId);
     rootWidget_->InvokePointerUp(px, py, TB_MODIFIER_NONE, true, touchId);
 }
 }
 
 
-static bool InvokeShortcut(int key, SPECIAL_KEY special_key, MODIFIER_KEYS modifierkeys, bool down)
+static bool InvokeShortcut(UI* ui, int key, SPECIAL_KEY special_key, MODIFIER_KEYS modifierkeys, bool down)
 {
 {
 #ifdef __APPLE__
 #ifdef __APPLE__
     bool shortcut_key = (modifierkeys & TB_SUPER) ? true : false;
     bool shortcut_key = (modifierkeys & TB_SUPER) ? true : false;
@@ -286,15 +286,20 @@ static bool InvokeShortcut(int key, SPECIAL_KEY special_key, MODIFIER_KEYS modif
 
 
     }
     }
 
 
-    if (!eventWidget)
+    if (!eventWidget || !eventWidget->InvokeEvent(ev))
+    {
+        VariantMap evData;
+        evData[UIUnhandledShortcut::P_REFID] = id;
+        ui->SendEvent(E_UIUNHANDLEDSHORTCUT, evData);
         return false;
         return false;
+    }
 
 
-    return eventWidget->InvokeEvent(ev);
+    return true;
 }
 }
 
 
-static bool InvokeKey(TBWidget* root, unsigned int key, SPECIAL_KEY special_key, MODIFIER_KEYS modifierkeys, bool keydown)
+static bool InvokeKey(UI* ui, TBWidget* root, unsigned int key, SPECIAL_KEY special_key, MODIFIER_KEYS modifierkeys, bool keydown)
 {
 {
-    if (InvokeShortcut(key, special_key, modifierkeys, keydown))
+    if (InvokeShortcut(ui, key, special_key, modifierkeys, keydown))
         return true;
         return true;
     root->InvokeKey(key, special_key, modifierkeys, keydown);
     root->InvokeKey(key, special_key, modifierkeys, keydown);
     return true;
     return true;
@@ -303,6 +308,11 @@ static bool InvokeKey(TBWidget* root, unsigned int key, SPECIAL_KEY special_key,
 
 
 void UI::HandleKey(bool keydown, int keycode, int scancode)
 void UI::HandleKey(bool keydown, int keycode, int scancode)
 {
 {
+    if (keydown && (keycode == KEY_ESC || keycode == KEY_RETURN || keycode == KEY_RETURN2 || keycode == KEY_KP_ENTER)
+            && TBWidget::focused_widget)
+    {
+        SendEvent(E_UIWIDGETFOCUSESCAPED);
+    }
 
 
 #ifdef ATOMIC_PLATFORM_WINDOWS
 #ifdef ATOMIC_PLATFORM_WINDOWS
     if (keycode == KEY_LCTRL || keycode == KEY_RCTRL)
     if (keycode == KEY_LCTRL || keycode == KEY_RCTRL)
@@ -322,95 +332,102 @@ void UI::HandleKey(bool keydown, int keycode, int scancode)
 #endif
 #endif
     MODIFIER_KEYS mod = GetModifierKeys(qualifiers, superdown);
     MODIFIER_KEYS mod = GetModifierKeys(qualifiers, superdown);
 
 
+    SPECIAL_KEY specialKey = TB_KEY_UNDEFINED;
+
     switch (keycode)
     switch (keycode)
     {
     {
     case KEY_RETURN:
     case KEY_RETURN:
     case KEY_RETURN2:
     case KEY_RETURN2:
     case KEY_KP_ENTER:
     case KEY_KP_ENTER:
-        InvokeKey(rootWidget_, 0, TB_KEY_ENTER, mod, keydown);
+        specialKey =  TB_KEY_ENTER;
         break;
         break;
-
     case KEY_F1:
     case KEY_F1:
-        InvokeKey(rootWidget_, 0, TB_KEY_F1, mod, keydown);
+        specialKey = TB_KEY_F1;
         break;
         break;
     case KEY_F2:
     case KEY_F2:
-        InvokeKey(rootWidget_, 0, TB_KEY_F2, mod, keydown);
+        specialKey = TB_KEY_F2;
         break;
         break;
     case KEY_F3:
     case KEY_F3:
-        InvokeKey(rootWidget_, 0, TB_KEY_F3, mod, keydown);
+        specialKey = TB_KEY_F3;
         break;
         break;
     case KEY_F4:
     case KEY_F4:
-        InvokeKey(rootWidget_, 0, TB_KEY_F4, mod, keydown);
+        specialKey = TB_KEY_F4;
         break;
         break;
     case KEY_F5:
     case KEY_F5:
-        InvokeKey(rootWidget_, 0, TB_KEY_F5, mod, keydown);
+        specialKey = TB_KEY_F5;
         break;
         break;
     case KEY_F6:
     case KEY_F6:
-        InvokeKey(rootWidget_, 0, TB_KEY_F6, mod, keydown);
+        specialKey = TB_KEY_F6;
         break;
         break;
     case KEY_F7:
     case KEY_F7:
-        InvokeKey(rootWidget_, 0, TB_KEY_F7, mod, keydown);
+        specialKey = TB_KEY_F7;
         break;
         break;
     case KEY_F8:
     case KEY_F8:
-        InvokeKey(rootWidget_, 0, TB_KEY_F8, mod, keydown);
+        specialKey = TB_KEY_F8;
         break;
         break;
     case KEY_F9:
     case KEY_F9:
-        InvokeKey(rootWidget_, 0, TB_KEY_F9, mod, keydown);
+        specialKey = TB_KEY_F9;
         break;
         break;
     case KEY_F10:
     case KEY_F10:
-        InvokeKey(rootWidget_, 0, TB_KEY_F10, mod, keydown);
+        specialKey = TB_KEY_F10;
         break;
         break;
     case KEY_F11:
     case KEY_F11:
-        InvokeKey(rootWidget_, 0, TB_KEY_F11, mod, keydown);
+        specialKey = TB_KEY_F11;
         break;
         break;
     case KEY_F12:
     case KEY_F12:
-        InvokeKey(rootWidget_, 0, TB_KEY_F12, mod, keydown);
+        specialKey = TB_KEY_F12;
         break;
         break;
     case KEY_LEFT:
     case KEY_LEFT:
-        InvokeKey(rootWidget_, 0, TB_KEY_LEFT, mod, keydown);
+        specialKey = TB_KEY_LEFT;
         break;
         break;
     case KEY_UP:
     case KEY_UP:
-        InvokeKey(rootWidget_, 0, TB_KEY_UP, mod, keydown);
+        specialKey = TB_KEY_UP;
         break;
         break;
     case KEY_RIGHT:
     case KEY_RIGHT:
-        InvokeKey(rootWidget_, 0, TB_KEY_RIGHT, mod, keydown);
+        specialKey = TB_KEY_RIGHT;
         break;
         break;
     case KEY_DOWN:
     case KEY_DOWN:
-        InvokeKey(rootWidget_, 0, TB_KEY_DOWN, mod, keydown);
+        specialKey = TB_KEY_DOWN;
         break;
         break;
     case KEY_PAGEUP:
     case KEY_PAGEUP:
-        InvokeKey(rootWidget_, 0, TB_KEY_PAGE_UP, mod, keydown);
+        specialKey = TB_KEY_PAGE_UP;
         break;
         break;
     case KEY_PAGEDOWN:
     case KEY_PAGEDOWN:
-        InvokeKey(rootWidget_, 0, TB_KEY_PAGE_DOWN, mod, keydown);
+        specialKey = TB_KEY_PAGE_DOWN;
         break;
         break;
     case KEY_HOME:
     case KEY_HOME:
-        InvokeKey(rootWidget_, 0, TB_KEY_HOME, mod, keydown);
+        specialKey = TB_KEY_HOME;
         break;
         break;
     case KEY_END:
     case KEY_END:
-        InvokeKey(rootWidget_, 0, TB_KEY_END, mod, keydown);
+        specialKey = TB_KEY_END;
         break;
         break;
     case KEY_INSERT:
     case KEY_INSERT:
-        InvokeKey(rootWidget_, 0, TB_KEY_INSERT, mod, keydown);
+        specialKey = TB_KEY_INSERT;
         break;
         break;
     case KEY_TAB:
     case KEY_TAB:
-        InvokeKey(rootWidget_, 0, TB_KEY_TAB, mod, keydown);
+        specialKey = TB_KEY_TAB;
         break;
         break;
     case KEY_DELETE:
     case KEY_DELETE:
-        InvokeKey(rootWidget_, 0, TB_KEY_DELETE, mod, keydown);
+        specialKey = TB_KEY_DELETE;
         break;
         break;
     case KEY_BACKSPACE:
     case KEY_BACKSPACE:
-        InvokeKey(rootWidget_, 0, TB_KEY_BACKSPACE, mod, keydown);
+        specialKey = TB_KEY_BACKSPACE;
         break;
         break;
     case KEY_ESC:
     case KEY_ESC:
-        InvokeKey(rootWidget_, 0, TB_KEY_ESC, mod, keydown);
+        specialKey =  TB_KEY_ESC;
         break;
         break;
-    default:
+    }
+
+    if (specialKey == TB_KEY_UNDEFINED)
+    {
         if (mod & TB_SUPER)
         if (mod & TB_SUPER)
         {
         {
-            InvokeKey(rootWidget_, keycode, TB_KEY_UNDEFINED, mod, keydown);
+            InvokeKey(this, rootWidget_, keycode, TB_KEY_UNDEFINED, mod, keydown);
         }
         }
-
+    }
+    else
+    {
+        InvokeKey(this, rootWidget_, 0, specialKey, mod, keydown);
     }
     }
 
 
 }
 }
@@ -477,8 +494,8 @@ void UI::HandleTextInput(StringHash eventType, VariantMap& eventData)
 
 
     for (unsigned i = 0; i < text.Length(); i++)
     for (unsigned i = 0; i < text.Length(); i++)
     {
     {
-        InvokeKey(rootWidget_, text[i], TB_KEY_UNDEFINED, TB_MODIFIER_NONE, true);
-        InvokeKey(rootWidget_, text[i], TB_KEY_UNDEFINED, TB_MODIFIER_NONE, false);
+        InvokeKey(this, rootWidget_, text[i], TB_KEY_UNDEFINED, TB_MODIFIER_NONE, true);
+        InvokeKey(this, rootWidget_, text[i], TB_KEY_UNDEFINED, TB_MODIFIER_NONE, false);
     }
     }
 
 
 }
 }

+ 28 - 0
Source/Atomic/UI/UIListView.cpp

@@ -230,6 +230,7 @@ ListViewItemWidget::ListViewItemWidget(ListViewItem *item, ListViewItemSource *s
     SetLayoutDistribution(LAYOUT_DISTRIBUTION_GRAVITY);
     SetLayoutDistribution(LAYOUT_DISTRIBUTION_GRAVITY);
     SetLayoutDistributionPosition(LAYOUT_DISTRIBUTION_POSITION_LEFT_TOP);
     SetLayoutDistributionPosition(LAYOUT_DISTRIBUTION_POSITION_LEFT_TOP);
     SetPaintOverflowFadeout(false);
     SetPaintOverflowFadeout(false);
+    SetCapturing(false);
 
 
     item_->widget_ = this;
     item_->widget_ = this;
 
 
@@ -294,6 +295,8 @@ bool ListViewItemWidget::OnEvent(const TBWidgetEvent &ev)
 
 
     if (ev.type == EVENT_TYPE_POINTER_DOWN || ev.type == EVENT_TYPE_RIGHT_POINTER_UP)
     if (ev.type == EVENT_TYPE_POINTER_DOWN || ev.type == EVENT_TYPE_RIGHT_POINTER_UP)
     {
     {
+        SetFocus(WIDGET_FOCUS_REASON_POINTER);
+
         TBWidget* parent = GetParent();
         TBWidget* parent = GetParent();
 
 
         while (parent)
         while (parent)
@@ -515,6 +518,23 @@ void UIListView::SetExpanded(unsigned itemID, bool value)
 
 
 }
 }
 
 
+bool UIListView::GetExpanded(unsigned itemID)
+{
+    if (!itemLookup_.Contains(itemID))
+        return false;
+
+    return itemLookup_[itemID]->GetExpanded();
+
+}
+
+bool UIListView::GetExpandable(unsigned itemID)
+{
+    if (!itemLookup_.Contains(itemID))
+        return false;
+
+    return itemLookup_[itemID]->children_.Size() > 0;
+}
+
 
 
 void UIListView::DeleteAllItems()
 void UIListView::DeleteAllItems()
 {
 {
@@ -542,5 +562,13 @@ void UIListView::SelectItemByID(const String& id)
     }
     }
 }
 }
 
 
+void UIListView::ScrollToSelectedItem()
+{
+    if (rootList_.Null())
+        return;
+
+    rootList_->ScrollToSelectedItem();
+}
+
 
 
 }
 }

+ 4 - 0
Source/Atomic/UI/UIListView.h

@@ -52,7 +52,11 @@ public:
     void SetItemIcon(const String& id, const String& icon);
     void SetItemIcon(const String& id, const String& icon);
     void DeleteItemByID(const String& id);
     void DeleteItemByID(const String& id);
 
 
+    void ScrollToSelectedItem();
+
     void SetExpanded(unsigned itemID, bool value);
     void SetExpanded(unsigned itemID, bool value);
+    bool GetExpanded(unsigned itemID);
+    bool GetExpandable(unsigned itemID);
 
 
     void DeleteAllItems();
     void DeleteAllItems();
     void SelectItemByID(const String& id);
     void SelectItemByID(const String& id);

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

@@ -22,8 +22,12 @@
 
 
 #include <TurboBadger/tb_select.h>
 #include <TurboBadger/tb_select.h>
 
 
+#include "../Atomic/IO/Log.h"
+#include "../Atomic/Input/Input.h"
+
 #include "UI.h"
 #include "UI.h"
 #include "UIEvents.h"
 #include "UIEvents.h"
+#include "UIDragDrop.h"
 #include "UISelectList.h"
 #include "UISelectList.h"
 
 
 using namespace tb;
 using namespace tb;
@@ -39,6 +43,8 @@ UISelectList::UISelectList(Context* context, bool createWidget) : UIWidget(conte
         widget_->SetDelegate(this);
         widget_->SetDelegate(this);
         GetSubsystem<UI>()->WrapWidget(this, widget_);
         GetSubsystem<UI>()->WrapWidget(this, widget_);
     }
     }
+
+    SubscribeToEvent(E_UIUPDATE, HANDLER(UISelectList, HandleUIUpdate));
 }
 }
 
 
 UISelectList::~UISelectList()
 UISelectList::~UISelectList()
@@ -130,9 +136,85 @@ void UISelectList::SetSource(UISelectItemSource* source)
     ((TBSelectList*)widget_)->SetSource(source ? source->GetTBItemSource() : NULL);
     ((TBSelectList*)widget_)->SetSource(source ? source->GetTBItemSource() : NULL);
 }
 }
 
 
+void UISelectList::ScrollToSelectedItem()
+{
+    if (!widget_)
+        return;
+
+    ((TBSelectList*)widget_)->ScrollToSelectedItem();
+
+}
+
+void UISelectList::HandleUIUpdate(StringHash eventType, VariantMap& eventData)
+{
+    if (!widget_)
+        return;
+
+    // if we have a drag and drop item, auto scroll if top/bottom
+
+    UIDragDrop* dragDrop = GetSubsystem<UIDragDrop>();
+
+    if (dragDrop->GetDraggingObject())
+    {
+        TBSelectList* select = (TBSelectList*) widget_;
+        Input* input = GetSubsystem<Input>();
+        IntVector2 pos = input->GetMousePosition();
+        select->ConvertFromRoot(pos.x_, pos.y_);
+
+        if ((select->GetHitStatus(pos.x_, pos.y_) != WIDGET_HIT_STATUS_NO_HIT))
+        {
+
+            // Adjust speed based on pixel distance from top and bottom
+            int value = pos.y_;
+
+            if (value > 16)
+                value = select->GetRect().h - pos.y_;
+
+            if (value > 16)
+                return;
+
+            int speed = 0;
+
+            if (value <= 16)
+                speed = -2;
+            if (value < 8)
+                speed = -4;
+
+            if (pos.y_ > 16)
+                speed = -speed;
+
+            if (speed)
+                select->GetScrollContainer()->ScrollBy(0, speed);
+
+        }
+
+    }
+
+}
+
 bool UISelectList::OnEvent(const tb::TBWidgetEvent &ev)
 bool UISelectList::OnEvent(const tb::TBWidgetEvent &ev)
 {
 {
+    if (ev.type == EVENT_TYPE_POINTER_DOWN)
+    {
+        GetTBSelectList()->SetFocus(WIDGET_FOCUS_REASON_POINTER);
+    }
     return UIWidget::OnEvent(ev);
     return UIWidget::OnEvent(ev);
 }
 }
 
 
+void UISelectList::SelectNextItem()
+{
+    if (!widget_)
+        return;
+    
+    ((TBSelectList*)widget_)->ChangeValue(TB_KEY_DOWN);
+}
+
+void UISelectList::SelectPreviousItem()
+{
+    if (!widget_)
+        return;
+
+    ((TBSelectList*)widget_)->ChangeValue(TB_KEY_UP);
+}
+
 }
 }

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

@@ -53,10 +53,17 @@ public:
     String GetHoverItemID();
     String GetHoverItemID();
     String GetSelectedItemID();
     String GetSelectedItemID();
 
 
+    void ScrollToSelectedItem();
+
     tb::TBSelectList* GetTBSelectList();
     tb::TBSelectList* GetTBSelectList();
 
 
+    void SelectNextItem();
+    void SelectPreviousItem();
+
 protected:
 protected:
 
 
+    void HandleUIUpdate(StringHash eventType, VariantMap& eventData);
+
     virtual bool OnEvent(const tb::TBWidgetEvent &ev);
     virtual bool OnEvent(const tb::TBWidgetEvent &ev);
 
 
 private:
 private:

+ 32 - 1
Source/Atomic/UI/UIWidget.cpp

@@ -39,7 +39,8 @@ namespace Atomic
 
 
 UIWidget::UIWidget(Context* context, bool createWidget) : Object(context),
 UIWidget::UIWidget(Context* context, bool createWidget) : Object(context),
     widget_(0),
     widget_(0),
-    preferredSize_(new UIPreferredSize())
+    preferredSize_(new UIPreferredSize()),
+    multiTouch_(false)
 {
 {
     AddRef();
     AddRef();
 
 
@@ -147,6 +148,18 @@ void UIWidget::ConvertEvent(UIWidget *handler, UIWidget* target, const tb::TBWid
         case TB_KEY_DELETE:
         case TB_KEY_DELETE:
             key = KEY_DELETE;
             key = KEY_DELETE;
             break;
             break;
+        case TB_KEY_DOWN:
+            key = KEY_DOWN;
+            break;
+        case TB_KEY_UP:
+            key = KEY_UP;
+            break;
+        case TB_KEY_LEFT:
+            key = KEY_LEFT;
+            break;
+        case TB_KEY_RIGHT:
+            key = KEY_RIGHT;
+            break;
         default:
         default:
             break;
             break;
         }
         }
@@ -738,6 +751,24 @@ bool UIWidget::OnEvent(const tb::TBWidgetEvent &ev)
     return false;
     return false;
 }
 }
 
 
+bool UIWidget::GetCaptured()
+{
+    if (!widget_)
+        return false;
+
+    return widget_->IsCaptured();
+
+}
+
+void UIWidget::SetCapturing(bool capturing)
+{
+    if (!widget_)
+        return;
+
+    widget_->SetCapturing(capturing);
+}
+
+
 void UIWidget::InvalidateLayout()
 void UIWidget::InvalidateLayout()
 {
 {
     if (!widget_)
     if (!widget_)

+ 2 - 2
Source/Atomic/UI/UIWidget.h

@@ -247,8 +247,9 @@ class UIWidget : public Object, public tb::TBWidgetDelegate
 
 
     bool IsMultiTouch() { return multiTouch_; }
     bool IsMultiTouch() { return multiTouch_; }
 
 
-    void SetCapturing(bool capturing) { widget_->SetCapturing(capturing); }
+    bool GetCaptured();
 
 
+    void SetCapturing(bool capturing);
     bool GetCapturing() { return widget_->GetCapturing(); }
     bool GetCapturing() { return widget_->GetCapturing(); }
 
 
     void InvokeShortcut(const String& shortcut);
     void InvokeShortcut(const String& shortcut);
@@ -269,7 +270,6 @@ protected:
 
 
     SharedPtr<UIDragObject> dragObject_;
     SharedPtr<UIDragObject> dragObject_;
 
 
-
     bool multiTouch_;
     bool multiTouch_;
 
 
 };
 };

+ 0 - 6
Source/AtomicEditor/Application/AEEditorApp.cpp

@@ -55,7 +55,6 @@ void AEEditorApp::Start()
     ui->Initialize("AtomicEditor/resources/language/lng_en.tb.txt");
     ui->Initialize("AtomicEditor/resources/language/lng_en.tb.txt");
 
 
     SubscribeToEvent(E_JSERROR, HANDLER(AEEditorApp, HandleJSError));
     SubscribeToEvent(E_JSERROR, HANDLER(AEEditorApp, HandleJSError));
-    SubscribeToEvent(E_EXITREQUESTED, HANDLER(AEEditorApp, HandleExitRequested));
 
 
     jsapi_init_editor(vm_);
     jsapi_init_editor(vm_);
 
 
@@ -118,11 +117,6 @@ void AEEditorApp::Stop()
     AEEditorCommon::Stop();
     AEEditorCommon::Stop();
 }
 }
 
 
-void AEEditorApp::HandleExitRequested(StringHash eventType, VariantMap& eventData)
-{
-
-}
-
 void AEEditorApp::HandleJSError(StringHash eventType, VariantMap& eventData)
 void AEEditorApp::HandleJSError(StringHash eventType, VariantMap& eventData)
 {
 {
     using namespace JSError;
     using namespace JSError;

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

@@ -115,6 +115,13 @@ void AEPlayerApplication::Setup()
 
 
                 value = AddTrailingSlash(value);
                 value = AddTrailingSlash(value);
 
 
+                // check that cache exists
+                if (!filesystem->DirExists(value + "Cache"))
+                {
+                    ErrorExit("Project cache folder does not exist, projects must be loaded into the Atomic Editor at least once before using the --player command line mode");
+                    return;
+                }
+
 #ifdef ATOMIC_DEV_BUILD
 #ifdef ATOMIC_DEV_BUILD
 
 
                 String resourcePaths = ToString("%s/Resources/CoreData;%s/Resources/PlayerData;%sResources;%s;%sCache",
                 String resourcePaths = ToString("%s/Resources/CoreData;%s/Resources/PlayerData;%sResources;%s;%sCache",

+ 3 - 45
Source/AtomicEditor/Editors/JSResourceEditor.cpp

@@ -38,7 +38,6 @@ JSResourceEditor ::JSResourceEditor(Context* context, const String &fullpath, UI
     autocomplete_(0),
     autocomplete_(0),
     textDirty_(true),
     textDirty_(true),
     textDelta_(0.0f),
     textDelta_(0.0f),
-    modified_(false),
     currentFindPos_(-1)
     currentFindPos_(-1)
 {
 {
 
 
@@ -221,11 +220,8 @@ void JSResourceEditor::OnChange(TBStyleEdit* styleEdit)
 {
 {
     textDelta_ = 0.25f;
     textDelta_ = 0.25f;
     textDirty_ = true;
     textDirty_ = true;
-    modified_ = true;
 
 
-    String filename = GetFileNameAndExtension(fullpath_);
-    filename += "*";
-    button_->SetText(filename.CString());
+    SetModified(true);
 
 
     autocomplete_->Hide();
     autocomplete_->Hide();
 
 
@@ -270,19 +266,7 @@ bool JSResourceEditor::OnEvent(const TBWidgetEvent &ev)
     {
     {
         if (ev.ref_id == TBIDC("close"))
         if (ev.ref_id == TBIDC("close"))
         {
         {
-            if (modified_)
-            {
-                TBMessageWindow *msg_win = new TBMessageWindow(container_->GetInternalWidget(), TBIDC("unsaved_jsmodifications_dialog"));
-                TBMessageWindowSettings settings(TB_MSG_OK_CANCEL, TBID(uint32(0)));
-                settings.dimmer = true;
-                settings.styling = true;
-                msg_win->Show("Unsaved Modifications", "There are unsaved modications.\nDo you wish to discard them and close?", &settings, 640, 360);
-            }
-            else
-            {
-                Close();
-            }
-
+            RequestClose();
         }
         }
 
 
         if (ev.ref_id == TBIDC("find"))
         if (ev.ref_id == TBIDC("find"))
@@ -318,24 +302,6 @@ bool JSResourceEditor::OnEvent(const TBWidgetEvent &ev)
         }
         }
     }
     }
 
 
-    if (ev.type == EVENT_TYPE_CLICK)
-    {
-        if (ev.target->GetID() == TBIDC("unsaved_jsmodifications_dialog"))
-        {
-            if (ev.ref_id == TBIDC("TBMessageWindow.ok"))
-            {
-                Close();
-            }
-            else
-            {
-                SetFocus();
-            }
-
-            return true;
-        }
-
-    }
-
     return false;
     return false;
 }
 }
 
 
@@ -525,12 +491,6 @@ void JSResourceEditor::GotoLineNumber(int lineNumber)
 
 
 }
 }
 
 
-
-bool JSResourceEditor::HasUnsavedModifications()
-{
-    return modified_;
-}
-
 bool JSResourceEditor::ParseJavascriptToJSON(const char* source, String& json, bool loose)
 bool JSResourceEditor::ParseJavascriptToJSON(const char* source, String& json, bool loose)
 {
 {
 
 
@@ -621,9 +581,7 @@ bool JSResourceEditor::Save()
     file.Write((void*) text.CStr(), text.Length());
     file.Write((void*) text.CStr(), text.Length());
     file.Close();
     file.Close();
 
 
-    String filename = GetFileNameAndExtension(fullpath_);
-    button_->SetText(filename.CString());
-    modified_ = false;
+    SetModified(false);
 
 
     return true;
     return true;
 
 

+ 1 - 5
Source/AtomicEditor/Editors/JSResourceEditor.h

@@ -18,7 +18,7 @@ using namespace tb;
 namespace AtomicEditor
 namespace AtomicEditor
 {
 {
 
 
-class JSAutocomplete;
+class JSAutocomplete; 
 
 
 class JSResourceEditor: public ResourceEditor, public TBStyleEditTextChangeListener
 class JSResourceEditor: public ResourceEditor, public TBStyleEditTextChangeListener
 {
 {
@@ -45,8 +45,6 @@ public:
 
 
     void SetFocus();
     void SetFocus();
 
 
-    bool HasUnsavedModifications();
-
     bool Save();
     bool Save();
 
 
 private:
 private:
@@ -64,8 +62,6 @@ private:
     float textDelta_;
     float textDelta_;
     bool textDirty_;
     bool textDirty_;
 
 
-    bool modified_;
-
     int currentFindPos_;
     int currentFindPos_;
 
 
 };
 };

+ 69 - 8
Source/AtomicEditor/Editors/ResourceEditor.cpp

@@ -7,9 +7,14 @@
 
 
 #include <TurboBadger/tb_tab_container.h>
 #include <TurboBadger/tb_tab_container.h>
 
 
+#include <Atomic/Core/StringUtils.h>
 #include <Atomic/IO/FileSystem.h>
 #include <Atomic/IO/FileSystem.h>
 #include <Atomic/Resource/ResourceEvents.h>
 #include <Atomic/Resource/ResourceEvents.h>
 
 
+#include <TurboBadger/tb_message_window.h>
+
+#include "ResourceEditorEvents.h"
+
 #include "ResourceEditor.h"
 #include "ResourceEditor.h"
 
 
 //#include "../UI/UIMainFrame.h"
 //#include "../UI/UIMainFrame.h"
@@ -32,17 +37,52 @@ public:
         button_->SetValue(value);
         button_->SetValue(value);
     }
     }
 
 
+    bool RequestClose()
+    {
+        if (editor_->HasUnsavedModifications())
+        {
+            TBMessageWindow *msg_win = new TBMessageWindow(this, TBIDC("unsaved_modifications_dialog"));
+            TBMessageWindowSettings settings(TB_MSG_OK_CANCEL, TBID(uint32(0)));
+            settings.dimmer = true;
+            settings.styling = true;
+            String windowString = Atomic::ToString("%s has unsaved modifications.\nDo you wish to discard them and close?", GetFileNameAndExtension(editor_->GetFullPath()).CString());
+            msg_win->Show("Unsaved Modifications",  windowString.CString(), &settings, 640, 360);
+            return false;
+        }
+        else
+        {
+            editor_->Close(container_->GetNumPages()>1);
+            return true;
+        }
+    }
+
     bool OnEvent(const TBWidgetEvent &ev)
     bool OnEvent(const TBWidgetEvent &ev)
     {
     {
         if (ev.type == EVENT_TYPE_CLICK || ev.type == EVENT_TYPE_POINTER_DOWN)
         if (ev.type == EVENT_TYPE_CLICK || ev.type == EVENT_TYPE_POINTER_DOWN)
         {
         {
-            if (ev.target->GetID() == TBIDC("tabclose"))
+            if (ev.target->GetID() == TBIDC("unsaved_modifications_dialog"))
             {
             {
-                container_->OnEvent(ev);
-                editor_->Close();
+                if (ev.ref_id == TBIDC("TBMessageWindow.ok"))
+                {
+                    container_->OnEvent(ev);
+                    editor_->Close(container_->GetNumPages()>1);
+                }
+                else
+                {
+                    editor_->SendEvent(E_EDITORRESOURCECLOSECANCELED);
+                    SetFocus(WIDGET_FOCUS_REASON_UNKNOWN);
+                }
                 return true;
                 return true;
             }
             }
-            else
+            if (ev.target->GetID() == TBIDC("tabclose"))
+            {
+                if (RequestClose())
+                {
+                    container_->OnEvent(ev);
+                    return true;
+                }
+            }
+            else 
             {
             {
                 TBWidgetEvent nevent = ev;
                 TBWidgetEvent nevent = ev;
                 nevent.target = this;
                 nevent.target = this;
@@ -56,7 +96,7 @@ public:
 
 
 ResourceEditor::ResourceEditor(Context* context, const String& fullpath, UITabContainer *container):
 ResourceEditor::ResourceEditor(Context* context, const String& fullpath, UITabContainer *container):
     Object(context), fullpath_(fullpath), container_(container),
     Object(context), fullpath_(fullpath), container_(container),
-    editorTabLayout_(0), rootContentWidget_(0), button_(0)
+    editorTabLayout_(0), rootContentWidget_(0), button_(0), modified_(false)
 {
 {
 
 
     String filename = GetFileNameAndExtension(fullpath_);
     String filename = GetFileNameAndExtension(fullpath_);
@@ -112,6 +152,11 @@ void ResourceEditor::HandleFileChanged(StringHash eventType, VariantMap& eventDa
     */
     */
 }
 }
 
 
+void ResourceEditor::RequestClose()
+{
+    editorTabLayout_->RequestClose();
+}
+
 void ResourceEditor::Close(bool navigateToAvailableResource)
 void ResourceEditor::Close(bool navigateToAvailableResource)
 {
 {
     // keep us alive through the close
     // keep us alive through the close
@@ -120,9 +165,9 @@ void ResourceEditor::Close(bool navigateToAvailableResource)
     ((TBTabContainer*)container_->GetInternalWidget())->GetTabLayout()->RemoveChild(editorTabLayout_);
     ((TBTabContainer*)container_->GetInternalWidget())->GetTabLayout()->RemoveChild(editorTabLayout_);
 
 
     VariantMap data;
     VariantMap data;
-    data["Editor"] = this;
-    data["NavigateToAvailableResource"] = navigateToAvailableResource;
-    SendEvent("EditorCloseResource", data);
+    data[EditorResourceClose::P_EDITOR] = this;
+    data[EditorResourceClose::P_NAVIGATE] = navigateToAvailableResource;
+    SendEvent(E_EDITORRESOURCECLOSE, data);
 }
 }
 
 
 void ResourceEditor::InvokeShortcut(const String& shortcut)
 void ResourceEditor::InvokeShortcut(const String& shortcut)
@@ -132,4 +177,20 @@ void ResourceEditor::InvokeShortcut(const String& shortcut)
     OnEvent(ev);
     OnEvent(ev);
 }
 }
 
 
+void ResourceEditor::SetModified(bool modified)
+{
+    modified_ = modified;
+    if (modified)
+    {
+        String filename = GetFileNameAndExtension(fullpath_);
+        filename += "*";
+        button_->SetText(filename.CString());
+    }
+    else
+    {
+        String filename = GetFileNameAndExtension(fullpath_);
+        button_->SetText(filename.CString());
+    }
+}
+
 }
 }

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

@@ -32,7 +32,7 @@ public:
 
 
     UIButton* GetButton() { return button_; }
     UIButton* GetButton() { return button_; }
 
 
-    virtual bool HasUnsavedModifications() { return false; }
+    virtual bool HasUnsavedModifications() { return modified_; }
 
 
     virtual void SetFocus() {}
     virtual void SetFocus() {}
     virtual void Close(bool navigateToAvailableResource = true);
     virtual void Close(bool navigateToAvailableResource = true);
@@ -46,16 +46,25 @@ public:
 
 
     const String& GetFullPath() { return fullpath_; }
     const String& GetFullPath() { return fullpath_; }
 
 
+    virtual void Undo() {}
+    virtual void Redo() {}
+
     virtual bool Save() { return true; }
     virtual bool Save() { return true; }
 
 
     UIWidget* GetRootContentWidget() { return rootContentWidget_; }
     UIWidget* GetRootContentWidget() { return rootContentWidget_; }
 
 
     void InvokeShortcut(const String& shortcut);
     void InvokeShortcut(const String& shortcut);
 
 
+    void RequestClose();
+
+    void SetModified(bool modified);
+
 protected:
 protected:
 
 
     String fullpath_;
     String fullpath_;
 
 
+    bool modified_;
+
     EditorTabLayout* editorTabLayout_;
     EditorTabLayout* editorTabLayout_;
 
 
     SharedPtr<UITabContainer> container_;
     SharedPtr<UITabContainer> container_;

+ 26 - 0
Source/AtomicEditor/Editors/ResourceEditorEvents.h

@@ -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
+//
+
+#pragma once
+
+#include <Atomic/Core/Object.h>
+
+namespace AtomicEditor
+{
+
+EVENT(E_EDITORRESOURCECLOSE, EditorResourceClose)
+{
+PARAM(P_EDITOR, Editor);            // ResourceEditor
+PARAM(P_NAVIGATE, NavigateToAvailableResource); // bool
+}
+
+EVENT(E_EDITORRESOURCECLOSECANCELED, EditorResourceCloseCanceled)
+{
+
+}
+
+}

+ 113 - 27
Source/AtomicEditor/Editors/SceneEditor3D/Gizmo3D.cpp

@@ -21,7 +21,8 @@ namespace AtomicEditor
 {
 {
 
 
 
 
-Gizmo3D::Gizmo3D(Context* context) : Object(context)
+Gizmo3D::Gizmo3D(Context* context) : Object(context),
+    dragging_(false)
 {
 {
     ResourceCache* cache = GetSubsystem<ResourceCache>();
     ResourceCache* cache = GetSubsystem<ResourceCache>();
 
 
@@ -169,7 +170,18 @@ void Gizmo3D::Use()
     // Recalculate axes only when not left-dragging
     // Recalculate axes only when not left-dragging
     bool drag = input->GetMouseButtonDown(MOUSEB_LEFT);// && (Abs(input->GetMouseMoveX()) > 3 || Abs(input->GetMouseMoveY()) > 3);
     bool drag = input->GetMouseButtonDown(MOUSEB_LEFT);// && (Abs(input->GetMouseMoveX()) > 3 || Abs(input->GetMouseMoveY()) > 3);
     if (!drag)
     if (!drag)
+    {
+        if (dragging_)
+        {
+            VariantMap eventData;
+            eventData[SceneEditSerializable::P_SERIALIZABLE] = editNodes_->At(0);
+            eventData[SceneEditSerializable::P_OPERATION] = 1;
+            scene_->SendEvent(E_SCENEEDITSERIALIZABLE, eventData);
+            dragging_ = false;
+        }
+
         CalculateGizmoAxes();
         CalculateGizmoAxes();
+    }
 
 
     gizmoAxisX_.Update(cameraRay, scale, drag, camera_->GetNode());
     gizmoAxisX_.Update(cameraRay, scale, drag, camera_->GetNode());
     gizmoAxisY_.Update(cameraRay, scale, drag, camera_->GetNode());
     gizmoAxisY_.Update(cameraRay, scale, drag, camera_->GetNode());
@@ -209,7 +221,7 @@ void Gizmo3D::Use()
         gizmoAxisZ_.lastSelected_ = gizmoAxisZ_.selected_;
         gizmoAxisZ_.lastSelected_ = gizmoAxisZ_.selected_;
     }
     }
 
 
-    if (drag)
+    if (drag && Selected())
         Drag();
         Drag();
 
 
 }
 }
@@ -218,19 +230,27 @@ bool Gizmo3D::MoveEditNodes(Vector3 adjust)
 {
 {
     bool moved = false;
     bool moved = false;
 
 
+    Input* input = GetSubsystem<Input>();
+
+#ifdef ATOMIC_PLATFORM_OSX
+    bool moveSnap = input->GetKeyDown(KEY_LGUI) || input->GetKeyDown(KEY_RGUI);
+#else
+    bool moveSnap = input->GetKeyDown(KEY_LCTRL) || input->GetKeyDown(KEY_RCTRL);
+#endif
+
     if (adjust.Length() > M_EPSILON)
     if (adjust.Length() > M_EPSILON)
     {
     {
         for (unsigned i = 0; i < editNodes_->Size(); ++i)
         for (unsigned i = 0; i < editNodes_->Size(); ++i)
         {
         {
-            /*
             if (moveSnap)
             if (moveSnap)
             {
             {
-                float moveStepScaled = moveStep * snapScale;
-                adjust.x = Floor(adjust.x / moveStepScaled + 0.5) * moveStepScaled;
-                adjust.y = Floor(adjust.y / moveStepScaled + 0.5) * moveStepScaled;
-                adjust.z = Floor(adjust.z / moveStepScaled + 0.5) * moveStepScaled;
+                float moveStepScaled = snapTranslationX_;
+                adjust.x_ = floorf(adjust.x_ / moveStepScaled + 0.5) * moveStepScaled;
+                moveStepScaled = snapTranslationY_;
+                adjust.y_ = floorf(adjust.y_ / moveStepScaled + 0.5) * moveStepScaled;
+                moveStepScaled = snapTranslationZ_;
+                adjust.z_ = floorf(adjust.z_ / moveStepScaled + 0.5) * moveStepScaled;
             }
             }
-            */
 
 
             Node* node = editNodes_->At(i);
             Node* node = editNodes_->At(i);
             Vector3 nodeAdjust = adjust;
             Vector3 nodeAdjust = adjust;
@@ -261,15 +281,21 @@ bool Gizmo3D::RotateEditNodes(Vector3 adjust)
 {
 {
     bool moved = false;
     bool moved = false;
 
 
-    /*
+    Input* input = GetSubsystem<Input>();
+
+#ifdef ATOMIC_PLATFORM_OSX
+    bool rotateSnap = input->GetKeyDown(KEY_LGUI) || input->GetKeyDown(KEY_RGUI);
+#else
+    bool rotateSnap = input->GetKeyDown(KEY_LCTRL) || input->GetKeyDown(KEY_RCTRL);
+#endif
+
     if (rotateSnap)
     if (rotateSnap)
     {
     {
-        float rotateStepScaled = rotateStep * snapScale;
-        adjust.x = Floor(adjust.x / rotateStepScaled + 0.5) * rotateStepScaled;
-        adjust.y = Floor(adjust.y / rotateStepScaled + 0.5) * rotateStepScaled;
-        adjust.z = Floor(adjust.z / rotateStepScaled + 0.5) * rotateStepScaled;
+        float rotateStepScaled = snapRotation_;
+        adjust.x_ = floorf(adjust.x_ / rotateStepScaled + 0.5) * rotateStepScaled;
+        adjust.y_ = floorf(adjust.y_ / rotateStepScaled + 0.5) * rotateStepScaled;
+        adjust.z_ = floorf(adjust.z_ / rotateStepScaled + 0.5) * rotateStepScaled;
     }
     }
-    */
 
 
     if (adjust.Length() > M_EPSILON)
     if (adjust.Length() > M_EPSILON)
     {
     {
@@ -302,6 +328,15 @@ bool Gizmo3D::ScaleEditNodes(Vector3 adjust)
 {
 {
     bool moved = false;
     bool moved = false;
 
 
+    Input* input = GetSubsystem<Input>();
+
+#ifdef ATOMIC_PLATFORM_OSX
+    bool scaleSnap = input->GetKeyDown(KEY_LGUI) || input->GetKeyDown(KEY_RGUI);
+#else
+    bool scaleSnap = input->GetKeyDown(KEY_LCTRL) || input->GetKeyDown(KEY_RCTRL);
+#endif
+
+
     if (adjust.Length() > M_EPSILON)
     if (adjust.Length() > M_EPSILON)
     {
     {
         for (unsigned i = 0; i < editNodes_->Size(); ++i)
         for (unsigned i = 0; i < editNodes_->Size(); ++i)
@@ -311,28 +346,26 @@ bool Gizmo3D::ScaleEditNodes(Vector3 adjust)
             Vector3 scale = node->GetScale();
             Vector3 scale = node->GetScale();
             Vector3 oldScale = scale;
             Vector3 oldScale = scale;
 
 
-            if (true)//!scaleSnap)
+            if (!scaleSnap)
                 scale += adjust;
                 scale += adjust;
             else
             else
             {
             {
-                /*
-                float scaleStepScaled = scaleStep * snapScale;
-                if (adjust.x != 0)
+                float scaleStepScaled = snapScale_;
+                if (adjust.x_ != 0)
                 {
                 {
-                    scale.x += adjust.x * scaleStepScaled;
-                    scale.x = Floor(scale.x / scaleStepScaled + 0.5) * scaleStepScaled;
+                    scale.x_ += adjust.x_ * scaleStepScaled;
+                    scale.x_ = floorf(scale.x_ / scaleStepScaled + 0.5) * scaleStepScaled;
                 }
                 }
-                if (adjust.y != 0)
+                if (adjust.y_ != 0)
                 {
                 {
-                    scale.y += adjust.y * scaleStepScaled;
-                    scale.y = Floor(scale.y / scaleStepScaled + 0.5) * scaleStepScaled;
+                    scale.y_ += adjust.y_ * scaleStepScaled;
+                    scale.y_ = floorf(scale.y_ / scaleStepScaled + 0.5) * scaleStepScaled;
                 }
                 }
-                if (adjust.z != 0)
+                if (adjust.z_ != 0)
                 {
                 {
-                    scale.z += adjust.z * scaleStepScaled;
-                    scale.z = Floor(scale.z / scaleStepScaled + 0.5) * scaleStepScaled;
+                    scale.z_ += adjust.z_ * scaleStepScaled;
+                    scale.z_ = floorf(scale.z_ / scaleStepScaled + 0.5) * scaleStepScaled;
                 }
                 }
-                */
             }
             }
 
 
             if (scale != oldScale)
             if (scale != oldScale)
@@ -359,6 +392,8 @@ void Gizmo3D::Drag()
 {
 {
     bool moved = false;
     bool moved = false;
 
 
+    dragging_ = true;
+
     float scale = gizmoNode_->GetScale().x_;
     float scale = gizmoNode_->GetScale().x_;
 
 
     if (editMode_ == EDIT_MOVE)
     if (editMode_ == EDIT_MOVE)
@@ -428,6 +463,7 @@ void Gizmo3D::SetEditMode(EditMode mode)
 
 
 void Gizmo3D::Hide()
 void Gizmo3D::Hide()
 {
 {
+    gizmoAxisX_.selected_ = gizmoAxisY_.selected_ = gizmoAxisZ_.selected_ = false;
     gizmo_->SetEnabled(false);
     gizmo_->SetEnabled(false);
 }
 }
 
 
@@ -446,4 +482,54 @@ void Gizmo3D::Show()
 
 
 }
 }
 
 
+float Gizmo3D::GetSnapTranslationX() const
+{
+    return snapTranslationX_;
+}
+
+float Gizmo3D::GetSnapTranslationY() const
+{
+    return snapTranslationY_;
+}
+
+float Gizmo3D::GetSnapTranslationZ() const
+{
+    return snapTranslationZ_;
+}
+
+float Gizmo3D::GetSnapRotation() const
+{
+    return snapRotation_;
+}
+
+float Gizmo3D::GetSnapScale() const
+{
+    return snapScale_;
+}
+
+void Gizmo3D::SetSnapTranslationX(float value)
+{
+    snapTranslationX_ = value;
+}
+
+void Gizmo3D::SetSnapTranslationY(float value)
+{
+    snapTranslationY_ = value;
+}
+
+void Gizmo3D::SetSnapTranslationZ(float value)
+{
+    snapTranslationZ_ = value;
+}
+
+void Gizmo3D::SetSnapRotation(float value)
+{
+    snapRotation_ = value;
+}
+
+void Gizmo3D::SetSnapScale(float value)
+{
+    snapScale_ = value;
+}
+
 }
 }

+ 21 - 0
Source/AtomicEditor/Editors/SceneEditor3D/Gizmo3D.h

@@ -110,6 +110,8 @@ public:
     void SetView(SceneView3D* view3D);
     void SetView(SceneView3D* view3D);
 
 
     void SetAxisMode(AxisMode mode);
     void SetAxisMode(AxisMode mode);
+    AxisMode GetAxisMode() const { return axisMode_; }
+
     void SetEditMode(EditMode mode);
     void SetEditMode(EditMode mode);
 
 
     bool Selected()
     bool Selected()
@@ -123,6 +125,18 @@ public:
 
 
     Node* GetGizmoNode() { return gizmoNode_; }
     Node* GetGizmoNode() { return gizmoNode_; }
 
 
+    float GetSnapTranslationX() const;
+    float GetSnapTranslationY() const;
+    float GetSnapTranslationZ() const;
+    float GetSnapRotation() const;
+    float GetSnapScale() const;
+
+    void SetSnapTranslationX(float value);
+    void SetSnapTranslationY(float value);
+    void SetSnapTranslationZ(float value);
+    void SetSnapRotation(float value);
+    void SetSnapScale(float value);
+
 private:
 private:
 
 
     void Position();
     void Position();
@@ -152,6 +166,13 @@ private:
     AxisMode axisMode_;
     AxisMode axisMode_;
 
 
     Vector<Node *> *editNodes_;
     Vector<Node *> *editNodes_;
+    bool dragging_;
+
+    float snapTranslationX_;
+    float snapTranslationY_;
+    float snapTranslationZ_;
+    float snapRotation_;
+    float snapScale_;
 
 
 };
 };
 
 

+ 138 - 0
Source/AtomicEditor/Editors/SceneEditor3D/SceneEditHistory.cpp

@@ -0,0 +1,138 @@
+
+#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 "SceneEditOps.h"
+#include "SceneEditor3DEvents.h"
+#include "SceneEditHistory.h"
+
+namespace AtomicEditor
+{
+
+SceneEditHistory::SceneEditHistory(Context* context, Scene* scene) :
+    Object(context),
+    scene_(scene)
+{
+    SubscribeToEvent(scene_, E_SCENEEDITSERIALIZABLE, HANDLER(SceneEditHistory, HandleSceneEditSerializable));    
+    SubscribeToEvent(scene_, E_SCENEEDITNODEADDEDREMOVED, HANDLER(SceneEditHistory, HandleSceneEditNodeAddedRemoved));
+}
+
+SceneEditHistory::~SceneEditHistory()
+{
+
+}
+
+void SceneEditHistory::AddUndoOp(SceneEditOp* op)
+{
+    undoHistory_.Push(op);
+
+    scene_->SendEvent(E_SCENEEDITSCENEMODIFIED);
+
+    for (unsigned i = 0; i < redoHistory_.Size(); i++)
+    {
+        delete redoHistory_[i];
+    }
+
+    redoHistory_.Clear();
+}
+
+void SceneEditHistory::HandleSceneEditSerializableUndoRedo(StringHash eventType, VariantMap& eventData)
+{
+    SharedPtr<Serializable> serial(static_cast<Serializable*>(eventData[SceneEditSerializableUndoRedo::P_SERIALIZABLE].GetPtr()));
+
+    if (editStates_.Contains(serial))
+    {
+        scene_->SendEvent(E_SCENEEDITSCENEMODIFIED);
+        editStates_[serial] = eventData[SceneEditSerializableUndoRedo::P_STATE].GetVectorBuffer();
+    }
+}
+
+void SceneEditHistory::HandleSceneEditNodeAddedRemoved(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);
+}
+
+void SceneEditHistory::HandleSceneEditSerializable(StringHash eventType, VariantMap& eventData)
+{
+
+    int editop = eventData[SceneEditSerializable::P_OPERATION].GetInt();
+
+    SharedPtr<Serializable> serial(static_cast<Serializable*>(eventData[SceneEditSerializable::P_SERIALIZABLE].GetPtr()));
+
+    if (editop == 0) // begin
+    {
+        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);
+    }
+    else if (editop == 1) // change
+    {
+        if (!editStates_.Contains(serial))
+            return;
+
+        VectorBuffer& beginState = editStates_[serial];
+        VectorBuffer deltaState;
+        serial->Serializable::Save(deltaState);
+        deltaState.Seek(0);
+
+        if (beginState.GetSize() != deltaState.GetSize() ||
+                memcmp(beginState.GetData(), deltaState.GetData(), deltaState.GetSize()))
+        {
+            SerializableEditOp* op = new SerializableEditOp(serial, beginState, deltaState);
+            AddUndoOp(op);
+            editStates_[serial] = deltaState;
+        }
+    }
+    else if (editop == 2) // end
+    {
+        if (!editStates_.Contains(serial))
+            return;
+
+        UnsubscribeFromEvent(serial, E_SCENEEDITSERIALIZABLEUNDOREDO);
+
+        editStates_.Erase(serial);
+    }
+}
+
+void SceneEditHistory::Undo()
+{
+    if (!undoHistory_.Size())
+        return;
+
+    SceneEditOp* op = undoHistory_.Back();
+    undoHistory_.Pop();
+
+    op->Undo();
+
+    redoHistory_.Push(op);
+
+}
+
+void SceneEditHistory::Redo()
+{
+    if (!redoHistory_.Size())
+        return;
+
+    SceneEditOp* op = redoHistory_.Back();
+    redoHistory_.Pop();
+
+    op->Redo();
+
+    undoHistory_.Push(op);
+
+}
+
+}

+ 56 - 0
Source/AtomicEditor/Editors/SceneEditor3D/SceneEditHistory.h

@@ -0,0 +1,56 @@
+//
+// 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 Serializable;
+class Scene;
+
+}
+
+namespace AtomicEditor
+{
+
+class SceneEditOp;
+
+/// Simple scene history to support undo/redo via snapshots of scene state
+class SceneEditHistory: public Object
+{
+    OBJECT(SceneEditHistory);
+
+public:
+
+    SceneEditHistory(Context* context, Scene* scene);
+    virtual ~SceneEditHistory();
+
+    void Undo();
+    void Redo();
+
+private:
+
+    void HandleSceneEditSerializable(StringHash eventType, VariantMap& eventData);
+    void HandleSceneEditSerializableUndoRedo(StringHash eventType, VariantMap& eventData);
+    void HandleSceneEditNodeAddedRemoved(StringHash eventType, VariantMap& eventData);
+
+    void AddUndoOp(SceneEditOp* op);
+
+    WeakPtr<Scene> scene_;
+
+    HashMap< SharedPtr<Serializable>, VectorBuffer > editStates_;
+
+    PODVector<SceneEditOp*> undoHistory_;
+    PODVector<SceneEditOp*> redoHistory_;
+
+};
+
+}

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

@@ -0,0 +1,109 @@
+
+#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;
+}
+
+}

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

@@ -0,0 +1,118 @@
+//
+// 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();
+};
+
+
+}

+ 135 - 11
Source/AtomicEditor/Editors/SceneEditor3D/SceneEditor3D.cpp

@@ -23,12 +23,18 @@
 #include <Atomic/Input/Input.h>
 #include <Atomic/Input/Input.h>
 #include <Atomic/UI/UI.h>
 #include <Atomic/UI/UI.h>
 
 
+#include <ToolCore/ToolSystem.h>
+#include <ToolCore/Project/Project.h>
+#include <ToolCore/Project/ProjectEvents.h>
+#include <ToolCore/Project/ProjectUserPrefs.h>
 #include <ToolCore/Assets/AssetDatabase.h>
 #include <ToolCore/Assets/AssetDatabase.h>
 #include <ToolCore/Assets/Asset.h>
 #include <ToolCore/Assets/Asset.h>
 
 
+
 #include "../../EditorMode/AEEditorEvents.h"
 #include "../../EditorMode/AEEditorEvents.h"
 
 
 #include "SceneEditor3D.h"
 #include "SceneEditor3D.h"
+#include "SceneEditHistory.h"
 #include "SceneEditor3DEvents.h"
 #include "SceneEditor3DEvents.h"
 
 
 using namespace ToolCore;
 using namespace ToolCore;
@@ -39,6 +45,12 @@ namespace AtomicEditor
 SceneEditor3D ::SceneEditor3D(Context* context, const String &fullpath, UITabContainer *container) :
 SceneEditor3D ::SceneEditor3D(Context* context, const String &fullpath, UITabContainer *container) :
     ResourceEditor(context, fullpath, container)
     ResourceEditor(context, fullpath, container)
 {
 {
+
+    // store a local reference to user project prefs
+    ToolSystem* tsystem = GetSubsystem<ToolSystem>();
+    Project* project = tsystem->GetProject();
+    userPrefs_ = project->GetUserPrefs();
+
     ResourceCache* cache = GetSubsystem<ResourceCache>();
     ResourceCache* cache = GetSubsystem<ResourceCache>();
 
 
     scene_ = new Scene(context_);
     scene_ = new Scene(context_);
@@ -56,11 +68,11 @@ SceneEditor3D ::SceneEditor3D(Context* context, const String &fullpath, UITabCon
     // EARLY ACCESS
     // EARLY ACCESS
     if (fullpath.Find(String("ToonTown")) != String::NPOS)
     if (fullpath.Find(String("ToonTown")) != String::NPOS)
     {
     {
-          sceneView_->GetCameraNode()->SetWorldPosition(Vector3(-119.073f, 76.1121f, 16.47763f));
-          Quaternion q(0.55f, 0.14f,  0.8f, -0.2f);
-          sceneView_->SetYaw(q.YawAngle());
-          sceneView_->SetPitch(q.PitchAngle());
-          sceneView_->GetCameraNode()->SetWorldRotation(q);
+        sceneView_->GetCameraNode()->SetWorldPosition(Vector3(-119.073f, 76.1121f, 16.47763f));
+        Quaternion q(0.55f, 0.14f,  0.8f, -0.2f);
+        sceneView_->SetYaw(q.YawAngle());
+        sceneView_->SetPitch(q.PitchAngle());
+        sceneView_->GetCameraNode()->SetWorldRotation(q);
     }
     }
     else
     else
     {
     {
@@ -80,6 +92,7 @@ SceneEditor3D ::SceneEditor3D(Context* context, const String &fullpath, UITabCon
     gizmo3D_ = new Gizmo3D(context_);
     gizmo3D_ = new Gizmo3D(context_);
     gizmo3D_->SetView(sceneView_);
     gizmo3D_->SetView(sceneView_);
     gizmo3D_->Show();
     gizmo3D_->Show();
+    UpdateGizmoSnapSettings();
 
 
     SubscribeToEvent(E_UPDATE, HANDLER(SceneEditor3D, HandleUpdate));
     SubscribeToEvent(E_UPDATE, HANDLER(SceneEditor3D, HandleUpdate));
     SubscribeToEvent(E_EDITORACTIVENODECHANGE, HANDLER(SceneEditor3D, HandleEditorActiveNodeChange));
     SubscribeToEvent(E_EDITORACTIVENODECHANGE, HANDLER(SceneEditor3D, HandleEditorActiveNodeChange));
@@ -92,11 +105,18 @@ SceneEditor3D ::SceneEditor3D(Context* context, const String &fullpath, UITabCon
     IntRect rect = container_->GetContentRoot()->GetRect();
     IntRect rect = container_->GetContentRoot()->GetRect();
     rootContentWidget_->SetSize(rect.Width(), rect.Height());
     rootContentWidget_->SetSize(rect.Width(), rect.Height());
 
 
+    SubscribeToEvent(E_PROJECTUSERPREFSAVED, HANDLER(SceneEditor3D, HandleUserPrefSaved));
+
     SubscribeToEvent(E_EDITORPLAYSTARTED, HANDLER(SceneEditor3D, HandlePlayStarted));
     SubscribeToEvent(E_EDITORPLAYSTARTED, HANDLER(SceneEditor3D, HandlePlayStarted));
     SubscribeToEvent(E_EDITORPLAYSTOPPED, HANDLER(SceneEditor3D, HandlePlayStopped));
     SubscribeToEvent(E_EDITORPLAYSTOPPED, HANDLER(SceneEditor3D, HandlePlayStopped));
 
 
+    SubscribeToEvent(scene_, E_NODEADDED, HANDLER(SceneEditor3D, HandleNodeAdded));
     SubscribeToEvent(scene_, E_NODEREMOVED, HANDLER(SceneEditor3D, HandleNodeRemoved));
     SubscribeToEvent(scene_, E_NODEREMOVED, HANDLER(SceneEditor3D, HandleNodeRemoved));
 
 
+    SubscribeToEvent(scene_, E_SCENEEDITSCENEMODIFIED, HANDLER(SceneEditor3D, HandleSceneEditSceneModified));
+
+    editHistory_ = new SceneEditHistory(context_, scene_);
+
 }
 }
 
 
 SceneEditor3D::~SceneEditor3D()
 SceneEditor3D::~SceneEditor3D()
@@ -112,7 +132,12 @@ bool SceneEditor3D::OnEvent(const TBWidgetEvent &ev)
         {
         {
             if (selectedNode_)
             if (selectedNode_)
             {
             {
-                selectedNode_->RemoveAllComponents();
+                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_->Remove();
                 selectedNode_ = 0;
                 selectedNode_ = 0;
             }
             }
@@ -138,8 +163,30 @@ bool SceneEditor3D::OnEvent(const TBWidgetEvent &ev)
                 VariantMap eventData;
                 VariantMap eventData;
                 eventData[EditorActiveNodeChange::P_NODE] = pasteNode;
                 eventData[EditorActiveNodeChange::P_NODE] = pasteNode;
                 SendEvent(E_EDITORACTIVENODECHANGE, eventData);
                 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);
             }
             }
         }
         }
+        else if (ev.ref_id == TBIDC("close"))
+        {
+            RequestClose();
+            return true;
+        }
+        else if (ev.ref_id == TBIDC("undo"))
+        {
+            Undo();
+            return true;
+        }
+        else if (ev.ref_id == TBIDC("redo"))
+        {
+            Redo();
+            return true;
+        }
     }
     }
 
 
     if (ev.type == EVENT_TYPE_CLICK)
     if (ev.type == EVENT_TYPE_CLICK)
@@ -185,6 +232,15 @@ void SceneEditor3D::SelectNode(Node* node)
 
 
 }
 }
 
 
+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)
 void SceneEditor3D::HandleNodeRemoved(StringHash eventType, VariantMap& eventData)
 {
 {
     Node* node = (Node*) (eventData[NodeRemoved::P_NODE].GetPtr());
     Node* node = (Node*) (eventData[NodeRemoved::P_NODE].GetPtr());
@@ -192,7 +248,6 @@ void SceneEditor3D::HandleNodeRemoved(StringHash eventType, VariantMap& eventDat
         SelectNode(0);
         SelectNode(0);
 }
 }
 
 
-
 void SceneEditor3D::HandleUpdate(StringHash eventType, VariantMap& eventData)
 void SceneEditor3D::HandleUpdate(StringHash eventType, VariantMap& eventData)
 {
 {
     Vector<Node*> editNodes;
     Vector<Node*> editNodes;
@@ -219,15 +274,24 @@ void SceneEditor3D::HandlePlayStopped(StringHash eventType, VariantMap& eventDat
 }
 }
 
 
 void SceneEditor3D::HandleGizmoEditModeChanged(StringHash eventType, VariantMap& eventData)
 void SceneEditor3D::HandleGizmoEditModeChanged(StringHash eventType, VariantMap& eventData)
-{
-    EditMode mode = (EditMode) ((int)eventData[GizmoEditModeChanged::P_MODE].GetFloat());
+{    
+    EditMode mode = (EditMode) (eventData[GizmoEditModeChanged::P_MODE].GetInt());
     gizmo3D_->SetEditMode(mode);
     gizmo3D_->SetEditMode(mode);
 }
 }
 
 
 void SceneEditor3D::HandleGizmoAxisModeChanged(StringHash eventType, VariantMap& eventData)
 void SceneEditor3D::HandleGizmoAxisModeChanged(StringHash eventType, VariantMap& eventData)
 {
 {
-    AxisMode mode = (AxisMode) ((int)eventData[GizmoEditModeChanged::P_MODE].GetFloat());
-    gizmo3D_->SetAxisMode(mode);
+    AxisMode mode = (AxisMode) (eventData[GizmoAxisModeChanged::P_MODE].GetInt());
+    bool toggle = eventData[GizmoAxisModeChanged::P_TOGGLE].GetBool();
+    if (toggle)
+    {
+        AxisMode mode = gizmo3D_->GetAxisMode() == AXIS_WORLD ? AXIS_LOCAL : AXIS_WORLD;
+        VariantMap neventData;
+        neventData[GizmoAxisModeChanged::P_MODE] = (int) mode;
+        SendEvent(E_GIZMOAXISMODECHANGED, neventData);
+    }
+    else
+        gizmo3D_->SetAxisMode(mode);
 }
 }
 
 
 
 
@@ -251,8 +315,68 @@ bool SceneEditor3D::Save()
         file.Close();
         file.Close();
     }
     }
 
 
+    SetModified(false);
+
     return true;
     return true;
 
 
 }
 }
 
 
+void SceneEditor3D::Undo()
+{
+    editHistory_->Undo();
+}
+
+void SceneEditor3D::Redo()
+{
+    editHistory_->Redo();
+}
+
+void SceneEditor3D::HandleSceneEditSceneModified(StringHash eventType, VariantMap& eventData)
+{
+    SetModified(true);    
+}
+
+void SceneEditor3D::HandleUserPrefSaved(StringHash eventType, VariantMap& eventData)
+{
+    UpdateGizmoSnapSettings();
+}
+
+void SceneEditor3D::GetSelectionBoundingBox(BoundingBox& bbox)
+{
+    bbox.Clear();
+
+    if (selectedNode_.Null())
+        return;
+
+    // TODO: Adjust once multiple selection is in
+    if (selectedNode_.Null())
+        return;
+
+    // Get all the drawables, which define the bounding box of the selection
+    PODVector<Drawable*> drawables;
+    selectedNode_->GetDerivedComponents<Drawable>(drawables, true);
+
+    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 SceneEditor3D::UpdateGizmoSnapSettings()
+{
+    gizmo3D_->SetSnapTranslationX(userPrefs_->GetSnapTranslationX());
+    gizmo3D_->SetSnapTranslationY(userPrefs_->GetSnapTranslationY());
+    gizmo3D_->SetSnapTranslationZ(userPrefs_->GetSnapTranslationZ());
+    gizmo3D_->SetSnapRotation(userPrefs_->GetSnapRotation());
+    gizmo3D_->SetSnapScale(userPrefs_->GetSnapScale());
+
+}
+
 }
 }

+ 27 - 0
Source/AtomicEditor/Editors/SceneEditor3D/SceneEditor3D.h

@@ -27,9 +27,18 @@ class Octree;
 
 
 }
 }
 
 
+namespace ToolCore
+{
+    class ProjectUserPrefs;
+}
+
+using namespace ToolCore;
+
 namespace AtomicEditor
 namespace AtomicEditor
 {
 {
 
 
+class SceneEditHistory;
+
 class SceneEditor3D: public ResourceEditor
 class SceneEditor3D: public ResourceEditor
 {
 {
     OBJECT(SceneEditor3D);
     OBJECT(SceneEditor3D);
@@ -43,6 +52,7 @@ public:
     bool OnEvent(const TBWidgetEvent &ev);
     bool OnEvent(const TBWidgetEvent &ev);
 
 
     void SelectNode(Node* node);
     void SelectNode(Node* node);
+    void GetSelectionBoundingBox(BoundingBox& bbox);
 
 
     Scene* GetScene() { return scene_; }
     Scene* GetScene() { return scene_; }
     Gizmo3D* GetGizmo() { return gizmo3D_; }
     Gizmo3D* GetGizmo() { return gizmo3D_; }
@@ -54,6 +64,11 @@ public:
     void Close(bool navigateToAvailableResource = true);
     void Close(bool navigateToAvailableResource = true);
     bool Save();
     bool Save();
 
 
+    void Undo();
+    void Redo();
+
+    ProjectUserPrefs* GetUserPrefs();
+
 private:
 private:
 
 
     void HandleUpdate(StringHash eventType, VariantMap& eventData);
     void HandleUpdate(StringHash eventType, VariantMap& eventData);
@@ -62,8 +77,16 @@ private:
     void HandlePlayStopped(StringHash eventType, VariantMap& eventData);
     void HandlePlayStopped(StringHash eventType, VariantMap& eventData);
     void HandleGizmoEditModeChanged(StringHash eventType, VariantMap& eventData);
     void HandleGizmoEditModeChanged(StringHash eventType, VariantMap& eventData);
     void HandleGizmoAxisModeChanged(StringHash eventType, VariantMap& eventData);
     void HandleGizmoAxisModeChanged(StringHash eventType, VariantMap& eventData);
+
+    void HandleUserPrefSaved(StringHash eventType, VariantMap& eventData);
+
+    void HandleNodeAdded(StringHash eventType, VariantMap& eventData);
     void HandleNodeRemoved(StringHash eventType, VariantMap& eventData);
     void HandleNodeRemoved(StringHash eventType, VariantMap& eventData);
 
 
+    void HandleSceneEditSceneModified(StringHash eventType, VariantMap& eventData);
+
+    void UpdateGizmoSnapSettings();
+
     SharedPtr<Scene> scene_;
     SharedPtr<Scene> scene_;
 
 
     // TODO: multiple views
     // TODO: multiple views
@@ -74,6 +97,10 @@ private:
     WeakPtr<Node> selectedNode_;
     WeakPtr<Node> selectedNode_;
     SharedPtr<Node> clipboardNode_;
     SharedPtr<Node> clipboardNode_;
 
 
+    SharedPtr<SceneEditHistory> editHistory_;
+
+    WeakPtr<ProjectUserPrefs> userPrefs_;
+
 };
 };
 
 
 }
 }

+ 37 - 2
Source/AtomicEditor/Editors/SceneEditor3D/SceneEditor3DEvents.h

@@ -15,12 +15,14 @@ namespace AtomicEditor
 /// Variable timestep scene update.
 /// Variable timestep scene update.
 EVENT(E_GIZMOEDITMODECHANGED, GizmoEditModeChanged)
 EVENT(E_GIZMOEDITMODECHANGED, GizmoEditModeChanged)
 {
 {
-    PARAM(P_MODE, MODE);            // int
+    PARAM(P_MODE, Mode);            // int
 }
 }
 
 
 EVENT(E_GIZMOAXISMODECHANGED, GizmoAxisModeChanged)
 EVENT(E_GIZMOAXISMODECHANGED, GizmoAxisModeChanged)
 {
 {
-    PARAM(P_MODE, MODE);            // int
+    PARAM(P_MODE, Mode);            // int
+    PARAM(P_TOGGLE, Toggle);            // bool
+
 }
 }
 
 
 EVENT(E_GIZMOMOVED, GizmoMoved)
 EVENT(E_GIZMOMOVED, GizmoMoved)
@@ -28,4 +30,37 @@ EVENT(E_GIZMOMOVED, GizmoMoved)
 
 
 }
 }
 
 
+EVENT(E_SCENEEDITSCENEMODIFIED, SceneEditSceneModified)
+{
+
+}
+
+EVENT(E_SCENEEDITNODEADDEDREMOVED, SceneEditNodeAddedRemoved)
+{
+    PARAM(P_SCENE, Scene);             // Scene
+    PARAM(P_NODE, Node);               // Node
+    PARAM(P_ADDED, Added);             // bool
+}
+
+EVENT(E_SCENEEDITCOMPONENTADDEDREMOVED, SceneEditComponentAddedRemoved)
+{
+    PARAM(P_SCENE, Scene);             // Scene
+    PARAM(P_COMPONENT, Component);     // Component
+    PARAM(P_ADDED, Added);             // bool
+}
+
+EVENT(E_SCENEEDITSERIALIZABLE, SceneEditSerializable)
+{
+    PARAM(P_SERIALIZABLE, Serializable);     // Serializable
+    PARAM(P_OPERATION, Operation);           // int (0: begin, 1: change, 2: end)
+}
+
+EVENT(E_SCENEEDITSERIALIZABLEUNDOREDO, SceneEditSerializableUndoRedo)
+{
+    PARAM(P_SERIALIZABLE, Serializable);     // Serializable
+    PARAM(P_STATE, State);                   // State (VectorBuffer);
+    PARAM(P_UNDO, Undo);                     // bool (true: undo, false: redo)
+}
+
+
 }
 }

+ 150 - 15
Source/AtomicEditor/Editors/SceneEditor3D/SceneView3D.cpp

@@ -44,6 +44,7 @@
 
 
 #include "SceneView3D.h"
 #include "SceneView3D.h"
 #include "SceneEditor3D.h"
 #include "SceneEditor3D.h"
+#include "SceneEditor3DEvents.h"
 
 
 using namespace ToolCore;
 using namespace ToolCore;
 
 
@@ -56,7 +57,8 @@ SceneView3D ::SceneView3D(Context* context, SceneEditor3D *sceneEditor) :
     pitch_(0.0f),
     pitch_(0.0f),
     mouseLeftDown_(false),
     mouseLeftDown_(false),
     mouseMoved_(false),
     mouseMoved_(false),
-    enabled_(true)
+    enabled_(true),
+    cameraMove_(false)
 {
 {
 
 
     sceneEditor_ = sceneEditor;
     sceneEditor_ = sceneEditor;
@@ -106,8 +108,10 @@ SceneView3D ::SceneView3D(Context* context, SceneEditor3D *sceneEditor) :
     SubscribeToEvent(this, E_DRAGEXITWIDGET, HANDLER(SceneView3D, HandleDragExitWidget));
     SubscribeToEvent(this, E_DRAGEXITWIDGET, HANDLER(SceneView3D, HandleDragExitWidget));
     SubscribeToEvent(this, E_DRAGENDED, HANDLER(SceneView3D, HandleDragEnded));
     SubscribeToEvent(this, E_DRAGENDED, HANDLER(SceneView3D, HandleDragEnded));
 
 
-    SetIsFocusable(true);
+    SubscribeToEvent(E_UIUNHANDLEDSHORTCUT, HANDLER(SceneView3D, HandleUIUnhandledShortcut));
+    SubscribeToEvent(E_UIWIDGETFOCUSESCAPED, HANDLER(SceneView3D, HandleUIWidgetFocusEscaped));
 
 
+    SetIsFocusable(true);
 
 
 }
 }
 
 
@@ -137,25 +141,44 @@ void SceneView3D::Disable()
 
 
 }
 }
 
 
-void SceneView3D::MoveCamera(float timeStep)
+bool SceneView3D::GetOrbitting()
+{
+    Input* input = GetSubsystem<Input>();
+    return framedNode_.NotNull() && MouseInView() && input->GetKeyDown(KEY_ALT) && input->GetMouseButtonDown(MOUSEB_LEFT);
+}
+
+bool SceneView3D::GetZooming()
 {
 {
+    Input* input = GetSubsystem<Input>();
+    return MouseInView() && input->GetKeyDown(KEY_ALT) && input->GetMouseMoveWheel();
+}
+
+
+void SceneView3D::MoveCamera(float timeStep)
+{    
     if (!enabled_ && !GetFocus())
     if (!enabled_ && !GetFocus())
         return;
         return;
 
 
     Input* input = GetSubsystem<Input>();
     Input* input = GetSubsystem<Input>();
+    bool shiftDown = false;
+    if (input->GetKeyDown(KEY_LSHIFT) || input->GetKeyDown(KEY_RSHIFT))
+        shiftDown = true;
+
+    bool mouseInView = MouseInView();
+    bool orbitting = GetOrbitting();
+    bool zooming = GetZooming();
 
 
     // Movement speed as world units per second
     // Movement speed as world units per second
     float MOVE_SPEED = 20.0f;
     float MOVE_SPEED = 20.0f;
     // Mouse sensitivity as degrees per pixel
     // Mouse sensitivity as degrees per pixel
     const float MOUSE_SENSITIVITY = 0.2f;
     const float MOUSE_SENSITIVITY = 0.2f;
 
 
-    if (input->GetKeyDown(KEY_LSHIFT) || input->GetKeyDown(KEY_RSHIFT))
+    if (shiftDown)
         MOVE_SPEED *= 3.0f;
         MOVE_SPEED *= 3.0f;
 
 
     // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
     // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
-    if (input->GetMouseButtonDown(MOUSEB_RIGHT))
+    if ((mouseInView && input->GetMouseButtonDown(MOUSEB_RIGHT)) || orbitting)
     {
     {
-
         SetFocus();
         SetFocus();
         IntVector2 mouseMove = input->GetMouseMove();
         IntVector2 mouseMove = input->GetMouseMove();
         yaw_ += MOUSE_SENSITIVITY * mouseMove.x_;
         yaw_ += MOUSE_SENSITIVITY * mouseMove.x_;
@@ -164,7 +187,32 @@ void SceneView3D::MoveCamera(float timeStep)
     }
     }
 
 
     // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
     // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
-    cameraNode_->SetRotation(Quaternion(pitch_, yaw_, 0.0f));
+    Quaternion q(pitch_, yaw_, 0.0f);
+
+    if (!zooming)
+        cameraNode_->SetRotation(q);
+
+    if (orbitting)
+    {
+        BoundingBox bbox;
+        sceneEditor_->GetSelectionBoundingBox(bbox);
+        if (bbox.defined_)
+        {
+            Vector3 centerPoint = bbox.Center();
+            Vector3 d = cameraNode_->GetWorldPosition() - centerPoint;
+            cameraNode_->SetWorldPosition(centerPoint - q * Vector3(0.0, 0.0, d.Length()));
+
+        }
+    }
+
+    if (zooming)
+    {
+        Ray ray = GetCameraRay();
+        Vector3 wpos = cameraNode_->GetWorldPosition();
+        wpos += ray.direction_ * (float (input->GetMouseMoveWheel()) * (shiftDown ? 0.6f : 0.2f));
+        cameraNode_->SetWorldPosition(wpos);
+    }
+
 
 
 #ifdef ATOMIC_PLATFORM_WINDOWS
 #ifdef ATOMIC_PLATFORM_WINDOWS
     bool superdown = input->GetKeyDown(KEY_LCTRL) || input->GetKeyDown(KEY_RCTRL);
     bool superdown = input->GetKeyDown(KEY_LCTRL) || input->GetKeyDown(KEY_RCTRL);
@@ -172,29 +220,61 @@ void SceneView3D::MoveCamera(float timeStep)
     bool superdown = input->GetKeyDown(KEY_LGUI) || input->GetKeyDown(KEY_RGUI);
     bool superdown = input->GetKeyDown(KEY_LGUI) || input->GetKeyDown(KEY_RGUI);
 #endif
 #endif
 
 
-    if (!superdown && input->GetMouseButtonDown(MOUSEB_RIGHT)) {
+    if (!orbitting && mouseInView && !superdown && input->GetMouseButtonDown(MOUSEB_RIGHT)) {
 
 
         // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
         // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
         // Use the Translate() function (default local space) to move relative to the node's orientation.
         // Use the Translate() function (default local space) to move relative to the node's orientation.
-        if (input->GetKeyDown('W'))
+        if (input->GetKeyDown(KEY_W))
         {
         {
             SetFocus();
             SetFocus();
             cameraNode_->Translate(Vector3::FORWARD * MOVE_SPEED * timeStep);
             cameraNode_->Translate(Vector3::FORWARD * MOVE_SPEED * timeStep);
         }
         }
-        if (input->GetKeyDown('S'))
+        if (input->GetKeyDown(KEY_S))
         {
         {
             SetFocus();
             SetFocus();
             cameraNode_->Translate(Vector3::BACK * MOVE_SPEED * timeStep);
             cameraNode_->Translate(Vector3::BACK * MOVE_SPEED * timeStep);
         }
         }
-        if (input->GetKeyDown('A'))
+        if (input->GetKeyDown(KEY_A))
         {   SetFocus();
         {   SetFocus();
             cameraNode_->Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
             cameraNode_->Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
         }
         }
-        if (input->GetKeyDown('D'))
+        if (input->GetKeyDown(KEY_D))
         {
         {
             SetFocus();
             SetFocus();
             cameraNode_->Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
             cameraNode_->Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
         }
         }
+        if (input->GetKeyDown(KEY_Q))
+        {
+            SetFocus();
+            cameraNode_->Translate(Vector3::UP * MOVE_SPEED * timeStep);
+        }
+        if (input->GetKeyDown(KEY_E))
+        {
+            SetFocus();
+            cameraNode_->Translate(Vector3::DOWN * MOVE_SPEED * timeStep);
+        }
+    }
+    else if (!superdown)
+    {
+        if (input->GetKeyPress(KEY_F))
+        {
+            FrameSelection();
+        }
+    }
+
+    if (cameraMove_)
+    {
+
+        cameraMoveTime_ += timeStep * 3.0f;
+
+        if (cameraMoveTime_ > 1.0f)
+        {
+            cameraMove_ = false;
+            cameraMoveTime_ = 1.0f;
+        }
+
+        Vector3 pos = cameraMoveStart_.Lerp(cameraMoveTarget_, cameraMoveTime_);
+        cameraNode_->SetWorldPosition(pos);
 
 
     }
     }
 }
 }
@@ -260,6 +340,29 @@ bool SceneView3D::MouseInView()
 
 
 }
 }
 
 
+void SceneView3D::HandleUIUnhandledShortcut(StringHash eventType, VariantMap& eventData)
+{
+    if (!enabled_)
+        return;
+
+    unsigned id = eventData[UIUnhandledShortcut::P_REFID].GetUInt();
+
+    if (id == TBIDC("undo"))
+        sceneEditor_->Undo();
+    else if (id == TBIDC("redo"))
+        sceneEditor_->Redo();
+
+    return;
+
+}
+
+void SceneView3D::HandleUIWidgetFocusEscaped(StringHash eventType, VariantMap& eventData)
+{
+    if (!enabled_)
+        return;
+
+    SetFocus();
+}
 
 
 void SceneView3D::HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
 void SceneView3D::HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
 {
 {
@@ -271,7 +374,7 @@ void SceneView3D::HandlePostRenderUpdate(StringHash eventType, VariantMap& event
 
 
     }
     }
 
 
-    if (!MouseInView())
+    if (!MouseInView() || GetOrbitting())
         return;
         return;
 
 
     Input* input = GetSubsystem<Input>();
     Input* input = GetSubsystem<Input>();
@@ -364,6 +467,12 @@ void SceneView3D::SelectNode(Node* node)
 
 
 bool SceneView3D::OnEvent(const TBWidgetEvent &ev)
 bool SceneView3D::OnEvent(const TBWidgetEvent &ev)
 {
 {
+    if (ev.type == EVENT_TYPE_SHORTCUT)
+    {
+        if (ev.ref_id == TBIDC("close"))
+            return false;
+    }
+
     return sceneEditor_->OnEvent(ev);
     return sceneEditor_->OnEvent(ev);
 }
 }
 
 
@@ -374,8 +483,7 @@ void SceneView3D::HandleUpdate(StringHash eventType, VariantMap& eventData)
     // Timestep parameter is same no matter what event is being listened to
     // Timestep parameter is same no matter what event is being listened to
     float timeStep = eventData[Update::P_TIMESTEP].GetFloat();
     float timeStep = eventData[Update::P_TIMESTEP].GetFloat();
 
 
-    if (MouseInView())
-        MoveCamera(timeStep);
+    MoveCamera(timeStep);
 
 
     QueueUpdate();
     QueueUpdate();
 
 
@@ -516,6 +624,13 @@ void SceneView3D::HandleDragEnded(StringHash eventType, VariantMap& eventData)
         VariantMap neventData;
         VariantMap neventData;
         neventData[EditorActiveNodeChange::P_NODE] = dragNode_;
         neventData[EditorActiveNodeChange::P_NODE] = dragNode_;
         SendEvent(E_EDITORACTIVENODECHANGE, neventData);
         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);
+
     }
     }
 
 
     if (dragObject && dragObject->GetObject()->GetType() == ToolCore::Asset::GetTypeStatic())
     if (dragObject && dragObject->GetObject()->GetType() == ToolCore::Asset::GetTypeStatic())
@@ -558,6 +673,26 @@ void SceneView3D::HandleDragEnded(StringHash eventType, VariantMap& eventData)
     dragNode_ = 0;
     dragNode_ = 0;
 }
 }
 
 
+void SceneView3D::FrameSelection()
+{
+    BoundingBox bbox;
+
+    sceneEditor_->GetSelectionBoundingBox(bbox);
+
+    if (!bbox.defined_)
+        return;
+
+    Sphere sphere(bbox);
 
 
+    if (sphere.radius_ < .01f || sphere.radius_ > 512)
+        return;
+
+    framedNode_ = selectedNode_;
+    cameraMoveStart_ = cameraNode_->GetWorldPosition();
+    cameraMoveTarget_ = bbox.Center() - (cameraNode_->GetWorldDirection() * sphere.radius_ * 3);
+    cameraMoveTime_ = 0.0f;
+    cameraMove_ = true;
+
+}
 
 
 }
 }

+ 14 - 1
Source/AtomicEditor/Editors/SceneEditor3D/SceneView3D.h

@@ -46,6 +46,8 @@ public:
     void SetPitch(float pitch) { pitch_ = pitch; }
     void SetPitch(float pitch) { pitch_ = pitch; }
     void SetYaw(float yaw) { yaw_ = yaw; }
     void SetYaw(float yaw) { yaw_ = yaw; }
 
 
+    void FrameSelection();
+
     void Enable();
     void Enable();
     void Disable();
     void Disable();
     bool IsEnabled() { return enabled_; }
     bool IsEnabled() { return enabled_; }
@@ -53,6 +55,8 @@ public:
 private:
 private:
 
 
     bool MouseInView();
     bool MouseInView();
+    bool GetOrbitting();
+    bool GetZooming();
 
 
     void HandleMouseMove(StringHash eventType, VariantMap& eventData);
     void HandleMouseMove(StringHash eventType, VariantMap& eventData);
 
 
@@ -67,6 +71,9 @@ private:
     void HandleEditorActiveNodeChange(StringHash eventType, VariantMap& eventData);
     void HandleEditorActiveNodeChange(StringHash eventType, VariantMap& eventData);
     void HandleNodeRemoved(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 DrawNodeDebug(Node* node, DebugRenderer* debug, bool drawNode = true);
 
 
     void MoveCamera(float timeStep);
     void MoveCamera(float timeStep);
@@ -79,12 +86,18 @@ private:
     bool mouseLeftDown_;
     bool mouseLeftDown_;
     bool mouseMoved_;
     bool mouseMoved_;
 
 
-    bool enabled_;
+    bool enabled_;    
+
+    bool cameraMove_;
+    float cameraMoveTime_;
+    Vector3 cameraMoveStart_;
+    Vector3 cameraMoveTarget_;
 
 
     SharedPtr<Camera> camera_;
     SharedPtr<Camera> camera_;
     SharedPtr<DebugRenderer> debugRenderer_;
     SharedPtr<DebugRenderer> debugRenderer_;
     SharedPtr<Octree> octree_;
     SharedPtr<Octree> octree_;
     SharedPtr<Node> selectedNode_;
     SharedPtr<Node> selectedNode_;
+    WeakPtr<Node> framedNode_;
 
 
     SharedPtr<Scene> preloadResourceScene_;
     SharedPtr<Scene> preloadResourceScene_;
     String dragAssetGUID_;
     String dragAssetGUID_;

+ 5 - 1
Source/AtomicEditor/PlayerMode/AEPlayerMode.cpp

@@ -23,6 +23,10 @@
 
 
 #include "AEPlayerMode.h"
 #include "AEPlayerMode.h"
 
 
+#ifdef ATOMIC_PLATFORM_WINDOWS
+#include <windows.h>
+#endif
+
 namespace AtomicEditor
 namespace AtomicEditor
 {
 {
 
 
@@ -200,7 +204,7 @@ void PlayerMode::HandleViewRender(StringHash eventType, VariantMap& eventData)
 // BEGIN LICENSE MANAGEMENT
 // BEGIN LICENSE MANAGEMENT
     static bool done = false;
     static bool done = false;
 
 
-    if (licenseModule3D_)
+    if (!launchedByEditor_ || licenseModule3D_)
         return;
         return;
 
 
     Camera* camera = static_cast<Camera*>(eventData[BeginViewRender::P_CAMERA].GetPtr());
     Camera* camera = static_cast<Camera*>(eventData[BeginViewRender::P_CAMERA].GetPtr());

+ 1 - 1
Source/AtomicJS/Javascript/JSPlugin.cpp

@@ -247,7 +247,7 @@ namespace Atomic
         gJSVMExports.duk_is_callable = duk_is_callable;
         gJSVMExports.duk_is_callable = duk_is_callable;
         gJSVMExports.duk_is_dynamic_buffer = duk_is_dynamic_buffer;
         gJSVMExports.duk_is_dynamic_buffer = duk_is_dynamic_buffer;
         gJSVMExports.duk_is_fixed_buffer = duk_is_fixed_buffer;
         gJSVMExports.duk_is_fixed_buffer = duk_is_fixed_buffer;
-        gJSVMExports.duk_is_primitive = duk_is_primitive;
+        //gJSVMExports.duk_is_primitive = duk_is_primitive;
         gJSVMExports.duk_get_error_code = duk_get_error_code;
         gJSVMExports.duk_get_error_code = duk_get_error_code;
 
 
         gJSVMExports.duk_get_boolean = duk_get_boolean;
         gJSVMExports.duk_get_boolean = duk_get_boolean;

+ 74 - 18
Source/ThirdParty/Duktape/duk_config.h

@@ -170,6 +170,11 @@ static __inline__ unsigned long long duk_rdtsc(void) {
 /* PowerPC */
 /* PowerPC */
 #if defined(__powerpc) || defined(__powerpc__) || defined(__PPC__)
 #if defined(__powerpc) || defined(__powerpc__) || defined(__PPC__)
 #define DUK_F_PPC
 #define DUK_F_PPC
+#if defined(__PPC64__)
+#define DUK_F_PPC64
+#else
+#define DUK_F_PPC32
+#endif
 #endif
 #endif
 
 
 /* Linux */
 /* Linux */
@@ -478,8 +483,8 @@ static __inline__ unsigned long long duk_rdtsc(void) {
  * there is no platform specific date parsing/formatting but there is still
  * there is no platform specific date parsing/formatting but there is still
  * the ISO 8601 standard format.
  * the ISO 8601 standard format.
  */
  */
+#if defined(DUK_COMPILING_DUKTAPE)
 #include <windows.h>
 #include <windows.h>
-#include <limits.h>
 // ATOMIC BEGIN
 // ATOMIC BEGIN
 #ifdef GetObject
 #ifdef GetObject
 #undef GetObject
 #undef GetObject
@@ -494,6 +499,8 @@ static __inline__ unsigned long long duk_rdtsc(void) {
 #undef FindText
 #undef FindText
 #endif
 #endif
 // ATOMIC END
 // ATOMIC END
+#endif
+#include <limits.h>
 #elif defined(DUK_F_FLASHPLAYER)
 #elif defined(DUK_F_FLASHPLAYER)
 /* Crossbridge */
 /* Crossbridge */
 #define DUK_USE_DATE_NOW_GETTIMEOFDAY
 #define DUK_USE_DATE_NOW_GETTIMEOFDAY
@@ -1096,6 +1103,11 @@ typedef duk_int_t duk_idx_t;
 #define DUK_IDX_MIN               DUK_INT_MIN
 #define DUK_IDX_MIN               DUK_INT_MIN
 #define DUK_IDX_MAX               DUK_INT_MAX
 #define DUK_IDX_MAX               DUK_INT_MAX
 
 
+/* Unsigned index variant. */
+typedef duk_uint_t duk_uidx_t;
+#define DUK_UIDX_MIN              DUK_UINT_MIN
+#define DUK_UIDX_MAX              DUK_UINT_MAX
+
 /* Array index values, could be exact 32 bits.
 /* Array index values, could be exact 32 bits.
  * Currently no need for signed duk_arridx_t.
  * Currently no need for signed duk_arridx_t.
  */
  */
@@ -1509,8 +1521,8 @@ typedef struct duk_hthread duk_context;
 #define DUK_USE_PACKED_TVAL_POSSIBLE
 #define DUK_USE_PACKED_TVAL_POSSIBLE
 #endif
 #endif
 
 
-/* PPC: packed always possible */
-#if !defined(DUK_USE_PACKED_TVAL_POSSIBLE) && defined(DUK_F_PPC)
+/* PPC32: packed always possible */
+#if !defined(DUK_USE_PACKED_TVAL_POSSIBLE) && defined(DUK_F_PPC32)
 #define DUK_USE_PACKED_TVAL_POSSIBLE
 #define DUK_USE_PACKED_TVAL_POSSIBLE
 #endif
 #endif
 
 
@@ -1900,6 +1912,12 @@ typedef FILE duk_file;
 		(void) (x); \
 		(void) (x); \
 	} while (0)
 	} while (0)
 
 
+/* Convert any input pointer into a "void *", losing a const qualifier.
+ * This is not fully portable because casting through duk_uintptr_t may
+ * not work on all architectures (e.g. those with long, segmented pointers).
+ */
+#define DUK_LOSE_CONST(src) ((void *) (duk_uintptr_t) (src))
+
 /*
 /*
  *  DUK_NORETURN: macro for declaring a 'noreturn' function.
  *  DUK_NORETURN: macro for declaring a 'noreturn' function.
  *  Unfortunately the noreturn declaration may appear in various
  *  Unfortunately the noreturn declaration may appear in various
@@ -2032,6 +2050,18 @@ typedef FILE duk_file;
 #define DUK_ALWAYS_INLINE  /*nop*/
 #define DUK_ALWAYS_INLINE  /*nop*/
 #endif
 #endif
 
 
+/* Temporary workaround for GH-323: avoid inlining control when
+ * compiling from multiple sources, as it causes compiler trouble.
+ */
+#if !defined(DUK_SINGLE_FILE)
+#undef DUK_NOINLINE
+#undef DUK_INLINE
+#undef DUK_ALWAYS_INLINE
+#define DUK_NOINLINE       /*nop*/
+#define DUK_INLINE         /*nop*/
+#define DUK_ALWAYS_INLINE  /*nop*/
+#endif
+
 /*
 /*
  *  Symbol visibility macros
  *  Symbol visibility macros
  *
  *
@@ -2241,6 +2271,16 @@ typedef FILE duk_file;
 #define DUK_USE_COMPILER_STRING "unknown"
 #define DUK_USE_COMPILER_STRING "unknown"
 #endif
 #endif
 
 
+/*
+ *  Target info string
+ */
+
+#if defined(DUK_OPT_TARGET_INFO)
+#define DUK_USE_TARGET_INFO DUK_OPT_TARGET_INFO
+#else
+#define DUK_USE_TARGET_INFO "unknown"
+#endif
+
 /*
 /*
  *  Long control transfer, setjmp/longjmp or alternatives
  *  Long control transfer, setjmp/longjmp or alternatives
  *
  *
@@ -2277,16 +2317,6 @@ typedef FILE duk_file;
 #error internal error
 #error internal error
 #endif
 #endif
 
 
-/*
- *  Target info string
- */
-
-#if defined(DUK_OPT_TARGET_INFO)
-#define DUK_USE_TARGET_INFO DUK_OPT_TARGET_INFO
-#else
-#define DUK_USE_TARGET_INFO "unknown"
-#endif
-
 /*
 /*
  *  Speed/size and other performance options
  *  Speed/size and other performance options
  */
  */
@@ -2308,11 +2338,17 @@ typedef FILE duk_file;
  * where a speed-size tradeoff exists (e.g. lookup tables).  When it really
  * where a speed-size tradeoff exists (e.g. lookup tables).  When it really
  * matters, specific use flags may be appropriate.
  * matters, specific use flags may be appropriate.
  */
  */
-#define DUK_USE_PREFER_SIZE
+#undef DUK_USE_PREFER_SIZE
 
 
 /* Use a sliding window for lexer; slightly larger footprint, slightly faster. */
 /* Use a sliding window for lexer; slightly larger footprint, slightly faster. */
 #define DUK_USE_LEXER_SLIDING_WINDOW
 #define DUK_USE_LEXER_SLIDING_WINDOW
 
 
+/* Transparent JSON.stringify() fastpath. */
+#undef DUK_USE_JSON_STRINGIFY_FASTPATH
+#if defined(DUK_OPT_JSON_STRINGIFY_FASTPATH)
+#define DUK_USE_JSON_STRINGIFY_FASTPATH
+#endif
+
 /*
 /*
  *  Tagged type representation (duk_tval)
  *  Tagged type representation (duk_tval)
  */
  */
@@ -2461,6 +2497,16 @@ typedef FILE duk_file;
 #define DUK_USE_DEBUGGER_DUMPHEAP
 #define DUK_USE_DEBUGGER_DUMPHEAP
 #endif
 #endif
 
 
+#define DUK_USE_DEBUGGER_THROW_NOTIFY
+#if defined(DUK_OPT_NO_DEBUGGER_THROW_NOTIFY)
+#undef DUK_USE_DEBUGGER_THROW_NOTIFY
+#endif
+
+#undef DUK_USE_DEBUGGER_PAUSE_UNCAUGHT
+#if defined(DUK_OPT_DEBUGGER_PAUSE_UNCAUGHT)
+#define DUK_USE_DEBUGGER_PAUSE_UNCAUGHT
+#endif
+
 /* Debugger transport read/write torture. */
 /* Debugger transport read/write torture. */
 #undef DUK_USE_DEBUGGER_TRANSPORT_TORTURE
 #undef DUK_USE_DEBUGGER_TRANSPORT_TORTURE
 #if defined(DUK_OPT_DEBUGGER_TRANSPORT_TORTURE)
 #if defined(DUK_OPT_DEBUGGER_TRANSPORT_TORTURE)
@@ -2695,6 +2741,12 @@ typedef FILE duk_file;
 #undef DUK_USE_NONSTD_ARRAY_WRITE
 #undef DUK_USE_NONSTD_ARRAY_WRITE
 #endif
 #endif
 
 
+/* Node.js Buffer and Khronos/ES6 typed array support. */
+#define DUK_USE_BUFFEROBJECT_SUPPORT
+#if defined(DUK_OPT_NO_BUFFEROBJECT_SUPPORT)
+#undef DUK_USE_BUFFEROBJECT_SUPPORT
+#endif
+
 /*
 /*
  *  Optional C API options
  *  Optional C API options
  */
  */
@@ -2930,10 +2982,6 @@ typedef FILE duk_file;
 #define DUK_USE_PROVIDE_DEFAULT_ALLOC_FUNCTIONS
 #define DUK_USE_PROVIDE_DEFAULT_ALLOC_FUNCTIONS
 #undef DUK_USE_EXPLICIT_NULL_INIT
 #undef DUK_USE_EXPLICIT_NULL_INIT
 
 
-#if !defined(DUK_USE_PACKED_TVAL)
-#define DUK_USE_EXPLICIT_NULL_INIT
-#endif
-
 #define DUK_USE_ZERO_BUFFER_DATA
 #define DUK_USE_ZERO_BUFFER_DATA
 #if defined(DUK_OPT_NO_ZERO_BUFFER_DATA)
 #if defined(DUK_OPT_NO_ZERO_BUFFER_DATA)
 #undef DUK_USE_ZERO_BUFFER_DATA
 #undef DUK_USE_ZERO_BUFFER_DATA
@@ -3011,9 +3059,12 @@ typedef FILE duk_file;
 #define DUK_USE_JSON_DECSTRING_FASTPATH
 #define DUK_USE_JSON_DECSTRING_FASTPATH
 #define DUK_USE_JSON_EATWHITE_FASTPATH
 #define DUK_USE_JSON_EATWHITE_FASTPATH
 #define DUK_USE_JSON_QUOTESTRING_FASTPATH
 #define DUK_USE_JSON_QUOTESTRING_FASTPATH
+#undef DUK_USE_JSON_STRINGIFY_FASTPATH
 #define DUK_USE_JSON_STRINGIFY_FASTPATH
 #define DUK_USE_JSON_STRINGIFY_FASTPATH
 #undef DUK_USE_MARK_AND_SWEEP
 #undef DUK_USE_MARK_AND_SWEEP
 #define DUK_USE_MARK_AND_SWEEP
 #define DUK_USE_MARK_AND_SWEEP
+#undef DUK_USE_PACKED_TVAL
+#undef DUK_USE_PREFER_SIZE
 #undef DUK_USE_REFERENCE_COUNTING
 #undef DUK_USE_REFERENCE_COUNTING
 #define DUK_USE_REFERENCE_COUNTING
 #define DUK_USE_REFERENCE_COUNTING
 #undef DUK_USE_VALSTACK_UNSAFE
 #undef DUK_USE_VALSTACK_UNSAFE
@@ -3030,13 +3081,18 @@ typedef FILE duk_file;
 #undef DUK_USE_DATE_GET_NOW
 #undef DUK_USE_DATE_GET_NOW
 #undef DUK_USE_DATE_PARSE_STRING
 #undef DUK_USE_DATE_PARSE_STRING
 #undef DUK_USE_DATE_PRS_GETDATE
 #undef DUK_USE_DATE_PRS_GETDATE
+#undef DUK_USE_EXEC_FUN_LOCAL
 #undef DUK_USE_INTEGER_ME
 #undef DUK_USE_INTEGER_ME
+#undef DUK_USE_INTERRUPT_DEBUG_FIXUP
 #define DUK_USE_JSON_DEC_RECLIMIT 1000
 #define DUK_USE_JSON_DEC_RECLIMIT 1000
 #define DUK_USE_JSON_ENC_RECLIMIT 1000
 #define DUK_USE_JSON_ENC_RECLIMIT 1000
+#undef DUK_USE_MARKANDSWEEP_FINALIZER_TORTURE
 #define DUK_USE_MARK_AND_SWEEP_RECLIMIT 256
 #define DUK_USE_MARK_AND_SWEEP_RECLIMIT 256
 #define DUK_USE_NATIVE_CALL_RECLIMIT 1000
 #define DUK_USE_NATIVE_CALL_RECLIMIT 1000
+#undef DUK_USE_REFZERO_FINALIZER_TORTURE
 #define DUK_USE_REGEXP_COMPILER_RECLIMIT 10000
 #define DUK_USE_REGEXP_COMPILER_RECLIMIT 10000
 #define DUK_USE_REGEXP_EXECUTOR_RECLIMIT 10000
 #define DUK_USE_REGEXP_EXECUTOR_RECLIMIT 10000
+#define DUK_USE_VERBOSE_PROP_ERRORS
 
 
 /*
 /*
  *  Alternative customization header
  *  Alternative customization header

File diff suppressed because it is too large
+ 512 - 96
Source/ThirdParty/Duktape/duktape.c


+ 60 - 16
Source/ThirdParty/Duktape/duktape.h

@@ -1,11 +1,12 @@
 /*
 /*
- *  Duktape public API for Duktape 1.2.99.
+ *  Duktape public API for Duktape 1.3.99.
  *  See the API reference for documentation on call semantics.
  *  See the API reference for documentation on call semantics.
  *  The exposed API is inside the DUK_API_PUBLIC_H_INCLUDED
  *  The exposed API is inside the DUK_API_PUBLIC_H_INCLUDED
  *  include guard.  Other parts of the header are Duktape
  *  include guard.  Other parts of the header are Duktape
  *  internal and related to platform/compiler/feature detection.
  *  internal and related to platform/compiler/feature detection.
  *
  *
- *  Git commit 50171d671af34f2c403acf61c6dc83f2d2561e24 (v1.2.0-398-g50171d6).
+ *  Git commit df5e369cbd20005700281f008e49ab09725677ef (v1.3.0-202-gdf5e369-dirty).
+ *  Git branch master.
  *
  *
  *  See Duktape AUTHORS.rst and LICENSE.txt for copyright and
  *  See Duktape AUTHORS.rst and LICENSE.txt for copyright and
  *  licensing information.
  *  licensing information.
@@ -59,8 +60,9 @@
  *  Please include an e-mail address, a link to your GitHub profile, or something
  *  Please include an e-mail address, a link to your GitHub profile, or something
  *  similar to allow your contribution to be identified accurately.
  *  similar to allow your contribution to be identified accurately.
  *  
  *  
- *  The following people have contributed code and agreed to irrevocably license
- *  their contributions under the Duktape ``LICENSE.txt`` (in order of appearance):
+ *  The following people have contributed code, website contents, or Wiki contents,
+ *  and agreed to irrevocably license their contributions under the Duktape
+ *  ``LICENSE.txt`` (in order of appearance):
  *  
  *  
  *  * Sami Vaarala <[email protected]>
  *  * Sami Vaarala <[email protected]>
  *  * Niki Dobrev
  *  * Niki Dobrev
@@ -68,6 +70,7 @@
  *  * L\u00e1szl\u00f3 Lang\u00f3 <[email protected]>
  *  * L\u00e1szl\u00f3 Lang\u00f3 <[email protected]>
  *  * Legimet <[email protected]>
  *  * Legimet <[email protected]>
  *  * Karl Skomski <[email protected]>
  *  * Karl Skomski <[email protected]>
+ *  * Bruce Pascoe <[email protected]>
  *  
  *  
  *  Other contributions
  *  Other contributions
  *  ===================
  *  ===================
@@ -208,13 +211,16 @@ struct duk_number_list_entry {
  * have 99 for patch level (e.g. 0.10.99 would be a development version
  * have 99 for patch level (e.g. 0.10.99 would be a development version
  * after 0.10.0 but before the next official release).
  * after 0.10.0 but before the next official release).
  */
  */
-#define DUK_VERSION                       10299L
+#define DUK_VERSION                       10399L
 
 
-/* Git describe for Duktape build.  Useful for non-official snapshot builds
- * so that application code can easily log which Duktape snapshot was used.
- * Not available in the Ecmascript environment.
+/* Git commit, describe, and branch for Duktape build.  Useful for
+ * non-official snapshot builds so that application code can easily log
+ * which Duktape snapshot was used.  Not available in the Ecmascript
+ * environment.
  */
  */
-#define DUK_GIT_DESCRIBE                  "v1.2.0-398-g50171d6"
+#define DUK_GIT_COMMIT                    "df5e369cbd20005700281f008e49ab09725677ef"
+#define DUK_GIT_DESCRIBE                  "v1.3.0-202-gdf5e369-dirty"
+#define DUK_GIT_BRANCH                    "master"
 
 
 /* Duktape debug protocol version used by this build. */
 /* Duktape debug protocol version used by this build. */
 #define DUK_DEBUG_PROTOCOL_VERSION        1
 #define DUK_DEBUG_PROTOCOL_VERSION        1
@@ -295,6 +301,12 @@ struct duk_number_list_entry {
 #define DUK_DEFPROP_HAVE_GETTER           (1 << 7)    /* set getter (given on value stack) */
 #define DUK_DEFPROP_HAVE_GETTER           (1 << 7)    /* set getter (given on value stack) */
 #define DUK_DEFPROP_HAVE_SETTER           (1 << 8)    /* set setter (given on value stack) */
 #define DUK_DEFPROP_HAVE_SETTER           (1 << 8)    /* set setter (given on value stack) */
 #define DUK_DEFPROP_FORCE                 (1 << 9)    /* force change if possible, may still fail for e.g. virtual properties */
 #define DUK_DEFPROP_FORCE                 (1 << 9)    /* force change if possible, may still fail for e.g. virtual properties */
+#define DUK_DEFPROP_SET_WRITABLE          (DUK_DEFPROP_HAVE_WRITABLE | DUK_DEFPROP_WRITABLE)
+#define DUK_DEFPROP_CLEAR_WRITABLE        DUK_DEFPROP_HAVE_WRITABLE
+#define DUK_DEFPROP_SET_ENUMERABLE        (DUK_DEFPROP_HAVE_ENUMERABLE | DUK_DEFPROP_ENUMERABLE)
+#define DUK_DEFPROP_CLEAR_ENUMERABLE      DUK_DEFPROP_HAVE_ENUMERABLE
+#define DUK_DEFPROP_SET_CONFIGURABLE      (DUK_DEFPROP_HAVE_CONFIGURABLE | DUK_DEFPROP_CONFIGURABLE)
+#define DUK_DEFPROP_CLEAR_CONFIGURABLE    DUK_DEFPROP_HAVE_CONFIGURABLE
 
 
 /* Flags for duk_push_thread_raw() */
 /* Flags for duk_push_thread_raw() */
 #define DUK_THREAD_NEW_GLOBAL_ENV         (1 << 0)    /* create a new global environment */
 #define DUK_THREAD_NEW_GLOBAL_ENV         (1 << 0)    /* create a new global environment */
@@ -536,16 +548,36 @@ DUK_EXTERNAL_DECL duk_idx_t duk_push_error_object_va_raw(duk_context *ctx, duk_e
 	duk_push_error_object_va_raw((ctx), (err_code), (const char *) (__FILE__), (duk_int_t) (__LINE__), (fmt), (ap))
 	duk_push_error_object_va_raw((ctx), (err_code), (const char *) (__FILE__), (duk_int_t) (__LINE__), (fmt), (ap))
 
 
 #define DUK_BUF_FLAG_DYNAMIC   (1 << 0)    /* internal flag: dynamic buffer */
 #define DUK_BUF_FLAG_DYNAMIC   (1 << 0)    /* internal flag: dynamic buffer */
-#define DUK_BUF_FLAG_NOZERO    (1 << 1)    /* internal flag: don't zero allocated buffer */
+#define DUK_BUF_FLAG_EXTERNAL  (1 << 1)    /* internal flag: external buffer */
+#define DUK_BUF_FLAG_NOZERO    (1 << 2)    /* internal flag: don't zero allocated buffer */
 
 
 DUK_EXTERNAL_DECL void *duk_push_buffer_raw(duk_context *ctx, duk_size_t size, duk_small_uint_t flags);
 DUK_EXTERNAL_DECL void *duk_push_buffer_raw(duk_context *ctx, duk_size_t size, duk_small_uint_t flags);
 
 
 #define duk_push_buffer(ctx,size,dynamic) \
 #define duk_push_buffer(ctx,size,dynamic) \
-	duk_push_buffer_raw((ctx), (size), (dynamic));
+	duk_push_buffer_raw((ctx), (size), (dynamic) ? DUK_BUF_FLAG_DYNAMIC : 0);
 #define duk_push_fixed_buffer(ctx,size) \
 #define duk_push_fixed_buffer(ctx,size) \
-	duk_push_buffer_raw((ctx), (size), 0 /*dynamic*/)
+	duk_push_buffer_raw((ctx), (size), 0 /*flags*/)
 #define duk_push_dynamic_buffer(ctx,size) \
 #define duk_push_dynamic_buffer(ctx,size) \
-	duk_push_buffer_raw((ctx), (size), 1 /*dynamic*/)
+	duk_push_buffer_raw((ctx), (size), DUK_BUF_FLAG_DYNAMIC /*flags*/)
+#define duk_push_external_buffer(ctx) \
+	((void) duk_push_buffer_raw((ctx), 0, DUK_BUF_FLAG_DYNAMIC | DUK_BUF_FLAG_EXTERNAL))
+
+#define DUK_BUFOBJ_CREATE_ARRBUF       (1 << 4)  /* internal flag: create backing ArrayBuffer; keep in one byte */
+#define DUK_BUFOBJ_DUKTAPE_BUFFER      0
+#define DUK_BUFOBJ_NODEJS_BUFFER       1
+#define DUK_BUFOBJ_ARRAYBUFFER         2
+#define DUK_BUFOBJ_DATAVIEW            (3 | DUK_BUFOBJ_CREATE_ARRBUF)
+#define DUK_BUFOBJ_INT8ARRAY           (4 | DUK_BUFOBJ_CREATE_ARRBUF)
+#define DUK_BUFOBJ_UINT8ARRAY          (5 | DUK_BUFOBJ_CREATE_ARRBUF)
+#define DUK_BUFOBJ_UINT8CLAMPEDARRAY   (6 | DUK_BUFOBJ_CREATE_ARRBUF)
+#define DUK_BUFOBJ_INT16ARRAY          (7 | DUK_BUFOBJ_CREATE_ARRBUF)
+#define DUK_BUFOBJ_UINT16ARRAY         (8 | DUK_BUFOBJ_CREATE_ARRBUF)
+#define DUK_BUFOBJ_INT32ARRAY          (9 | DUK_BUFOBJ_CREATE_ARRBUF)
+#define DUK_BUFOBJ_UINT32ARRAY         (10 | DUK_BUFOBJ_CREATE_ARRBUF)
+#define DUK_BUFOBJ_FLOAT32ARRAY        (11 | DUK_BUFOBJ_CREATE_ARRBUF)
+#define DUK_BUFOBJ_FLOAT64ARRAY        (12 | DUK_BUFOBJ_CREATE_ARRBUF)
+
+DUK_EXTERNAL_DECL void duk_push_buffer_object(duk_context *ctx, duk_idx_t idx_buffer, duk_size_t byte_offset, duk_size_t byte_length, duk_uint_t flags);
 
 
 DUK_EXTERNAL_DECL duk_idx_t duk_push_heapptr(duk_context *ctx, void *ptr);
 DUK_EXTERNAL_DECL duk_idx_t duk_push_heapptr(duk_context *ctx, void *ptr);
 
 
@@ -592,8 +624,18 @@ DUK_EXTERNAL_DECL duk_bool_t duk_is_thread(duk_context *ctx, duk_idx_t index);
 DUK_EXTERNAL_DECL duk_bool_t duk_is_callable(duk_context *ctx, duk_idx_t index);
 DUK_EXTERNAL_DECL duk_bool_t duk_is_callable(duk_context *ctx, duk_idx_t index);
 DUK_EXTERNAL_DECL duk_bool_t duk_is_dynamic_buffer(duk_context *ctx, duk_idx_t index);
 DUK_EXTERNAL_DECL duk_bool_t duk_is_dynamic_buffer(duk_context *ctx, duk_idx_t index);
 DUK_EXTERNAL_DECL duk_bool_t duk_is_fixed_buffer(duk_context *ctx, duk_idx_t index);
 DUK_EXTERNAL_DECL duk_bool_t duk_is_fixed_buffer(duk_context *ctx, duk_idx_t index);
+DUK_EXTERNAL_DECL duk_bool_t duk_is_external_buffer(duk_context *ctx, duk_idx_t index);
+
+#define duk_is_primitive(ctx,index) \
+	duk_check_type_mask((ctx), (index), DUK_TYPE_MASK_UNDEFINED | \
+	                                    DUK_TYPE_MASK_NULL | \
+	                                    DUK_TYPE_MASK_BOOLEAN | \
+	                                    DUK_TYPE_MASK_NUMBER | \
+	                                    DUK_TYPE_MASK_STRING | \
+	                                    DUK_TYPE_MASK_BUFFER | \
+	                                    DUK_TYPE_MASK_POINTER | \
+	                                    DUK_TYPE_MASK_LIGHTFUNC)
 
 
-DUK_EXTERNAL_DECL duk_bool_t duk_is_primitive(duk_context *ctx, duk_idx_t index);
 #define duk_is_object_coercible(ctx,index) \
 #define duk_is_object_coercible(ctx,index) \
 	duk_check_type_mask((ctx), (index), DUK_TYPE_MASK_BOOLEAN | \
 	duk_check_type_mask((ctx), (index), DUK_TYPE_MASK_BOOLEAN | \
 	                                    DUK_TYPE_MASK_NUMBER | \
 	                                    DUK_TYPE_MASK_NUMBER | \
@@ -719,6 +761,7 @@ DUK_EXTERNAL_DECL void duk_json_decode(duk_context *ctx, duk_idx_t index);
 
 
 DUK_EXTERNAL_DECL void *duk_resize_buffer(duk_context *ctx, duk_idx_t index, duk_size_t new_size);
 DUK_EXTERNAL_DECL void *duk_resize_buffer(duk_context *ctx, duk_idx_t index, duk_size_t new_size);
 DUK_EXTERNAL_DECL void *duk_steal_buffer(duk_context *ctx, duk_idx_t index, duk_size_t *out_size);
 DUK_EXTERNAL_DECL void *duk_steal_buffer(duk_context *ctx, duk_idx_t index, duk_size_t *out_size);
+DUK_EXTERNAL_DECL void duk_config_buffer(duk_context *ctx, duk_idx_t index, void *ptr, duk_size_t len);
 
 
 /*
 /*
  *  Property access
  *  Property access
@@ -1315,7 +1358,7 @@ typedef union duk_double_union duk_double_union;
  *
  *
  *  When packed duk_tval is used, the NaN space is used to store pointers
  *  When packed duk_tval is used, the NaN space is used to store pointers
  *  and other tagged values in addition to NaNs.  Actual NaNs are normalized
  *  and other tagged values in addition to NaNs.  Actual NaNs are normalized
- *  to a specific format.  The macros below are used by the implementation
+ *  to a specific quiet NaN.  The macros below are used by the implementation
  *  to check and normalize NaN values when they might be created.  The macros
  *  to check and normalize NaN values when they might be created.  The macros
  *  are essentially NOPs when the non-packed duk_tval representation is used.
  *  are essentially NOPs when the non-packed duk_tval representation is used.
  *
  *
@@ -1323,7 +1366,8 @@ typedef union duk_double_union duk_double_union;
  *  the packed duk_tval and works correctly for all NaNs except those that
  *  the packed duk_tval and works correctly for all NaNs except those that
  *  begin with 0x7ff0.  Since the 'normalized NaN' values used with packed
  *  begin with 0x7ff0.  Since the 'normalized NaN' values used with packed
  *  duk_tval begin with 0x7ff8, the partial check is reliable when packed
  *  duk_tval begin with 0x7ff8, the partial check is reliable when packed
- *  duk_tval is used.
+ *  duk_tval is used.  The 0x7ff8 prefix means the normalized NaN will be a
+ *  quiet NaN regardless of its remaining lower bits.
  *
  *
  *  The ME variant below is specifically for ARM byte order, which has the
  *  The ME variant below is specifically for ARM byte order, which has the
  *  feature that while doubles have a mixed byte order (32107654), unsigned
  *  feature that while doubles have a mixed byte order (32107654), unsigned

+ 21 - 0
Source/ThirdParty/TurboBadger/tb_editfield.cpp

@@ -357,6 +357,27 @@ void TBEditField::OnFocusChanged(bool focused)
 {
 {
 	m_style_edit.Focus(focused);
 	m_style_edit.Focus(focused);
 
 
+    if (focused)
+    {
+        if (!m_style_edit.packed.styling_on)
+            GetText(m_initial_edit_text);
+    }
+    else
+    {
+        if (!m_style_edit.packed.styling_on)
+        {
+            TBStr curText;
+            GetText(curText);
+            if (!curText.Equals(m_initial_edit_text))
+            {
+                TBWidgetEvent ev(EVENT_TYPE_CUSTOM);
+                ev.ref_id = TBIDC("edit_complete");
+                // forward to delegate
+                TBWidget::OnEvent(ev);
+            }
+        }
+    }
+
     TBWidget::OnFocusChanged(focused);
     TBWidget::OnFocusChanged(focused);
 }
 }
 
 

+ 2 - 0
Source/ThirdParty/TurboBadger/tb_editfield.h

@@ -190,6 +190,8 @@ private:
 	int m_virtual_width;
 	int m_virtual_width;
 	void UpdateScrollbarVisibility(bool multiline);
 	void UpdateScrollbarVisibility(bool multiline);
 
 
+    TBStr m_initial_edit_text;
+
 	// == TBStyleEditListener =======================
 	// == TBStyleEditListener =======================
 	virtual void OnChange();
 	virtual void OnChange();
 	virtual bool OnEnter();
 	virtual bool OnEnter();

+ 57 - 3
Source/ThirdParty/TurboBadger/tb_inline_select.cpp

@@ -18,7 +18,8 @@ namespace tb {
 TBInlineSelect::TBInlineSelect()
 TBInlineSelect::TBInlineSelect()
 	: m_value(0)
 	: m_value(0)
 	, m_min(0)
 	, m_min(0)
-	, m_max(100)
+    , m_max(100)
+    , m_modified(false)
 {
 {
 	SetSkinBg(TBIDC("TBInlineSelect"));
 	SetSkinBg(TBIDC("TBInlineSelect"));
 	AddChild(&m_layout);
 	AddChild(&m_layout);
@@ -41,10 +42,15 @@ TBInlineSelect::TBInlineSelect()
 	m_editfield.SetTextAlign(TB_TEXT_ALIGN_CENTER);
 	m_editfield.SetTextAlign(TB_TEXT_ALIGN_CENTER);
 	m_editfield.SetEditType(EDIT_TYPE_NUMBER);
 	m_editfield.SetEditType(EDIT_TYPE_NUMBER);
 	m_editfield.SetText("0");
 	m_editfield.SetText("0");
+
+    m_editfield.AddListener(this);
+
 }
 }
 
 
 TBInlineSelect::~TBInlineSelect()
 TBInlineSelect::~TBInlineSelect()
 {
 {
+    m_editfield.RemoveListener(this);
+
 	m_layout.RemoveChild(&m_buttons[1]);
 	m_layout.RemoveChild(&m_buttons[1]);
 	m_layout.RemoveChild(&m_editfield);
 	m_layout.RemoveChild(&m_editfield);
 	m_layout.RemoveChild(&m_buttons[0]);
 	m_layout.RemoveChild(&m_buttons[0]);
@@ -87,8 +93,8 @@ void TBInlineSelect::SetValueInternal(double value, bool update_text)
 		m_editfield.SetText(strval);
 		m_editfield.SetText(strval);
 	}
 	}
 
 
-	TBWidgetEvent ev(EVENT_TYPE_CHANGED);
-	InvokeEvent(ev);
+    TBWidgetEvent ev(EVENT_TYPE_CHANGED);
+    InvokeEvent(ev);
 
 
 	// Warning: Do nothing here since the event might have deleted us.
 	// Warning: Do nothing here since the event might have deleted us.
 	//          If needed, check if we are alive using a safe pointer first.
 	//          If needed, check if we are alive using a safe pointer first.
@@ -126,7 +132,55 @@ bool TBInlineSelect::OnEvent(const TBWidgetEvent &ev)
 		m_editfield.GetText(text);
 		m_editfield.GetText(text);
         SetValueInternal((double) atof(text), false);
         SetValueInternal((double) atof(text), false);
 	}
 	}
+
+    // catch mouse up/mouse down on buttons for modifications
+    if (ev.target == &m_buttons[0] || ev.target == &m_buttons[1])
+    {
+        if (ev.type == EVENT_TYPE_POINTER_DOWN)
+        {
+            m_modified = true;
+        }
+        else if (ev.type == EVENT_TYPE_POINTER_UP)
+        {
+            if (m_modified)
+                InvokeModifiedEvent();
+        }
+    }
+
 	return false;
 	return false;
 }
 }
 
 
+void TBInlineSelect::InvokeModifiedEvent()
+{
+    TBWidgetEvent ev(EVENT_TYPE_CUSTOM);
+    ev.ref_id = TBIDC("edit_complete");
+    // forward to delegate
+    TBWidget::OnEvent(ev);
+    m_modified = false;
+    m_editfield.GetText(m_initial_edit_value);
+}
+
+bool TBInlineSelect::OnWidgetInvokeEvent(TBWidget *widget, const TBWidgetEvent &ev)
+{
+    return false;
+}
+
+void TBInlineSelect::OnWidgetFocusChanged(TBWidget *widget, bool focused)
+{
+    if (widget == &m_editfield)
+    {
+        if (focused)
+            m_editfield.GetText(m_initial_edit_value);
+        else
+        {
+            TBStr editvalue;
+            m_editfield.GetText(editvalue);
+            if (m_modified || !editvalue.Equals(m_initial_edit_value.CStr()))
+            {
+                InvokeModifiedEvent();
+            }
+        }
+    }
+}
+
 }; // namespace tb
 }; // namespace tb

+ 10 - 1
Source/ThirdParty/TurboBadger/tb_inline_select.h

@@ -19,7 +19,7 @@ namespace tb {
 	FIX: Should also be possible to set a list of strings that will be
 	FIX: Should also be possible to set a list of strings that will be
 		shown instead of numbers.
 		shown instead of numbers.
 */
 */
-class TBInlineSelect : public TBWidget
+class TBInlineSelect : public TBWidget, private TBWidgetListener
 {
 {
 public:
 public:
 	// For safe typecasting
 	// For safe typecasting
@@ -53,8 +53,17 @@ protected:
 	TBEditField m_editfield;
 	TBEditField m_editfield;
     double m_value;
     double m_value;
     double m_min, m_max;
     double m_min, m_max;
+    bool m_modified;
 
 
     void SetValueInternal(double value, bool update_text);
     void SetValueInternal(double value, bool update_text);
+
+private:
+
+    TBStr m_initial_edit_value;
+
+    void InvokeModifiedEvent();
+    void OnWidgetFocusChanged(TBWidget *widget, bool focused);
+    bool OnWidgetInvokeEvent(TBWidget *widget, const TBWidgetEvent &ev);
 };
 };
 
 
 }; // namespace tb
 }; // namespace tb

+ 2 - 1
Source/ThirdParty/TurboBadger/tb_select.cpp

@@ -316,7 +316,8 @@ bool TBSelectList::OnEvent(const TBWidgetEvent &ev)
 		if (GetScrollContainer()->OnEvent(ev))
 		if (GetScrollContainer()->OnEvent(ev))
 			return true;
 			return true;
 	}
 	}
-	return false;
+
+    return TBWidget::OnEvent(ev);
 }
 }
 
 
 bool TBSelectList::ChangeValue(SPECIAL_KEY key)
 bool TBSelectList::ChangeValue(SPECIAL_KEY key)

+ 7 - 0
Source/ThirdParty/TurboBadger/tb_widgets_reader.cpp

@@ -188,6 +188,13 @@ void TBEditField::OnInflate(const INFLATE_INFO &info)
 		else if (stristr(type, "url"))		SetEditType(EDIT_TYPE_URL);
 		else if (stristr(type, "url"))		SetEditType(EDIT_TYPE_URL);
 		else if (stristr(type, "number"))	SetEditType(EDIT_TYPE_NUMBER);
 		else if (stristr(type, "number"))	SetEditType(EDIT_TYPE_NUMBER);
 	}
 	}
+    if (const char *text_align = info.node->GetValueString("text-align", nullptr))
+    {
+        if (!strcmp(text_align, "left"))		SetTextAlign(TB_TEXT_ALIGN_LEFT);
+        else if (!strcmp(text_align, "center"))	SetTextAlign(TB_TEXT_ALIGN_CENTER);
+        else if (!strcmp(text_align, "right"))	SetTextAlign(TB_TEXT_ALIGN_RIGHT);
+    }
+
 	TBWidget::OnInflate(info);
 	TBWidget::OnInflate(info);
 }
 }
 
 

+ 5 - 0
Source/ToolCore/Assets/Asset.cpp

@@ -21,6 +21,7 @@
 #include "TextureImporter.h"
 #include "TextureImporter.h"
 #include "PrefabImporter.h"
 #include "PrefabImporter.h"
 #include "JavascriptImporter.h"
 #include "JavascriptImporter.h"
+#include "JSONImporter.h"
 #include "SpriterImporter.h"
 #include "SpriterImporter.h"
 #include "TMXImporter.h"
 #include "TMXImporter.h"
 #include "PEXImporter.h"
 #include "PEXImporter.h"
@@ -268,6 +269,10 @@ bool Asset::CreateImporter()
         {
         {
             importer_ = new JavascriptImporter(context_, this);
             importer_ = new JavascriptImporter(context_, this);
         }
         }
+        else if (ext == ".json")
+        {
+            importer_ = new JSONImporter(context_, this);
+        }
         else if (ext == ".scene")
         else if (ext == ".scene")
         {
         {
             importer_ = new SceneImporter(context_, this);
             importer_ = new SceneImporter(context_, this);

+ 41 - 3
Source/ToolCore/Assets/AssetDatabase.cpp

@@ -18,6 +18,7 @@
 #include "../ToolEvents.h"
 #include "../ToolEvents.h"
 #include "../ToolSystem.h"
 #include "../ToolSystem.h"
 #include "../Project/Project.h"
 #include "../Project/Project.h"
+#include "../Project/ProjectEvents.h"
 #include "AssetEvents.h"
 #include "AssetEvents.h"
 #include "AssetDatabase.h"
 #include "AssetDatabase.h"
 
 
@@ -27,6 +28,7 @@ namespace ToolCore
 
 
 AssetDatabase::AssetDatabase(Context* context) : Object(context)
 AssetDatabase::AssetDatabase(Context* context) : Object(context)
 {
 {
+    SubscribeToEvent(E_LOADFAILED, HANDLER(AssetDatabase, HandleResourceLoadFailed));
     SubscribeToEvent(E_PROJECTLOADED, HANDLER(AssetDatabase, HandleProjectLoaded));
     SubscribeToEvent(E_PROJECTLOADED, HANDLER(AssetDatabase, HandleProjectLoaded));
     SubscribeToEvent(E_PROJECTUNLOADED, HANDLER(AssetDatabase, HandleProjectUnloaded));
     SubscribeToEvent(E_PROJECTUNLOADED, HANDLER(AssetDatabase, HandleProjectUnloaded));
 }
 }
@@ -98,9 +100,6 @@ Asset* AssetDatabase::GetAssetByCachePath(const String& cachePath)
 {
 {
     List<SharedPtr<Asset>>::ConstIterator itr = assets_.Begin();
     List<SharedPtr<Asset>>::ConstIterator itr = assets_.Begin();
 
 
-    if (!cachePath.StartsWith("Cache/"))
-        return 0;
-
     String cacheFilename = GetFileName(cachePath);
     String cacheFilename = GetFileName(cachePath);
 
 
     while (itr != assets_.End())
     while (itr != assets_.End())
@@ -438,11 +437,48 @@ void AssetDatabase::HandleProjectUnloaded(StringHash eventType, VariantMap& even
     cache->RemoveResourceDir(GetCachePath());
     cache->RemoveResourceDir(GetCachePath());
     assets_.Clear();
     assets_.Clear();
     usedGUID_.Clear();
     usedGUID_.Clear();
+    assetImportErrorTimes_.Clear();
     project_ = 0;
     project_ = 0;
 
 
     UnsubscribeFromEvent(E_FILECHANGED);
     UnsubscribeFromEvent(E_FILECHANGED);
 }
 }
 
 
+void AssetDatabase::HandleResourceLoadFailed(StringHash eventType, VariantMap& eventData)
+{
+
+    if (project_.Null())
+        return;
+
+    String path = eventData[LoadFailed::P_RESOURCENAME].GetString();
+
+    Asset* asset = GetAssetByPath(path);
+
+    if (!asset)
+        asset = GetAssetByPath(project_->GetResourcePath() + path);
+
+    if (!asset)
+        return;
+
+    Time* time = GetSubsystem<Time>();
+
+    unsigned ctime = time->GetSystemTime();
+
+    // if less than 5 seconds since last report, stifle report
+    if (assetImportErrorTimes_.Contains(asset->guid_))
+        if (ctime - assetImportErrorTimes_[asset->guid_] < 5000)
+            return;
+
+    assetImportErrorTimes_[asset->guid_] = ctime;
+
+    VariantMap evData;
+    evData[AssetImportError::P_PATH] = asset->path_;
+    evData[AssetImportError::P_GUID] = asset->guid_;
+    evData[AssetImportError::P_ERROR] = ToString("Asset %s Failed to Load", asset->path_.CString());
+    SendEvent(E_ASSETIMPORTERROR, evData);
+
+
+}
+
 void AssetDatabase::HandleFileChanged(StringHash eventType, VariantMap& eventData)
 void AssetDatabase::HandleFileChanged(StringHash eventType, VariantMap& eventData)
 {
 {
     using namespace FileChanged;
     using namespace FileChanged;
@@ -498,8 +534,10 @@ String AssetDatabase::GetResourceImporterName(const String& resourceTypeName)
         resourceTypeToImporterType_["Sprite2D"] = "TextureImporter";
         resourceTypeToImporterType_["Sprite2D"] = "TextureImporter";
         resourceTypeToImporterType_["AnimatedSprite2D"] = "SpriterImporter";
         resourceTypeToImporterType_["AnimatedSprite2D"] = "SpriterImporter";
         resourceTypeToImporterType_["JSComponentFile"] = "JavascriptImporter";        
         resourceTypeToImporterType_["JSComponentFile"] = "JavascriptImporter";        
+        resourceTypeToImporterType_["JSONFile"] = "JSONImporter";
         resourceTypeToImporterType_["ParticleEffect2D"] = "PEXImporter";
         resourceTypeToImporterType_["ParticleEffect2D"] = "PEXImporter";
 
 
+
 #ifdef ATOMIC_DOTNET
 #ifdef ATOMIC_DOTNET
         resourceTypeToImporterType_["CSComponentAssembly"] = "NETAssemblyImporter";
         resourceTypeToImporterType_["CSComponentAssembly"] = "NETAssemblyImporter";
 #endif
 #endif

+ 4 - 0
Source/ToolCore/Assets/AssetDatabase.h

@@ -55,6 +55,7 @@ private:
     void HandleProjectLoaded(StringHash eventType, VariantMap& eventData);
     void HandleProjectLoaded(StringHash eventType, VariantMap& eventData);
     void HandleProjectUnloaded(StringHash eventType, VariantMap& eventData);
     void HandleProjectUnloaded(StringHash eventType, VariantMap& eventData);
     void HandleFileChanged(StringHash eventType, VariantMap& eventData);
     void HandleFileChanged(StringHash eventType, VariantMap& eventData);
+    void HandleResourceLoadFailed(StringHash eventType, VariantMap& eventData);
 
 
     void AddAsset(SharedPtr<Asset>& asset);
     void AddAsset(SharedPtr<Asset>& asset);
 
 
@@ -70,6 +71,9 @@ private:
 
 
     HashMap<StringHash, String> resourceTypeToImporterType_;
     HashMap<StringHash, String> resourceTypeToImporterType_;
 
 
+    /// Hash value of times, so we don't spam import errors
+    HashMap<StringHash, unsigned> assetImportErrorTimes_;
+
     Vector<String> usedGUID_;
     Vector<String> usedGUID_;
 
 
 };
 };

+ 76 - 0
Source/ToolCore/Assets/JSONImporter.cpp

@@ -0,0 +1,76 @@
+//
+// 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/IO/File.h>
+#include <Atomic/Resource/ResourceCache.h>
+#include <Atomic/Resource/Image.h>
+
+#include <AtomicJS/Javascript/JSComponentFile.h>
+
+#include "Asset.h"
+#include "AssetDatabase.h"
+#include "JSONImporter.h"
+
+
+namespace ToolCore
+{
+
+JSONImporter::JSONImporter(Context* context, Asset *asset) : AssetImporter(context, asset)
+{
+    requiresCacheFile_ = false;
+}
+
+JSONImporter::~JSONImporter()
+{
+
+}
+
+void JSONImporter::SetDefaults()
+{
+    AssetImporter::SetDefaults();
+}
+
+bool JSONImporter::Import()
+{
+    return true;
+}
+
+bool JSONImporter::LoadSettingsInternal(JSONValue& jsonRoot)
+{
+    if (!AssetImporter::LoadSettingsInternal(jsonRoot))
+        return false;
+
+    JSONValue import = jsonRoot.Get("JSONImporter");
+
+    return true;
+}
+
+bool JSONImporter::SaveSettingsInternal(JSONValue& jsonRoot)
+{
+    if (!AssetImporter::SaveSettingsInternal(jsonRoot))
+        return false;
+
+    JSONValue import;
+    jsonRoot.Set("JSONImporter", import);
+
+    return true;
+}
+
+Resource* JSONImporter::GetResource(const String& typeName)
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+
+    JSONFile* jsfile = cache->GetResource<JSONFile>(asset_->GetPath());
+
+    return jsfile;
+
+}
+
+
+
+}

+ 37 - 0
Source/ToolCore/Assets/JSONImporter.h

@@ -0,0 +1,37 @@
+//
+// 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 "AssetImporter.h"
+
+namespace ToolCore
+{
+
+class JSONImporter : public AssetImporter
+{
+    OBJECT(JSONImporter);
+
+public:
+    /// Construct.
+    JSONImporter(Context* context, Asset* asset);
+    virtual ~JSONImporter();
+
+    virtual void SetDefaults();
+
+    Resource* GetResource(const String& typeName = String::EMPTY);
+
+protected:
+
+    bool Import();
+
+    virtual bool LoadSettingsInternal(JSONValue& jsonRoot);
+    virtual bool SaveSettingsInternal(JSONValue& jsonRoot);
+
+};
+
+}

+ 1 - 1
Source/ToolCore/NETTools/NETToolSystem.cpp

@@ -9,7 +9,7 @@
 #include <Atomic/IO/FileSystem.h>
 #include <Atomic/IO/FileSystem.h>
 #include <AtomicNET/NETCore/NETCore.h>
 #include <AtomicNET/NETCore/NETCore.h>
 
 
-#include "../ToolEvents.h"
+#include "../Project/ProjectEvents.h"
 #include "../ToolEnvironment.h"
 #include "../ToolEnvironment.h"
 
 
 #include "NETToolSystem.h"
 #include "NETToolSystem.h"

+ 12 - 0
Source/ToolCore/Platform/PlatformAndroid.cpp

@@ -7,6 +7,7 @@
 
 
 #include <Atomic/IO/MemoryBuffer.h>
 #include <Atomic/IO/MemoryBuffer.h>
 #include <Atomic/IO/FileSystem.h>
 #include <Atomic/IO/FileSystem.h>
+#include <Atomic/IO/Log.h>
 
 
 #include "../ToolEnvironment.h"
 #include "../ToolEnvironment.h"
 #include "../Subprocess/SubprocessSystem.h"
 #include "../Subprocess/SubprocessSystem.h"
@@ -83,6 +84,17 @@ void PlatformAndroid::RefreshAndroidTargets()
     if (refreshAndroidTargetsProcess_.NotNull())
     if (refreshAndroidTargetsProcess_.NotNull())
         return;
         return;
 
 
+    ToolPrefs* prefs = GetSubsystem<ToolEnvironment>()->GetToolPrefs();
+    FileSystem* fileSystem = GetSubsystem<FileSystem>();
+
+    String androidSDKPath = prefs->GetAndroidSDKPath();
+
+    if (!fileSystem->DirExists(androidSDKPath))
+    {
+        LOGERRORF("The Android SDK path %s does not exist", androidSDKPath.CString());
+        return;
+    }
+
     SubprocessSystem* subs = GetSubsystem<SubprocessSystem>();
     SubprocessSystem* subs = GetSubsystem<SubprocessSystem>();
 
 
     String androidCommand = GetAndroidCommand();
     String androidCommand = GetAndroidCommand();

+ 1 - 0
Source/ToolCore/Project/Project.cpp

@@ -18,6 +18,7 @@
 #include "../ToolEvents.h"
 #include "../ToolEvents.h"
 #include "../Platform/Platform.h"
 #include "../Platform/Platform.h"
 
 
+#include "ProjectEvents.h"
 #include "ProjectFile.h"
 #include "ProjectFile.h"
 #include "ProjectBuildSettings.h"
 #include "ProjectBuildSettings.h"
 #include "ProjectUserPrefs.h"
 #include "ProjectUserPrefs.h"

+ 32 - 0
Source/ToolCore/Project/ProjectEvents.h

@@ -0,0 +1,32 @@
+//
+// 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 ToolCore
+{
+
+EVENT(E_PROJECTLOADED, ProjectLoaded)
+{
+    PARAM(P_PROJECTPATH, ProjectPath);    // string
+}
+
+EVENT(E_PROJECTUNLOADED, ProjectUnloaded)
+{
+}
+
+EVENT(E_PROJECTUSERPREFSAVED, ProjectUserPrefSaved)
+{
+    PARAM(P_PREFS, Prefs);    // ProjectUserPrefs
+}
+
+
+}

+ 82 - 1
Source/ToolCore/Project/ProjectUserPrefs.cpp

@@ -5,6 +5,7 @@
 // license information: https://github.com/AtomicGameEngine/AtomicGameEngine
 // license information: https://github.com/AtomicGameEngine/AtomicGameEngine
 //
 //
 
 
+#include "ProjectEvents.h"
 #include "ProjectUserPrefs.h"
 #include "ProjectUserPrefs.h"
 
 
 #include <Atomic/IO/File.h>
 #include <Atomic/IO/File.h>
@@ -13,7 +14,13 @@
 namespace ToolCore
 namespace ToolCore
 {
 {
 
 
-ProjectUserPrefs::ProjectUserPrefs(Context* context) : Object(context)
+ProjectUserPrefs::ProjectUserPrefs(Context* context) : Object(context),
+  snapTranslationX_(1.0f),
+  snapTranslationY_(1.0f),
+  snapTranslationZ_(1.0f),
+  snapRotation_(15.0f),
+  snapScale_(0.1f)
+
 {
 {
 #ifdef ATOMIC_PLATFORM_OSX
 #ifdef ATOMIC_PLATFORM_OSX
     defaultPlatform_ = PLATFORMID_MAC;
     defaultPlatform_ = PLATFORMID_MAC;
@@ -27,6 +34,56 @@ ProjectUserPrefs::~ProjectUserPrefs()
 
 
 }
 }
 
 
+float ProjectUserPrefs::GetSnapTranslationX() const
+{
+    return snapTranslationX_;
+}
+
+float ProjectUserPrefs::GetSnapTranslationY() const
+{
+    return snapTranslationY_;
+}
+
+float ProjectUserPrefs::GetSnapTranslationZ() const
+{
+    return snapTranslationZ_;
+}
+
+float ProjectUserPrefs::GetSnapRotation() const
+{
+    return snapRotation_;
+}
+
+float ProjectUserPrefs::GetSnapScale() const
+{
+    return snapScale_;
+}
+
+void ProjectUserPrefs::SetSnapTranslationX(float value)
+{
+    snapTranslationX_ = value;
+}
+
+void ProjectUserPrefs::SetSnapTranslationY(float value)
+{
+    snapTranslationY_ = value;
+}
+
+void ProjectUserPrefs::SetSnapTranslationZ(float value)
+{
+    snapTranslationZ_ = value;
+}
+
+void ProjectUserPrefs::SetSnapRotation(float value)
+{
+    snapRotation_ = value;
+}
+
+void ProjectUserPrefs::SetSnapScale(float value)
+{
+    snapScale_ = value;
+}
+
 bool ProjectUserPrefs::Load(const String& path)
 bool ProjectUserPrefs::Load(const String& path)
 {
 {
     SharedPtr<File> file(new File(context_, path));
     SharedPtr<File> file(new File(context_, path));
@@ -46,6 +103,19 @@ bool ProjectUserPrefs::Load(const String& path)
 
 
     lastBuildPath_ = root.Get("lastBuildPath").GetString();
     lastBuildPath_ = root.Get("lastBuildPath").GetString();
 
 
+    if (root.Contains("snapTransX"))
+        SetSnapTranslationX(root.Get("snapTransX").GetFloat());
+    if (root.Contains("snapTransY"))
+        SetSnapTranslationY(root.Get("snapTransY").GetFloat());
+    if (root.Contains("snapTransZ"))
+        SetSnapTranslationZ(root.Get("snapTransZ").GetFloat());
+
+    if (root.Contains("snapRotation"))
+        SetSnapRotation(root.Get("snapRotation").GetFloat());
+
+    if (root.Contains("snapScale"))
+        SetSnapScale(root.Get("snapScale").GetFloat());
+
     return true;
     return true;
 }
 }
 
 
@@ -60,10 +130,21 @@ void ProjectUserPrefs::Save(const String& path)
 
 
     root.Set("lastBuildPath", lastBuildPath_);
     root.Set("lastBuildPath", lastBuildPath_);
 
 
+    // Snap settings
+    root.Set("snapTransX", snapTranslationX_);
+    root.Set("snapTransY", snapTranslationY_);
+    root.Set("snapTransZ", snapTranslationZ_);
+    root.Set("snapRotation", snapRotation_);
+    root.Set("snapScale", snapScale_);
+
     jsonFile->Save(*file, String("   "));
     jsonFile->Save(*file, String("   "));
 
 
     file->Close();
     file->Close();
 
 
+    VariantMap evData;
+    evData[ProjectUserPrefSaved::P_PREFS] = this;
+    SendEvent(E_PROJECTUSERPREFSAVED);
+
 
 
 }
 }
 
 

+ 18 - 0
Source/ToolCore/Project/ProjectUserPrefs.h

@@ -34,6 +34,18 @@ public:
     const String& GetLastBuildPath() { return lastBuildPath_; }
     const String& GetLastBuildPath() { return lastBuildPath_; }
     void SetLastBuildPath(const String& path) { lastBuildPath_ = path; }
     void SetLastBuildPath(const String& path) { lastBuildPath_ = path; }
 
 
+    float GetSnapTranslationX() const;
+    float GetSnapTranslationY() const;
+    float GetSnapTranslationZ() const;
+    float GetSnapRotation() const;
+    float GetSnapScale() const;
+
+    void SetSnapTranslationX(float value);
+    void SetSnapTranslationY(float value);
+    void SetSnapTranslationZ(float value);
+    void SetSnapRotation(float value);
+    void SetSnapScale(float value);
+
 private:
 private:
 
 
     bool Load(const String& path);
     bool Load(const String& path);
@@ -42,6 +54,12 @@ private:
     PlatformID defaultPlatform_;
     PlatformID defaultPlatform_;
     String lastBuildPath_;
     String lastBuildPath_;
 
 
+    float snapTranslationX_;
+    float snapTranslationY_;
+    float snapTranslationZ_;
+    float snapRotation_;
+    float snapScale_;
+
 };
 };
 
 
 }
 }

+ 0 - 9
Source/ToolCore/ToolEvents.h

@@ -14,15 +14,6 @@ using namespace Atomic;
 namespace ToolCore
 namespace ToolCore
 {
 {
 
 
-EVENT(E_PROJECTLOADED, ProjectLoaded)
-{
-    PARAM(P_PROJECTPATH, ProjectPath);    // string
-}
-
-EVENT(E_PROJECTUNLOADED, ProjectUnloaded)
-{
-}
-
 EVENT(E_PLATFORMCHANGED, PlatformChanged)
 EVENT(E_PLATFORMCHANGED, PlatformChanged)
 {
 {
     PARAM(P_PLATFORM, Platform);    // Platform Ptr
     PARAM(P_PLATFORM, Platform);    // Platform Ptr

+ 5 - 0
Source/ToolCore/ToolPrefs.cpp

@@ -51,6 +51,11 @@ void ToolPrefs::Load()
 {
 {
     String path = GetPrefsPath();
     String path = GetPrefsPath();
 
 
+    // Check that the tool prefs file exists
+    FileSystem* fs = GetSubsystem<FileSystem>();
+    if (!fs->FileExists(path))
+        return;
+
     SharedPtr<File> file(new File(context_, path));
     SharedPtr<File> file(new File(context_, path));
 
 
     if (!file->IsOpen())
     if (!file->IsOpen())

+ 1 - 0
Source/ToolCore/ToolSystem.cpp

@@ -26,6 +26,7 @@
 #include "ToolEvents.h"
 #include "ToolEvents.h"
 
 
 #include "Project/Project.h"
 #include "Project/Project.h"
+#include "Project/ProjectEvents.h"
 #include "Project/ProjectUserPrefs.h"
 #include "Project/ProjectUserPrefs.h"
 
 
 #ifdef ATOMIC_DOTNET
 #ifdef ATOMIC_DOTNET

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