NodeInspector.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. //
  2. // Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
  3. // LICENSE: Atomic Game Engine Editor and Tools EULA
  4. // Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
  5. // license information: https://github.com/AtomicGameEngine/AtomicGameEngine
  6. //
  7. import ScriptWidget = require("ui/ScriptWidget");
  8. import ComponentInspector = require("./ComponentInspector");
  9. import DataBinding = require("./DataBinding");
  10. import CreateComponentButton = require("./CreateComponentButton");
  11. import EditorEvents = require("editor/EditorEvents");
  12. interface ComponentState {
  13. expanded: boolean;
  14. }
  15. interface NodeState {
  16. expanded: boolean;
  17. componentStates: { [id: number]: ComponentState };
  18. }
  19. class NodeInspector extends ScriptWidget {
  20. constructor() {
  21. super();
  22. this.subscribeToEvent(this, "WidgetEvent", (ev) => this.handleWidgetEvent(ev));
  23. this.subscribeToEvent("GizmoMoved", (ev) => this.handleGizmoModed(ev));
  24. this.subscribeToEvent("Update", (ev) => this.handleUpdate(ev));
  25. }
  26. handleUpdate(ev) {
  27. // to keep from spamming UI update we have a little delta on the gizmo updates
  28. if (!this.node) {
  29. this.gizmoMoved = false;
  30. return;
  31. }
  32. if (this.gizmoMoved) {
  33. if (this.updateDelta > 1.0) {
  34. this.updateDelta = 0.0;
  35. }
  36. else {
  37. this.updateDelta -= ev.timeStep;
  38. }
  39. if (this.updateDelta <= 0) {
  40. for (var i in this.bindings) {
  41. this.bindings[i].setWidgetValueFromObject();
  42. }
  43. this.gizmoMoved = false;
  44. this.updateDelta = 0;
  45. }
  46. }
  47. }
  48. handleGizmoModed(ev) {
  49. if (!this.node) return;
  50. this.gizmoMoved = true;
  51. this.updateDelta += .3;
  52. }
  53. handleWidgetEvent(ev: Atomic.UIWidgetEvent) {
  54. var handled = false;
  55. for (var i = 0; i < this.bindings.length; i++) {
  56. if (this.bindings[i].handleWidgetEvent(ev)) {
  57. handled = true;
  58. }
  59. }
  60. // return handled
  61. return handled;
  62. }
  63. getPrefabComponent(node: Atomic.Node): Atomic.PrefabComponent {
  64. if (node.getComponent("PrefabComponent"))
  65. return <Atomic.PrefabComponent>node.getComponent("PrefabComponent");
  66. if (node.parent)
  67. return this.getPrefabComponent(node.parent);
  68. return null;
  69. }
  70. detectPrefab(node: Atomic.Node): boolean {
  71. if (node.getComponent("PrefabComponent"))
  72. return true;
  73. if (node.parent)
  74. return this.detectPrefab(node.parent);
  75. return false;
  76. }
  77. inspect(node: Atomic.Node) {
  78. this.bindings = new Array();
  79. this.node = node;
  80. node.scene.sendEvent("SceneEditSerializable", { serializable: node, operation: 0 });
  81. this.subscribeToEvent(node, "SceneEditStateChange", (data) => this.handleSceneEditStateChangeEvent(data));
  82. this.isPrefab = this.detectPrefab(node);
  83. var fd = new Atomic.UIFontDescription();
  84. fd.id = "Vera";
  85. fd.size = 11;
  86. var nlp = new Atomic.UILayoutParams();
  87. nlp.width = 304;
  88. var nodeLayout = this.nodeLayout = new Atomic.UILayout();
  89. nodeLayout.spacing = 4;
  90. nodeLayout.layoutDistribution = Atomic.UI_LAYOUT_DISTRIBUTION_GRAVITY;
  91. nodeLayout.layoutPosition = Atomic.UI_LAYOUT_POSITION_LEFT_TOP;
  92. nodeLayout.layoutParams = nlp;
  93. nodeLayout.axis = Atomic.UI_AXIS_Y;
  94. // node attr layout
  95. var nodeSection = new Atomic.UISection();
  96. nodeSection.id = "node_section";
  97. nodeSection.text = "Node";
  98. nodeSection.value = 1;
  99. nodeLayout.addChild(nodeSection);
  100. var attrsVerticalLayout = new Atomic.UILayout(Atomic.UI_AXIS_Y);
  101. attrsVerticalLayout.spacing = 3;
  102. attrsVerticalLayout.layoutPosition = Atomic.UI_LAYOUT_POSITION_LEFT_TOP;
  103. attrsVerticalLayout.layoutSize = Atomic.UI_LAYOUT_SIZE_AVAILABLE;
  104. nodeSection.contentRoot.addChild(attrsVerticalLayout);
  105. var attrs = node.getAttributes();
  106. for (var i in attrs) {
  107. var attr = <Atomic.AttributeInfo>attrs[i];
  108. if (attr.mode & Atomic.AM_NOEDIT)
  109. continue;
  110. var binding = DataBinding.createBinding(node, attr);
  111. if (!binding)
  112. continue;
  113. var attrLayout = new Atomic.UILayout();
  114. attrLayout.layoutDistribution = Atomic.UI_LAYOUT_DISTRIBUTION_GRAVITY;
  115. var name = new Atomic.UITextField();
  116. name.textAlign = Atomic.UI_TEXT_ALIGN_LEFT;
  117. name.skinBg = "InspectorTextAttrName";
  118. if (attr.type == Atomic.VAR_VECTOR3 || attr.type == Atomic.VAR_COLOR ||
  119. attr.type == Atomic.VAR_QUATERNION) {
  120. attrLayout.axis = Atomic.UI_AXIS_Y;
  121. attrLayout.layoutPosition = Atomic.UI_LAYOUT_POSITION_LEFT_TOP;
  122. attrLayout.skinBg = "InspectorVectorAttrLayout";
  123. }
  124. var bname = attr.name;
  125. if (bname == "Is Enabled")
  126. bname = "Enabled";
  127. name.text = bname;
  128. name.fontDescription = fd;
  129. attrLayout.addChild(name);
  130. attrLayout.addChild(binding.widget);
  131. attrsVerticalLayout.addChild(attrLayout);
  132. this.bindings.push(binding);
  133. }
  134. // PREFAB
  135. if (this.isPrefab) {
  136. var name = new Atomic.UITextField();
  137. name.textAlign = Atomic.UI_TEXT_ALIGN_LEFT;
  138. name.skinBg = "InspectorTextAttrName";
  139. name.text = "Prefab"
  140. name.fontDescription = fd;
  141. var prefabLayout = new Atomic.UILayout();
  142. prefabLayout.layoutDistribution = Atomic.UI_LAYOUT_DISTRIBUTION_GRAVITY;
  143. var saveButton = new Atomic.UIButton();
  144. saveButton.text = "Save";
  145. saveButton.fontDescription = fd;
  146. saveButton.onClick = () => {
  147. var prefabComponent = this.getPrefabComponent(this.node);
  148. if (prefabComponent) {
  149. prefabComponent.savePrefab();
  150. return true;
  151. }
  152. }
  153. var undoButton = new Atomic.UIButton();
  154. undoButton.text = "Undo";
  155. undoButton.fontDescription = fd;
  156. undoButton.onClick = () => {
  157. var prefabComponent = this.getPrefabComponent(this.node);
  158. if (prefabComponent) {
  159. prefabComponent.undoPrefab();
  160. // our components are now recreated
  161. // need to handle this
  162. return true;
  163. }
  164. }
  165. var breakButton = new Atomic.UIButton();
  166. breakButton.text = "Break";
  167. breakButton.fontDescription = fd;
  168. breakButton.onClick = () => {
  169. var prefabComponent = this.getPrefabComponent(this.node);
  170. if (prefabComponent) {
  171. prefabComponent.breakPrefab();
  172. prefabLayout.visibility = Atomic.UI_WIDGET_VISIBILITY_GONE;
  173. return true;
  174. }
  175. }
  176. prefabLayout.addChild(name);
  177. prefabLayout.addChild(saveButton);
  178. prefabLayout.addChild(undoButton);
  179. prefabLayout.addChild(breakButton);
  180. attrsVerticalLayout.addChild(prefabLayout);
  181. }
  182. // COMPONENTS
  183. var components = node.getComponents();
  184. for (var i in components) {
  185. var component = components[i];
  186. //if (component.isTemporary())
  187. // continue;
  188. var ci = new ComponentInspector();
  189. this.componentInspectors.push(ci);
  190. ci.id = "component_section_" + component.id;
  191. ci.inspect(component);
  192. nodeLayout.addChild(ci);
  193. }
  194. this.addChild(nodeLayout);
  195. var button = new CreateComponentButton(node);
  196. nodeLayout.addChild(button);
  197. for (var i in this.bindings) {
  198. this.bindings[i].setWidgetValueFromObject();
  199. this.bindings[i].objectLocked = false;
  200. }
  201. this.loadState();
  202. }
  203. updateWidgetValues(components: boolean = false) {
  204. for (var i in this.bindings) {
  205. this.bindings[i].objectLocked = true;
  206. this.bindings[i].setWidgetValueFromObject();
  207. this.bindings[i].objectLocked = false;
  208. }
  209. if (components) {
  210. for (var i in this.componentInspectors) {
  211. this.componentInspectors[i].updateWidgetValues();
  212. }
  213. }
  214. }
  215. handleSceneEditStateChangeEvent(ev) {
  216. this.updateWidgetValues();
  217. }
  218. saveState() {
  219. var node = this.node;
  220. if (!node.scene)
  221. return;
  222. var nodeStates = NodeInspector.nodeStates[node.scene.id];
  223. if (!nodeStates)
  224. return;
  225. var state = nodeStates[node.id];
  226. if (!state) {
  227. state = nodeStates[node.id] = { expanded: true, componentStates: {} };
  228. }
  229. var section: Atomic.UISection = <Atomic.UISection>this.nodeLayout.getWidget("node_section");
  230. state.expanded = section.value ? true : false;
  231. var components = node.getComponents();
  232. for (var i in components) {
  233. var component = components[i];
  234. var cstate = state.componentStates[component.id];
  235. if (!cstate) {
  236. cstate = state.componentStates[component.id] = { expanded: false };
  237. }
  238. section = <Atomic.UISection>this.nodeLayout.getWidget("component_section_" + component.id);
  239. if (section)
  240. cstate.expanded = section.value ? true : false;
  241. }
  242. }
  243. loadState() {
  244. var node = this.node;
  245. // lookup in node states via scene id
  246. var nodeStates = NodeInspector.nodeStates[node.scene.id];
  247. if (!nodeStates) {
  248. nodeStates = NodeInspector.nodeStates[node.scene.id] = {};
  249. }
  250. // lookup by node id
  251. var state = nodeStates[node.id];
  252. if (!state) {
  253. // we don't have a state, so save default state
  254. this.saveState();
  255. } else {
  256. var section: Atomic.UISection = <Atomic.UISection>this.nodeLayout.getWidget("node_section");
  257. section.value = state.expanded ? 1 : 0;
  258. var components = node.getComponents();
  259. for (var i in components) {
  260. var component = components[i];
  261. var cstate = state.componentStates[component.id];
  262. section = <Atomic.UISection>this.nodeLayout.getWidget("component_section_" + component.id);
  263. if (cstate && section) {
  264. section.value = cstate.expanded ? 1 : 0;
  265. }
  266. }
  267. }
  268. }
  269. isPrefab: boolean;
  270. node: Atomic.Node;
  271. nodeLayout: Atomic.UILayout;
  272. bindings: Array<DataBinding>;
  273. gizmoMoved = false;
  274. updateDelta = 0;
  275. componentInspectors: Array<ComponentInspector> = [];
  276. static nodeStates: { [sceneID: number]: { [nodeId: number]: NodeState } } = {};
  277. }
  278. export = NodeInspector;