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