AttributeEditor.as 50 KB

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