AttributeEditor.as 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237
  1. // Attribute editor
  2. //
  3. // Functions that caller must implement:
  4. // - void SetAttributeEditorID(UIElement@ attrEdit, Array<Serializable@>@ serializables);
  5. // - bool PreEditAttribute(Array<Serializable@>@ serializables, uint index);
  6. // - void PostEditAttribute(Array<Serializable@>@ serializables, uint index, const Array<Variant>& oldValues);
  7. // - Array<Serializable@>@ GetAttributeEditorTargets(UIElement@ attrEdit);
  8. // - String GetVariableName(ShortStringHash hash);
  9. const uint MIN_NODE_ATTRIBUTES = 4;
  10. const uint MAX_NODE_ATTRIBUTES = 8;
  11. const int ATTRNAME_WIDTH = 150;
  12. const int ATTR_HEIGHT = 19;
  13. const StringHash TEXT_CHANGED_EVENT_TYPE("TextChanged");
  14. bool inLoadAttributeEditor = false;
  15. bool inEditAttribute = false;
  16. bool showNonEditableAttribute = false;
  17. Color normalTextColor(1.0f, 1.0f, 1.0f);
  18. Color modifiedTextColor(1.0f, 0.8f, 0.5f);
  19. Color nonEditableTextColor(0.7f, 0.7f, 0.7f);
  20. String sceneResourcePath = AddTrailingSlash(fileSystem.programDir + "Data");
  21. bool rememberResourcePath = true;
  22. WeakHandle testAnimState;
  23. UIElement@ SetEditable(UIElement@ element, bool editable)
  24. {
  25. if (element is null)
  26. return element;
  27. element.editable = editable;
  28. element.colors[C_TOPLEFT] = editable ? element.colors[C_BOTTOMRIGHT] : nonEditableTextColor;
  29. element.colors[C_BOTTOMLEFT] = element.colors[C_TOPLEFT];
  30. element.colors[C_TOPRIGHT] = element.colors[C_TOPLEFT];
  31. return element;
  32. }
  33. UIElement@ SetValue(LineEdit@ element, const String&in value, bool sameValue)
  34. {
  35. element.text = sameValue ? value : STRIKED_OUT;
  36. element.cursorPosition = 0;
  37. return element;
  38. }
  39. UIElement@ SetValue(CheckBox@ element, bool value, bool sameValue)
  40. {
  41. element.checked = sameValue ? value : false;
  42. return element;
  43. }
  44. UIElement@ SetValue(DropDownList@ element, int value, bool sameValue)
  45. {
  46. element.selection = sameValue ? value : M_MAX_UNSIGNED;
  47. return element;
  48. }
  49. UIElement@ CreateAttributeEditorParentWithSeparatedLabel(ListView@ list, const String&in name, uint index, uint subIndex, bool suppressedSeparatedLabel = false)
  50. {
  51. UIElement@ editorParent = UIElement("Edit" + String(index) + "_" + String(subIndex));
  52. editorParent.vars["Index"] = index;
  53. editorParent.vars["SubIndex"] = subIndex;
  54. editorParent.SetLayout(LM_VERTICAL, 2);
  55. list.AddItem(editorParent);
  56. if (suppressedSeparatedLabel)
  57. {
  58. UIElement@ placeHolder = UIElement(name);
  59. editorParent.AddChild(placeHolder);
  60. }
  61. else
  62. {
  63. Text@ attrNameText = Text();
  64. editorParent.AddChild(attrNameText);
  65. attrNameText.style = "EditorAttributeText";
  66. attrNameText.text = name;
  67. }
  68. return editorParent;
  69. }
  70. UIElement@ CreateAttributeEditorParentAsListChild(ListView@ list, const String&in name, uint index, uint subIndex)
  71. {
  72. UIElement@ editorParent = UIElement("Edit" + String(index) + "_" + String(subIndex));
  73. editorParent.vars["Index"] = index;
  74. editorParent.vars["SubIndex"] = subIndex;
  75. editorParent.SetLayout(LM_HORIZONTAL);
  76. list.AddChild(editorParent);
  77. UIElement@ placeHolder = UIElement(name);
  78. editorParent.AddChild(placeHolder);
  79. return editorParent;
  80. }
  81. UIElement@ CreateAttributeEditorParent(ListView@ list, const String&in name, uint index, uint subIndex)
  82. {
  83. UIElement@ editorParent = UIElement("Edit" + String(index) + "_" + String(subIndex));
  84. editorParent.vars["Index"] = index;
  85. editorParent.vars["SubIndex"] = subIndex;
  86. editorParent.SetLayout(LM_HORIZONTAL);
  87. editorParent.SetFixedHeight(ATTR_HEIGHT);
  88. list.AddItem(editorParent);
  89. Text@ attrNameText = Text();
  90. editorParent.AddChild(attrNameText);
  91. attrNameText.style = "EditorAttributeText";
  92. attrNameText.text = name;
  93. attrNameText.SetFixedWidth(ATTRNAME_WIDTH);
  94. return editorParent;
  95. }
  96. LineEdit@ CreateAttributeLineEdit(UIElement@ parent, Array<Serializable@>@ serializables, uint index, uint subIndex)
  97. {
  98. LineEdit@ attrEdit = LineEdit();
  99. parent.AddChild(attrEdit);
  100. attrEdit.style = "EditorAttributeEdit";
  101. attrEdit.SetFixedHeight(ATTR_HEIGHT - 2);
  102. attrEdit.vars["Index"] = index;
  103. attrEdit.vars["SubIndex"] = subIndex;
  104. SetAttributeEditorID(attrEdit, serializables);
  105. return attrEdit;
  106. }
  107. UIElement@ CreateStringAttributeEditor(ListView@ list, Array<Serializable@>@ serializables, const AttributeInfo&in info, uint index, uint subIndex)
  108. {
  109. UIElement@ parent = CreateAttributeEditorParent(list, info.name, index, subIndex);
  110. LineEdit@ attrEdit = CreateAttributeLineEdit(parent, serializables, index, subIndex);
  111. attrEdit.dragDropMode = DD_TARGET;
  112. SubscribeToEvent(attrEdit, "TextChanged", "EditAttribute");
  113. SubscribeToEvent(attrEdit, "TextFinished", "EditAttribute");
  114. return parent;
  115. }
  116. UIElement@ CreateBoolAttributeEditor(ListView@ list, Array<Serializable@>@ serializables, const AttributeInfo&in info, uint index, uint subIndex)
  117. {
  118. bool isUIElement = cast<UIElement>(serializables[0]) !is null;
  119. UIElement@ parent;
  120. if (info.name == (isUIElement ? "Is Visible" : "Is Enabled"))
  121. parent = CreateAttributeEditorParentAsListChild(list, info.name, index, subIndex);
  122. else
  123. parent = CreateAttributeEditorParent(list, info.name, index, subIndex);
  124. CheckBox@ attrEdit = CheckBox();
  125. parent.AddChild(attrEdit);
  126. attrEdit.style = AUTO_STYLE;
  127. attrEdit.vars["Index"] = index;
  128. attrEdit.vars["SubIndex"] = subIndex;
  129. SetAttributeEditorID(attrEdit, serializables);
  130. SubscribeToEvent(attrEdit, "Toggled", "EditAttribute");
  131. return parent;
  132. }
  133. UIElement@ CreateNumAttributeEditor(ListView@ list, Array<Serializable@>@ serializables, const AttributeInfo&in info, uint index, uint subIndex)
  134. {
  135. UIElement@ parent = CreateAttributeEditorParent(list, info.name, index, subIndex);
  136. VariantType type = info.type;
  137. uint numCoords = type - VAR_FLOAT + 1;
  138. if (type == VAR_QUATERNION)
  139. numCoords = 3;
  140. else if (type == VAR_COLOR || type == VAR_INTRECT)
  141. numCoords = 4;
  142. else if (type == VAR_INTVECTOR2)
  143. numCoords = 2;
  144. for (uint i = 0; i < numCoords; ++i)
  145. {
  146. LineEdit@ attrEdit = CreateAttributeLineEdit(parent, serializables, index, subIndex);
  147. attrEdit.vars["Coordinate"] = i;
  148. SubscribeToEvent(attrEdit, "TextChanged", "EditAttribute");
  149. SubscribeToEvent(attrEdit, "TextFinished", "EditAttribute");
  150. }
  151. return parent;
  152. }
  153. UIElement@ CreateIntAttributeEditor(ListView@ list, Array<Serializable@>@ serializables, const AttributeInfo&in info, uint index, uint subIndex)
  154. {
  155. UIElement@ parent = CreateAttributeEditorParent(list, info.name, index, subIndex);
  156. // Check for enums
  157. if (info.enumNames is null || info.enumNames.empty)
  158. {
  159. // No enums, create a numeric editor
  160. LineEdit@ attrEdit = CreateAttributeLineEdit(parent, serializables, index, subIndex);
  161. SubscribeToEvent(attrEdit, "TextChanged", "EditAttribute");
  162. SubscribeToEvent(attrEdit, "TextFinished", "EditAttribute");
  163. }
  164. else
  165. {
  166. DropDownList@ attrEdit = DropDownList();
  167. parent.AddChild(attrEdit);
  168. attrEdit.style = AUTO_STYLE;
  169. attrEdit.SetFixedHeight(ATTR_HEIGHT - 2);
  170. attrEdit.resizePopup = true;
  171. attrEdit.placeholderText = STRIKED_OUT;
  172. attrEdit.vars["Index"] = index;
  173. attrEdit.vars["SubIndex"] = subIndex;
  174. attrEdit.SetLayout(LM_HORIZONTAL, 0, IntRect(4, 1, 4, 1));
  175. SetAttributeEditorID(attrEdit, serializables);
  176. for (uint i = 0; i < info.enumNames.length; ++i)
  177. {
  178. Text@ choice = Text();
  179. attrEdit.AddItem(choice);
  180. choice.style = "EditorEnumAttributeText";
  181. choice.text = info.enumNames[i];
  182. }
  183. SubscribeToEvent(attrEdit, "ItemSelected", "EditAttribute");
  184. }
  185. return parent;
  186. }
  187. UIElement@ CreateResourceRefAttributeEditor(ListView@ list, Array<Serializable@>@ serializables, const AttributeInfo&in info, uint index, uint subIndex, bool suppressedSeparatedLabel = false)
  188. {
  189. UIElement@ parent;
  190. ShortStringHash resourceType;
  191. // Get the real attribute info from the serializable for the correct resource type
  192. AttributeInfo attrInfo = serializables[0].attributeInfos[index];
  193. if (attrInfo.type == VAR_RESOURCEREF)
  194. resourceType = serializables[0].attributes[index].GetResourceRef().type;
  195. else if (attrInfo.type == VAR_RESOURCEREFLIST)
  196. resourceType = serializables[0].attributes[index].GetResourceRefList().type;
  197. else if (attrInfo.type == VAR_VARIANTVECTOR)
  198. resourceType = serializables[0].attributes[index].GetVariantVector()[subIndex].GetResourceRef().type;
  199. ResourcePicker@ picker = GetResourcePicker(resourceType);
  200. // Create the attribute name on a separate non-interactive line to allow for more space
  201. parent = CreateAttributeEditorParentWithSeparatedLabel(list, info.name, index, subIndex, suppressedSeparatedLabel);
  202. UIElement@ container = UIElement();
  203. container.SetLayout(LM_HORIZONTAL, 4, IntRect(info.name.StartsWith(" ") ? 20 : 10, 0, 4, 0)); // Left margin is indented more when the name is so
  204. container.SetFixedHeight(ATTR_HEIGHT);
  205. parent.AddChild(container);
  206. LineEdit@ attrEdit = CreateAttributeLineEdit(container, serializables, index, subIndex);
  207. attrEdit.vars[TYPE_VAR] = resourceType.value;
  208. SubscribeToEvent(attrEdit, "TextFinished", "EditAttribute");
  209. if ((picker.actions & ACTION_PICK) != 0)
  210. {
  211. Button@ pickButton = CreateResourcePickerButton(container, serializables, index, subIndex, "Pick");
  212. SubscribeToEvent(pickButton, "Released", "PickResource");
  213. }
  214. if ((picker.actions & ACTION_OPEN) != 0)
  215. {
  216. Button@ openButton = CreateResourcePickerButton(container, serializables, index, subIndex, "Open");
  217. SubscribeToEvent(openButton, "Released", "OpenResource");
  218. }
  219. if ((picker.actions & ACTION_EDIT) != 0)
  220. {
  221. Button@ editButton = CreateResourcePickerButton(container, serializables, index, subIndex, "Edit");
  222. SubscribeToEvent(editButton, "Released", "EditResource");
  223. }
  224. if ((picker.actions & ACTION_TEST) != 0)
  225. {
  226. Button@ testButton = CreateResourcePickerButton(container, serializables, index, subIndex, "Test");
  227. SubscribeToEvent(testButton, "Released", "TestResource");
  228. }
  229. return parent;
  230. }
  231. Button@ CreateResourcePickerButton(UIElement@ container, Array<Serializable@>@ serializables, uint index, uint subIndex, const String&in text)
  232. {
  233. Button@ button = Button();
  234. container.AddChild(button);
  235. button.style = AUTO_STYLE;
  236. button.SetFixedSize(36, ATTR_HEIGHT - 2);
  237. button.vars["Index"] = index;
  238. button.vars["SubIndex"] = subIndex;
  239. SetAttributeEditorID(button, serializables);
  240. Text@ buttonText = Text();
  241. button.AddChild(buttonText);
  242. buttonText.style = "EditorAttributeText";
  243. buttonText.SetAlignment(HA_CENTER, VA_CENTER);
  244. buttonText.text = text;
  245. return button;
  246. }
  247. UIElement@ CreateAttributeEditor(ListView@ list, Array<Serializable@>@ serializables, const AttributeInfo&in info, uint index, uint subIndex, bool suppressedSeparatedLabel = false)
  248. {
  249. UIElement@ parent;
  250. VariantType type = info.type;
  251. if (type == VAR_STRING || type == VAR_BUFFER)
  252. parent = CreateStringAttributeEditor(list, serializables, info, index, subIndex);
  253. else if (type == VAR_BOOL)
  254. parent = CreateBoolAttributeEditor(list, serializables, info, index, subIndex);
  255. else if ((type >= VAR_FLOAT && type <= VAR_VECTOR4) || type == VAR_QUATERNION || type == VAR_COLOR || type == VAR_INTVECTOR2 || type == VAR_INTRECT)
  256. parent = CreateNumAttributeEditor(list, serializables, info, index, subIndex);
  257. else if (type == VAR_INT)
  258. parent = CreateIntAttributeEditor(list, serializables, info, index, subIndex);
  259. else if (type == VAR_RESOURCEREF)
  260. parent = CreateResourceRefAttributeEditor(list, serializables, info, index, subIndex, suppressedSeparatedLabel);
  261. else if (type == VAR_RESOURCEREFLIST)
  262. {
  263. uint numRefs = serializables[0].attributes[index].GetResourceRefList().length;
  264. // Straightly speaking the individual resource reference in the list is not an attribute of the serializable
  265. // However, the AttributeInfo structure is used here to reduce the number of parameters being passed in the function
  266. AttributeInfo refInfo;
  267. refInfo.name = info.name;
  268. refInfo.type = VAR_RESOURCEREF;
  269. for (uint i = 0; i < numRefs; ++i)
  270. CreateAttributeEditor(list, serializables, refInfo, index, i, i > 0);
  271. }
  272. else if (type == VAR_VARIANTVECTOR)
  273. {
  274. VectorStruct@ vectorStruct = GetVectorStruct(serializables, index);
  275. if (vectorStruct is null)
  276. return null;
  277. uint nameIndex = 0;
  278. Array<Variant>@ vector = serializables[0].attributes[index].GetVariantVector();
  279. for (uint i = 0; i < vector.length; ++i)
  280. {
  281. // The individual variant in the vector is not an attribute of the serializable, the structure is reused for convenience
  282. AttributeInfo vectorInfo;
  283. vectorInfo.name = vectorStruct.variableNames[nameIndex];
  284. vectorInfo.type = vector[i].type;
  285. CreateAttributeEditor(list, serializables, vectorInfo, index, i);
  286. ++nameIndex;
  287. if (nameIndex >= vectorStruct.variableNames.length)
  288. nameIndex = vectorStruct.restartIndex;
  289. }
  290. }
  291. else if (type == VAR_VARIANTMAP)
  292. {
  293. VariantMap map = serializables[0].attributes[index].GetVariantMap();
  294. Array<ShortStringHash>@ keys = map.keys;
  295. for (uint i = 0; i < keys.length; ++i)
  296. {
  297. String varName = GetVariableName(keys[i]);
  298. Variant value = map[keys[i]];
  299. // The individual variant in the map is not an attribute of the serializable, the structure is reused for convenience
  300. AttributeInfo mapInfo;
  301. mapInfo.name = varName + " (Var)";
  302. mapInfo.type = value.type;
  303. parent = CreateAttributeEditor(list, serializables, mapInfo, index, i);
  304. // Add the variant key to the parent. We may fail to add the editor in case it is unsupported
  305. if (parent !is null)
  306. {
  307. parent.vars["Key"] = keys[i].value;
  308. // If variable name is not registered (i.e. it is an editor internal variable) then hide it
  309. if (varName.empty)
  310. parent.visible = false;
  311. }
  312. }
  313. }
  314. return parent;
  315. }
  316. uint GetAttributeEditorCount(Array<Serializable@>@ serializables)
  317. {
  318. uint count = 0;
  319. if (!serializables.empty)
  320. {
  321. /// \todo When multi-editing, this only counts the editor count of the first serializable
  322. bool isUIElement = cast<UIElement>(serializables[0]) !is null;
  323. for (uint i = 0; i < serializables[0].numAttributes; ++i)
  324. {
  325. AttributeInfo info = serializables[0].attributeInfos[i];
  326. if (!showNonEditableAttribute && info.mode & AM_NOEDIT != 0)
  327. continue;
  328. // "Is Enabled" is not inserted into the main attribute list, so do not count
  329. // Similarly, for UIElement, "Is Visible" is not inserted
  330. if (info.name == (isUIElement ? "Is Visible" : "Is Enabled"))
  331. continue;
  332. if (info.type == VAR_RESOURCEREFLIST)
  333. count += serializables[0].attributes[i].GetResourceRefList().length;
  334. else if (info.type == VAR_VARIANTVECTOR && GetVectorStruct(serializables, i) !is null)
  335. count += serializables[0].attributes[i].GetVariantVector().length;
  336. else if (info.type == VAR_VARIANTMAP)
  337. count += serializables[0].attributes[i].GetVariantMap().length;
  338. else
  339. ++count;
  340. }
  341. }
  342. return count;
  343. }
  344. UIElement@ GetAttributeEditorParent(UIElement@ parent, uint index, uint subIndex)
  345. {
  346. return parent.GetChild("Edit" + String(index) + "_" + String(subIndex), true);
  347. }
  348. void LoadAttributeEditor(ListView@ list, Array<Serializable@>@ serializables, const AttributeInfo&in info, uint index)
  349. {
  350. bool editable = info.mode & AM_NOEDIT == 0;
  351. UIElement@ parent = GetAttributeEditorParent(list, index, 0);
  352. if (parent is null)
  353. return;
  354. inLoadAttributeEditor = true;
  355. bool sameName = true;
  356. bool sameValue = true;
  357. Variant value = serializables[0].attributes[index];
  358. Array<Variant> values;
  359. for (uint i = 0; i < serializables.length; ++i)
  360. {
  361. if (index >= serializables[i].numAttributes || serializables[i].attributeInfos[index].name != info.name)
  362. {
  363. sameName = false;
  364. break;
  365. }
  366. Variant val = serializables[i].attributes[index];
  367. if (val != value)
  368. sameValue = false;
  369. values.Push(val);
  370. }
  371. // Attribute with different values from multiple-select is loaded with default/empty value and non-editable
  372. if (sameName)
  373. LoadAttributeEditor(parent, value, info, editable, sameValue, values);
  374. else
  375. parent.visible = false;
  376. inLoadAttributeEditor = false;
  377. }
  378. void LoadAttributeEditor(UIElement@ parent, const Variant&in value, const AttributeInfo&in info, bool editable, bool sameValue, const Array<Variant>&in values)
  379. {
  380. uint index = parent.vars["Index"].GetUInt();
  381. // Assume the first child is always a text label element or a container that containing a text label element
  382. UIElement@ label = parent.children[0];
  383. if (label.type == UI_ELEMENT_TYPE && label.numChildren > 0)
  384. label = label.children[0];
  385. if (label.type == TEXT_TYPE)
  386. {
  387. bool modified;
  388. if (info.defaultValue.type == VAR_NONE || info.defaultValue.type == VAR_RESOURCEREFLIST)
  389. modified = !value.zero;
  390. else
  391. modified = value != info.defaultValue;
  392. cast<Text>(label).color = (editable ? (modified ? modifiedTextColor : normalTextColor) : nonEditableTextColor);
  393. }
  394. VariantType type = info.type;
  395. if (type == VAR_FLOAT || type == VAR_STRING || type == VAR_BUFFER)
  396. SetEditable(SetValue(parent.children[1], value.ToString(), sameValue), editable && sameValue);
  397. else if (type == VAR_BOOL)
  398. SetEditable(SetValue(parent.children[1], value.GetBool(), sameValue), editable && sameValue);
  399. else if (type == VAR_INT)
  400. {
  401. if (info.enumNames is null || info.enumNames.empty)
  402. SetEditable(SetValue(parent.children[1], value.ToString(), sameValue), editable && sameValue);
  403. else
  404. SetEditable(SetValue(parent.children[1], value.GetInt(), sameValue), editable && sameValue);
  405. }
  406. else if (type == VAR_RESOURCEREF)
  407. {
  408. SetEditable(SetValue(parent.children[1].children[0], value.GetResourceRef().name, sameValue), editable && sameValue);
  409. SetEditable(parent.children[1].children[1], editable && sameValue); // If editable then can pick
  410. for (uint i = 2; i < parent.children[1].numChildren; ++i)
  411. SetEditable(parent.children[1].children[i], sameValue); // If same value then can open/edit/test
  412. }
  413. else if (type == VAR_RESOURCEREFLIST)
  414. {
  415. UIElement@ list = parent.parent;
  416. ResourceRefList refList = value.GetResourceRefList();
  417. for (uint subIndex = 0; subIndex < refList.length; ++subIndex)
  418. {
  419. parent = GetAttributeEditorParent(list, index, subIndex);
  420. if (parent is null)
  421. break;
  422. String firstName = refList.names[subIndex];
  423. bool nameSameValue = true;
  424. if (!sameValue)
  425. {
  426. // Reevaluate each name in the list
  427. for (uint i = 0; i < values.length; ++i)
  428. {
  429. ResourceRefList refList = values[i].GetResourceRefList();
  430. if (subIndex >= refList.length || refList.names[subIndex] != firstName)
  431. {
  432. nameSameValue = false;
  433. break;
  434. }
  435. }
  436. }
  437. SetEditable(SetValue(parent.children[1].children[0], firstName, nameSameValue), editable && nameSameValue);
  438. }
  439. }
  440. else if (type == VAR_VARIANTVECTOR)
  441. {
  442. UIElement@ list = parent.parent;
  443. Array<Variant>@ vector = value.GetVariantVector();
  444. for (uint subIndex = 0; subIndex < vector.length; ++subIndex)
  445. {
  446. parent = GetAttributeEditorParent(list, index, subIndex);
  447. if (parent is null)
  448. break;
  449. Variant firstValue = vector[subIndex];
  450. bool sameValue = true;
  451. Array<Variant> varValues;
  452. // Reevaluate aach variant in the vector
  453. for (uint i = 0; i < values.length; ++i)
  454. {
  455. Array<Variant>@ vector = values[i].GetVariantVector();
  456. if (subIndex < vector.length)
  457. {
  458. Variant value = vector[subIndex];
  459. varValues.Push(value);
  460. if (value != firstValue)
  461. sameValue = false;
  462. }
  463. else
  464. sameValue = false;
  465. }
  466. // The individual variant in the list is not an attribute of the serializable, the structure is reused for convenience
  467. AttributeInfo info;
  468. info.type = firstValue.type;
  469. LoadAttributeEditor(parent, firstValue, info, editable, sameValue, varValues);
  470. }
  471. }
  472. else if (type == VAR_VARIANTMAP)
  473. {
  474. UIElement@ list = parent.parent;
  475. VariantMap map = value.GetVariantMap();
  476. Array<ShortStringHash>@ keys = map.keys;
  477. for (uint subIndex = 0; subIndex < keys.length; ++subIndex)
  478. {
  479. parent = GetAttributeEditorParent(list, index, subIndex);
  480. if (parent is null)
  481. break;
  482. String varName = GetVariableName(keys[subIndex]);
  483. if (varName.empty)
  484. continue;
  485. Variant firstValue = map[keys[subIndex]];
  486. bool sameValue = true;
  487. Array<Variant> varValues;
  488. // Reevaluate each variant in the map
  489. for (uint i = 0; i < values.length; ++i)
  490. {
  491. VariantMap map = values[i].GetVariantMap();
  492. if (map.Contains(keys[subIndex]))
  493. {
  494. Variant value = map[keys[subIndex]];
  495. varValues.Push(value);
  496. if (value != firstValue)
  497. sameValue = false;
  498. }
  499. else
  500. sameValue = false;
  501. }
  502. // The individual variant in the map is not an attribute of the serializable, the structure is reused for convenience
  503. AttributeInfo info;
  504. info.type = firstValue.type;
  505. LoadAttributeEditor(parent, firstValue, info, editable, sameValue, varValues);
  506. }
  507. }
  508. else
  509. {
  510. Array<Array<String> > coordinates;
  511. for (uint i = 0; i < values.length; ++i)
  512. {
  513. Variant value = values[i];
  514. // Convert Quaternion value to Vector3 value first
  515. if (type == VAR_QUATERNION)
  516. value = value.GetQuaternion().eulerAngles;
  517. coordinates.Push(value.ToString().Split(' '));
  518. }
  519. for (uint i = 0; i < coordinates[0].length; ++i)
  520. {
  521. String value = coordinates[0][i];
  522. bool coordinateSameValue = true;
  523. if (!sameValue)
  524. {
  525. // Reevaluate each coordinate
  526. for (uint j = 1; j < coordinates.length; ++j)
  527. {
  528. if (coordinates[j][i] != value)
  529. {
  530. coordinateSameValue = false;
  531. break;
  532. }
  533. }
  534. }
  535. SetEditable(SetValue(parent.children[i + 1], value, coordinateSameValue), editable && coordinateSameValue);
  536. }
  537. }
  538. }
  539. void StoreAttributeEditor(UIElement@ parent, Array<Serializable@>@ serializables, uint index, uint subIndex, uint coordinate)
  540. {
  541. AttributeInfo info = serializables[0].attributeInfos[index];
  542. if (info.type == VAR_RESOURCEREFLIST)
  543. {
  544. for (uint i = 0; i < serializables.length; ++i)
  545. {
  546. ResourceRefList refList = serializables[i].attributes[index].GetResourceRefList();
  547. Variant[] values(1);
  548. GetEditorValue(parent, VAR_RESOURCEREF, null, coordinate, values);
  549. ResourceRef ref = values[0].GetResourceRef();
  550. refList.names[subIndex] = ref.name;
  551. serializables[i].attributes[index] = Variant(refList);
  552. }
  553. }
  554. else if (info.type == VAR_VARIANTVECTOR)
  555. {
  556. for (uint i = 0; i < serializables.length; ++i)
  557. {
  558. Array<Variant>@ vector = serializables[i].attributes[index].GetVariantVector();
  559. Variant[] values;
  560. values.Push(vector[subIndex]); // Each individual variant may have multiple coordinates itself
  561. GetEditorValue(parent, vector[subIndex].type, null, coordinate, values);
  562. vector[subIndex] = values[0];
  563. serializables[i].attributes[index] = Variant(vector);
  564. }
  565. }
  566. else if (info.type == VAR_VARIANTMAP)
  567. {
  568. VariantMap map = serializables[0].attributes[index].GetVariantMap();
  569. ShortStringHash key(parent.vars["Key"].GetUInt());
  570. for (uint i = 0; i < serializables.length; ++i)
  571. {
  572. VariantMap map = serializables[i].attributes[index].GetVariantMap();
  573. Variant[] values;
  574. values.Push(map[key]); // Each individual variant may have multiple coordinates itself
  575. GetEditorValue(parent, map[key].type, null, coordinate, values);
  576. map[key] = values[0];
  577. serializables[i].attributes[index] = Variant(map);
  578. }
  579. }
  580. else
  581. {
  582. Array<Variant> values;
  583. for (uint i = 0; i < serializables.length; ++i)
  584. values.Push(serializables[i].attributes[index]);
  585. GetEditorValue(parent, info.type, info.enumNames, coordinate, values);
  586. for (uint i = 0; i < serializables.length; ++i)
  587. serializables[i].attributes[index] = values[i];
  588. }
  589. }
  590. void FillValue(Array<Variant>& values, const Variant&in value)
  591. {
  592. for (uint i = 0; i < values.length; ++i)
  593. values[i] = value;
  594. }
  595. void SanitizeNumericalValue(VariantType type, String& value)
  596. {
  597. if (type >= VAR_FLOAT && type <= VAR_COLOR)
  598. value = String(value.ToFloat());
  599. else if (type == VAR_INT || type == VAR_INTRECT || type == VAR_INTVECTOR2)
  600. value = String(value.ToInt());
  601. }
  602. void GetEditorValue(UIElement@ parent, VariantType type, Array<String>@ enumNames, uint coordinate, Array<Variant>& values)
  603. {
  604. LineEdit@ attrEdit = parent.children[coordinate + 1];
  605. if (type == VAR_STRING)
  606. FillValue(values, Variant(attrEdit.text.Trimmed()));
  607. else if (type == VAR_BOOL)
  608. {
  609. CheckBox@ attrEdit = parent.children[1];
  610. FillValue(values, Variant(attrEdit.checked));
  611. }
  612. else if (type == VAR_FLOAT)
  613. FillValue(values, Variant(attrEdit.text.ToFloat()));
  614. else if (type == VAR_QUATERNION)
  615. {
  616. float value = attrEdit.text.ToFloat();
  617. for (uint i = 0; i < values.length; ++i)
  618. {
  619. float[] data = values[i].GetQuaternion().eulerAngles.data;
  620. data[coordinate] = value;
  621. values[i] = Quaternion(Vector3(data));
  622. }
  623. }
  624. else if (type == VAR_INT)
  625. {
  626. if (enumNames is null || enumNames.empty)
  627. FillValue(values, Variant(attrEdit.text.ToInt()));
  628. else
  629. {
  630. DropDownList@ attrEdit = parent.children[1];
  631. FillValue(values, Variant(attrEdit.selection));
  632. }
  633. }
  634. else if (type == VAR_RESOURCEREF)
  635. {
  636. LineEdit@ attrEdit = parent.children[0];
  637. ResourceRef ref;
  638. ref.name = attrEdit.text.Trimmed();
  639. ref.type = ShortStringHash(attrEdit.vars[TYPE_VAR].GetUInt());
  640. FillValue(values, Variant(ref));
  641. }
  642. else
  643. {
  644. String value = attrEdit.text;
  645. SanitizeNumericalValue(type, value);
  646. for (uint i = 0; i < values.length; ++i)
  647. {
  648. String[] data = values[i].ToString().Split(' ');
  649. data[coordinate] = value;
  650. values[i] = Variant(type, Join(data, " "));
  651. }
  652. }
  653. }
  654. void UpdateAttributes(Array<Serializable@>@ serializables, ListView@ list, bool& fullUpdate)
  655. {
  656. // If attributes have changed structurally, do a full update
  657. uint count = GetAttributeEditorCount(serializables);
  658. if (fullUpdate == false)
  659. {
  660. if (list.contentElement.numChildren != count)
  661. fullUpdate = true;
  662. }
  663. // Remember the old scroll position so that a full update does not feel as jarring
  664. IntVector2 oldViewPos = list.viewPosition;
  665. if (fullUpdate)
  666. {
  667. list.RemoveAllItems();
  668. Array<UIElement@> children = list.GetChildren();
  669. for (uint i = 0; i < children.length; ++i)
  670. {
  671. if (!children[i].internal)
  672. children[i].Remove();
  673. }
  674. }
  675. if (serializables.empty)
  676. return;
  677. // If there are many serializables, they must share same attribute structure (up to certain number if not all)
  678. for (uint i = 0; i < serializables[0].numAttributes; ++i)
  679. {
  680. AttributeInfo info = serializables[0].attributeInfos[i];
  681. if (!showNonEditableAttribute && info.mode & AM_NOEDIT != 0)
  682. continue;
  683. // Use the default value (could be instance's default value) of the first serializable as the default for all
  684. info.defaultValue = serializables[0].attributeDefaults[i];
  685. if (fullUpdate)
  686. CreateAttributeEditor(list, serializables, info, i, 0);
  687. LoadAttributeEditor(list, serializables, info, i);
  688. }
  689. if (fullUpdate)
  690. list.viewPosition = oldViewPos;
  691. }
  692. void EditAttribute(StringHash eventType, VariantMap& eventData)
  693. {
  694. // Changing elements programmatically may cause events to be sent. Stop possible infinite loop in that case.
  695. if (inLoadAttributeEditor)
  696. return;
  697. UIElement@ attrEdit = eventData["Element"].GetUIElement();
  698. UIElement@ parent = attrEdit.parent;
  699. Array<Serializable@>@ serializables = GetAttributeEditorTargets(attrEdit);
  700. if (serializables.empty)
  701. return;
  702. uint index = attrEdit.vars["Index"].GetUInt();
  703. uint subIndex = attrEdit.vars["SubIndex"].GetUInt();
  704. uint coordinate = attrEdit.vars["Coordinate"].GetUInt();
  705. bool intermediateEdit = eventType == TEXT_CHANGED_EVENT_TYPE;
  706. // Do the editor pre logic before attribute is being modified
  707. if (!PreEditAttribute(serializables, index))
  708. return;
  709. inEditAttribute = true;
  710. // Store old values so that PostEditAttribute can create undo actions
  711. Array<Variant> oldValues;
  712. for (uint i = 0; i < serializables.length; ++i)
  713. oldValues.Push(serializables[i].attributes[index]);
  714. StoreAttributeEditor(parent, serializables, index, subIndex, coordinate);
  715. for (uint i = 0; i < serializables.length; ++i)
  716. serializables[i].ApplyAttributes();
  717. // Do the editor post logic after attribute has been modified.
  718. PostEditAttribute(serializables, index, oldValues);
  719. inEditAttribute = false;
  720. // If not an intermediate edit, reload the editor fields with validated values
  721. // (attributes may have interactions; therefore we load everything, not just the value being edited)
  722. if (!intermediateEdit)
  723. attributesDirty = true;
  724. }
  725. // Resource picker functionality
  726. const uint ACTION_PICK = 1;
  727. const uint ACTION_OPEN = 2;
  728. const uint ACTION_EDIT = 4;
  729. const uint ACTION_TEST = 8;
  730. class ResourcePicker
  731. {
  732. String typeName;
  733. ShortStringHash type;
  734. String lastPath;
  735. uint lastFilter;
  736. Array<String> filters;
  737. uint actions;
  738. ResourcePicker(const String&in typeName_, const String&in filter_, uint actions_ = ACTION_PICK | ACTION_OPEN)
  739. {
  740. typeName = typeName_;
  741. type = ShortStringHash(typeName_);
  742. actions = actions_;
  743. filters.Push(filter_);
  744. filters.Push("*.*");
  745. lastFilter = 0;
  746. }
  747. ResourcePicker(const String&in typeName_, const Array<String>@ filters_, uint actions_ = ACTION_PICK | ACTION_OPEN)
  748. {
  749. typeName = typeName_;
  750. type = ShortStringHash(typeName_);
  751. filters = filters_;
  752. actions = actions_;
  753. filters.Push("*.*");
  754. lastFilter = 0;
  755. }
  756. };
  757. Array<ResourcePicker@> resourcePickers;
  758. Array<Serializable@> resourceTargets;
  759. uint resourcePickIndex = 0;
  760. uint resourcePickSubIndex = 0;
  761. ResourcePicker@ resourcePicker = null;
  762. void InitResourcePicker()
  763. {
  764. // Fill resource picker data
  765. Array<String> fontFilters = {"*.ttf", "*.fnt", "*.xml"};
  766. Array<String> imageFilters = {"*.png", "*.jpg"};
  767. Array<String> textureFilters = {"*.dds", "*.png", "*.jpg", "*.bmp", "*.ktx", "*.pvr"};
  768. Array<String> soundFilters = {"*.wav","*.ogg"};
  769. Array<String> scriptFilters = {"*.as", "*.asc"};
  770. Array<String> materialFilters = {"*.xml", "*.material"};
  771. resourcePickers.Push(ResourcePicker("Animation", "*.ani", ACTION_PICK | ACTION_TEST));
  772. resourcePickers.Push(ResourcePicker("Font", fontFilters));
  773. resourcePickers.Push(ResourcePicker("Image", imageFilters));
  774. resourcePickers.Push(ResourcePicker("Model", "*.mdl", ACTION_PICK));
  775. resourcePickers.Push(ResourcePicker("Material", materialFilters, ACTION_PICK | ACTION_OPEN | ACTION_EDIT));
  776. resourcePickers.Push(ResourcePicker("Technique", "*.xml"));
  777. resourcePickers.Push(ResourcePicker("Texture2D", textureFilters));
  778. resourcePickers.Push(ResourcePicker("TextureCube", "*.xml"));
  779. resourcePickers.Push(ResourcePicker("ScriptFile", scriptFilters));
  780. resourcePickers.Push(ResourcePicker("XMLFile", "*.xml"));
  781. resourcePickers.Push(ResourcePicker("Sound", soundFilters));
  782. }
  783. ResourcePicker@ GetResourcePicker(ShortStringHash resourceType)
  784. {
  785. for (uint i = 0; i < resourcePickers.length; ++i)
  786. {
  787. if (resourcePickers[i].type == resourceType)
  788. return resourcePickers[i];
  789. }
  790. return null;
  791. }
  792. void PickResource(StringHash eventType, VariantMap& eventData)
  793. {
  794. UIElement@ button = eventData["Element"].GetUIElement();
  795. LineEdit@ attrEdit = button.parent.children[0];
  796. Array<Serializable@>@ targets = GetAttributeEditorTargets(attrEdit);
  797. if (targets.empty)
  798. return;
  799. resourcePickIndex = attrEdit.vars["Index"].GetUInt();
  800. resourcePickSubIndex = attrEdit.vars["SubIndex"].GetUInt();
  801. AttributeInfo info = targets[0].attributeInfos[resourcePickIndex];
  802. ShortStringHash resourceType;
  803. if (info.type == VAR_RESOURCEREF)
  804. resourceType = targets[0].attributes[resourcePickIndex].GetResourceRef().type;
  805. else if (info.type == VAR_RESOURCEREFLIST)
  806. resourceType = targets[0].attributes[resourcePickIndex].GetResourceRefList().type;
  807. else if (info.type == VAR_VARIANTVECTOR)
  808. resourceType = targets[0].attributes[resourcePickIndex].GetVariantVector()[resourcePickSubIndex].GetResourceRef().type;
  809. @resourcePicker = GetResourcePicker(resourceType);
  810. if (resourcePicker is null)
  811. return;
  812. resourceTargets.Clear();
  813. for (uint i = 0; i < targets.length; ++i)
  814. resourceTargets.Push(targets[i]);
  815. String lastPath = resourcePicker.lastPath;
  816. if (lastPath.empty)
  817. lastPath = sceneResourcePath;
  818. CreateFileSelector("Pick " + resourcePicker.typeName, "OK", "Cancel", lastPath, resourcePicker.filters, resourcePicker.lastFilter);
  819. SubscribeToEvent(uiFileSelector, "FileSelected", "PickResourceDone");
  820. }
  821. void PickResourceDone(StringHash eventType, VariantMap& eventData)
  822. {
  823. StoreResourcePickerPath();
  824. CloseFileSelector();
  825. if (!eventData["OK"].GetBool())
  826. {
  827. resourceTargets.Clear();
  828. @resourcePicker = null;
  829. return;
  830. }
  831. if (resourcePicker is null)
  832. return;
  833. // Validate the resource. It must come from within a registered resource directory, and be loaded successfully
  834. String resourceName = eventData["FileName"].GetString();
  835. Resource@ res = GetPickedResource(resourceName);
  836. if (res is null)
  837. {
  838. @resourcePicker = null;
  839. return;
  840. }
  841. // Store old values so that PostEditAttribute can create undo actions
  842. Array<Variant> oldValues;
  843. for (uint i = 0; i < resourceTargets.length; ++i)
  844. oldValues.Push(resourceTargets[i].attributes[resourcePickIndex]);
  845. for (uint i = 0; i < resourceTargets.length; ++i)
  846. {
  847. Serializable@ target = resourceTargets[i];
  848. AttributeInfo info = target.attributeInfos[resourcePickIndex];
  849. if (info.type == VAR_RESOURCEREF)
  850. {
  851. ResourceRef ref = target.attributes[resourcePickIndex].GetResourceRef();
  852. ref.type = res.type;
  853. ref.name = res.name;
  854. target.attributes[resourcePickIndex] = Variant(ref);
  855. target.ApplyAttributes();
  856. }
  857. else if (info.type == VAR_RESOURCEREFLIST)
  858. {
  859. ResourceRefList refList = target.attributes[resourcePickIndex].GetResourceRefList();
  860. if (resourcePickSubIndex < refList.length)
  861. {
  862. refList.names[resourcePickSubIndex] = res.name;
  863. target.attributes[resourcePickIndex] = Variant(refList);
  864. target.ApplyAttributes();
  865. }
  866. }
  867. else if (info.type == VAR_VARIANTVECTOR)
  868. {
  869. Array<Variant>@ attrs = target.attributes[resourcePickIndex].GetVariantVector();
  870. ResourceRef ref = attrs[resourcePickSubIndex].GetResourceRef();
  871. ref.type = res.type;
  872. ref.name = res.name;
  873. attrs[resourcePickSubIndex] = ref;
  874. target.attributes[resourcePickIndex] = Variant(attrs);
  875. target.ApplyAttributes();
  876. }
  877. }
  878. PostEditAttribute(resourceTargets, resourcePickIndex, oldValues);
  879. UpdateAttributeInspector(false);
  880. resourceTargets.Clear();
  881. @resourcePicker = null;
  882. }
  883. void StoreResourcePickerPath()
  884. {
  885. // Store filter and directory for next time
  886. if (resourcePicker !is null && uiFileSelector !is null)
  887. {
  888. resourcePicker.lastPath = uiFileSelector.path;
  889. resourcePicker.lastFilter = uiFileSelector.filterIndex;
  890. }
  891. }
  892. Resource@ GetPickedResource(String resourceName)
  893. {
  894. resourceName = GetResourceNameFromFullName(resourceName);
  895. Resource@ res = cache.GetResource(resourcePicker.typeName, resourceName);
  896. if (res is null)
  897. log.Warning("Cannot find resource type: " + resourcePicker.typeName + " Name:" + resourceName);
  898. return res;
  899. }
  900. String GetResourceNameFromFullName(const String&in resourceName)
  901. {
  902. Array<String>@ resourceDirs = cache.resourceDirs;
  903. for (uint i = 0; i < resourceDirs.length; ++i)
  904. {
  905. if (!resourceName.ToLower().StartsWith(resourceDirs[i].ToLower()))
  906. continue;
  907. return resourceName.Substring(resourceDirs[i].length);
  908. }
  909. return ""; // Not found
  910. }
  911. void OpenResource(StringHash eventType, VariantMap& eventData)
  912. {
  913. UIElement@ button = eventData["Element"].GetUIElement();
  914. LineEdit@ attrEdit = button.parent.children[0];
  915. String fileName = attrEdit.text.Trimmed();
  916. if (fileName.empty)
  917. return;
  918. Array<String>@ resourceDirs = cache.resourceDirs;
  919. for (uint i = 0; i < resourceDirs.length; ++i)
  920. {
  921. String fullPath = resourceDirs[i] + fileName;
  922. if (fileSystem.FileExists(fullPath))
  923. {
  924. fileSystem.SystemOpen(fullPath, "");
  925. return;
  926. }
  927. }
  928. }
  929. void EditResource(StringHash eventType, VariantMap& eventData)
  930. {
  931. UIElement@ button = eventData["Element"].GetUIElement();
  932. LineEdit@ attrEdit = button.parent.children[0];
  933. String fileName = attrEdit.text.Trimmed();
  934. if (fileName.empty)
  935. return;
  936. ShortStringHash resourceType(attrEdit.vars[TYPE_VAR].GetUInt());
  937. Resource@ resource = cache.GetResource(resourceType, fileName);
  938. if (resource !is null)
  939. {
  940. // For now only Materials can be edited
  941. if (resource.typeName == "Material")
  942. EditMaterial(cast<Material>(resource));
  943. }
  944. }
  945. void TestResource(StringHash eventType, VariantMap& eventData)
  946. {
  947. UIElement@ button = eventData["Element"].GetUIElement();
  948. LineEdit@ attrEdit = button.parent.children[0];
  949. ShortStringHash resourceType(attrEdit.vars[TYPE_VAR].GetUInt());
  950. // For now only Animations can be tested
  951. ShortStringHash animType("Animation");
  952. if (resourceType == animType)
  953. TestAnimation(attrEdit);
  954. }
  955. void TestAnimation(UIElement@ attrEdit)
  956. {
  957. // Note: only supports the AnimationState array in AnimatedModel, and if only 1 model selected
  958. Array<Serializable@>@ targets = GetAttributeEditorTargets(attrEdit);
  959. if (targets.length != 1)
  960. return;
  961. AnimatedModel@ model = cast<AnimatedModel>(targets[0]);
  962. if (model is null)
  963. return;
  964. uint animStateIndex = (attrEdit.vars["SubIndex"].GetUInt() - 1) / 6;
  965. if (testAnimState.Get() is null)
  966. {
  967. testAnimState = model.GetAnimationState(animStateIndex);
  968. AnimationState@ animState = testAnimState.Get();
  969. if (animState !is null)
  970. animState.time = 0; // Start from beginning
  971. }
  972. else
  973. testAnimState = null;
  974. }
  975. void UpdateTestAnimation(float timeStep)
  976. {
  977. AnimationState@ animState = testAnimState.Get();
  978. if (animState !is null)
  979. {
  980. // If has also an AnimationController, and scene update is enabled, check if it is also driving the animation
  981. // and skip in that case (avoid double speed animation)
  982. if (runUpdate)
  983. {
  984. AnimatedModel@ model = animState.model;
  985. if (model !is null)
  986. {
  987. Node@ node = model.node;
  988. if (node !is null)
  989. {
  990. AnimationController@ ctrl = node.GetComponent("AnimationController");
  991. Animation@ anim = animState.animation;
  992. if (ctrl !is null && anim !is null)
  993. {
  994. if (ctrl.IsPlaying(anim.name))
  995. return;
  996. }
  997. }
  998. }
  999. }
  1000. animState.AddTime(timeStep);
  1001. }
  1002. }
  1003. // VariantVector decoding & editing for certain components
  1004. class VectorStruct
  1005. {
  1006. String componentTypeName;
  1007. String attributeName;
  1008. Array<String> variableNames;
  1009. uint restartIndex;
  1010. VectorStruct(const String&in componentTypeName_, const String&in attributeName_, const Array<String>@ variableNames_, uint restartIndex_)
  1011. {
  1012. componentTypeName = componentTypeName_;
  1013. attributeName = attributeName_;
  1014. variableNames = variableNames_;
  1015. restartIndex = restartIndex_;
  1016. }
  1017. };
  1018. Array<VectorStruct@> vectorStructs;
  1019. void InitVectorStructs()
  1020. {
  1021. // Fill vector structure data
  1022. Array<String> billboardVariables = {
  1023. "Billboard Count",
  1024. " Position",
  1025. " Size",
  1026. " UV Coordinates",
  1027. " Color",
  1028. " Rotation",
  1029. " Is Enabled"
  1030. };
  1031. vectorStructs.Push(VectorStruct("BillboardSet", "Billboards", billboardVariables, 1));
  1032. Array<String> animationStateVariables = {
  1033. "Anim State Count",
  1034. " Animation",
  1035. " Start Bone",
  1036. " Is Looped",
  1037. " Weight",
  1038. " Time",
  1039. " Layer"
  1040. };
  1041. vectorStructs.Push(VectorStruct("AnimatedModel", "Animation States", animationStateVariables, 1));
  1042. Array<String> particleColorVariables = {
  1043. "Color Animation Frames",
  1044. " Color",
  1045. " Time"
  1046. };
  1047. vectorStructs.Push(VectorStruct("ParticleEmitter", "Particle Colors", particleColorVariables, 1));
  1048. Array<String> particleUVAnimVariables = {
  1049. "UV Animation Frames",
  1050. " UV Coords",
  1051. " Time"
  1052. };
  1053. vectorStructs.Push(VectorStruct("ParticleEmitter", "UV Animation", particleUVAnimVariables, 1));
  1054. Array<String> staticModelGroupInstanceVariables = {
  1055. "Instance Count",
  1056. " NodeID"
  1057. };
  1058. vectorStructs.Push(VectorStruct("StaticModelGroup", "Instance Nodes", staticModelGroupInstanceVariables, 1));
  1059. Array<String> splineControlPointVariables = {
  1060. "Control Point Count",
  1061. " Point"
  1062. };
  1063. vectorStructs.Push(VectorStruct("Spline", "Control Points", splineControlPointVariables, 1));
  1064. }
  1065. VectorStruct@ GetVectorStruct(Array<Serializable@>@ serializables, uint index)
  1066. {
  1067. AttributeInfo info = serializables[0].attributeInfos[index];
  1068. for (uint i = 0; i < vectorStructs.length; ++i)
  1069. {
  1070. if (vectorStructs[i].componentTypeName == serializables[0].typeName && vectorStructs[i].attributeName == info.name)
  1071. return vectorStructs[i];
  1072. }
  1073. return null;
  1074. }
  1075. int GetAttributeIndex(Serializable@ serializable, const String&in attrName)
  1076. {
  1077. for (uint i = 0; i < serializable.numAttributes; ++i)
  1078. {
  1079. if (serializable.attributeInfos[i].name.Compare(attrName, false) == 0)
  1080. return i;
  1081. }
  1082. return -1;
  1083. }