EditorUIElement.as 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723
  1. // Urho3D editor UI-element handling
  2. UIElement@ editorUIElement;
  3. XMLFile@ uiElementDefaultStyle;
  4. Array<String> availableStyles;
  5. UIElement@ editUIElement;
  6. Array<Serializable@> selectedUIElements;
  7. Array<Serializable@> editUIElements;
  8. Array<XMLFile@> uiElementCopyBuffer;
  9. bool suppressUIElementChanges = false;
  10. const StringHash FILENAME_VAR("FileName");
  11. const StringHash MODIFIED_VAR("Modified");
  12. const StringHash CHILD_ELEMENT_FILENAME_VAR("ChildElemFileName");
  13. void ClearUIElementSelection()
  14. {
  15. editUIElement = null;
  16. selectedUIElements.Clear();
  17. editUIElements.Clear();
  18. }
  19. void CreateRootUIElement()
  20. {
  21. // Create a root UIElement only once here, do not confuse this with ui.root itself
  22. editorUIElement = ui.root.CreateChild("UIElement");
  23. editorUIElement.name = "UI";
  24. editorUIElement.SetSize(graphics.width, graphics.height);
  25. editorUIElement.traversalMode = TM_DEPTH_FIRST; // This is needed for root-like element to prevent artifacts
  26. editorUIElement.priority = -1000; // All user-created UI elements have lowest priority so they do not cover editor's windows
  27. // This is needed to distinguish our own element events from Editor's UI element events
  28. editorUIElement.elementEventSender = true;
  29. SubscribeToEvent(editorUIElement, "ElementAdded", "HandleUIElementAdded");
  30. SubscribeToEvent(editorUIElement, "ElementRemoved", "HandleUIElementRemoved");
  31. // Since this root UIElement is not being handled by above handlers, update it into hierarchy list manually as another list root item
  32. UpdateHierarchyItem(M_MAX_UNSIGNED, editorUIElement, null);
  33. }
  34. bool NewUIElement(const String&in typeName)
  35. {
  36. // If no edit element then parented to root
  37. UIElement@ parent = editUIElement !is null ? editUIElement : editorUIElement;
  38. UIElement@ element = parent.CreateChild(typeName);
  39. if (element !is null)
  40. {
  41. // Use the predefined UI style if set, otherwise use editor's own UI style
  42. XMLFile@ defaultStyle = uiElementDefaultStyle !is null ? uiElementDefaultStyle : uiStyle;
  43. if (editUIElement is null)
  44. {
  45. // If parented to root, set the internal variables
  46. element.vars[FILENAME_VAR] = "";
  47. element.vars[MODIFIED_VAR] = false;
  48. // set a default UI style
  49. element.defaultStyle = defaultStyle;
  50. // and position the newly created element at center
  51. CenterDialog(element);
  52. }
  53. // Apply the auto style
  54. element.style = AUTO_STYLE;
  55. // Do not allow UI subsystem to reorder children while editing the element in the editor
  56. element.sortChildren = false;
  57. // Create an undo action for the create
  58. CreateUIElementAction action;
  59. action.Define(element);
  60. SaveEditAction(action);
  61. SetUIElementModified(element);
  62. FocusUIElement(element);
  63. }
  64. return true;
  65. }
  66. void ResetSortChildren(UIElement@ element)
  67. {
  68. element.sortChildren = false;
  69. // Perform the action recursively for child elements
  70. for (uint i = 0; i < element.numChildren; ++i)
  71. ResetSortChildren(element.children[i]);
  72. }
  73. void OpenUILayout(const String&in fileName)
  74. {
  75. if (fileName.empty)
  76. return;
  77. ui.cursor.shape = CS_BUSY;
  78. // Check if the UI element has been opened before
  79. if (editorUIElement.GetChild(FILENAME_VAR, Variant(fileName)) !is null)
  80. {
  81. MessageBox("UI element is already opened.\n" + fileName);
  82. return;
  83. }
  84. // Always load from the filesystem, not from resource paths
  85. if (!fileSystem.FileExists(fileName))
  86. {
  87. MessageBox("No such file.\n" + fileName);
  88. return;
  89. }
  90. File file(fileName, FILE_READ);
  91. if (!file.open)
  92. {
  93. MessageBox("Could not open file.\n" + fileName);
  94. return;
  95. }
  96. // Add the UI layout's resource path in case it's necessary
  97. SetResourcePath(GetPath(fileName), true, true);
  98. XMLFile@ xmlFile = XMLFile();
  99. xmlFile.Load(file);
  100. suppressUIElementChanges = true;
  101. // If uiElementDefaultStyle is not set then automatically fallback to use the editor's own default style
  102. UIElement@ element = ui.LoadLayout(xmlFile, uiElementDefaultStyle);
  103. if (element !is null)
  104. {
  105. element.vars[FILENAME_VAR] = fileName;
  106. element.vars[MODIFIED_VAR] = false;
  107. // Do not allow UI subsystem to reorder children while editing the element in the editor
  108. ResetSortChildren(element);
  109. // Register variable names from the 'enriched' XMLElement, if any
  110. RegisterUIElementVar(xmlFile.root);
  111. editorUIElement.AddChild(element);
  112. UpdateHierarchyItem(element);
  113. FocusUIElement(element);
  114. ClearEditActions();
  115. }
  116. else
  117. MessageBox("Could not load UI layout successfully!\nSee Urho3D.log for more detail.");
  118. suppressUIElementChanges = false;
  119. }
  120. bool CloseUILayout()
  121. {
  122. ui.cursor.shape = CS_BUSY;
  123. if (messageBoxCallback is null)
  124. {
  125. for (uint i = 0; i < selectedUIElements.length; ++i)
  126. {
  127. UIElement@ element = GetTopLevelUIElement(selectedUIElements[i]);
  128. if (element !is null && element.vars[MODIFIED_VAR].GetBool())
  129. {
  130. MessageBox@ messageBox = MessageBox("UI layout has been modified.\nContinue to close?", "Warning");
  131. if (messageBox.window !is null)
  132. {
  133. Button@ cancelButton = messageBox.window.GetChild("CancelButton", true);
  134. cancelButton.visible = true;
  135. cancelButton.focus = true;
  136. SubscribeToEvent(messageBox, "MessageACK", "HandleMessageAcknowledgement");
  137. messageBoxCallback = @CloseUILayout;
  138. return false;
  139. }
  140. }
  141. }
  142. }
  143. else
  144. messageBoxCallback = null;
  145. suppressUIElementChanges = true;
  146. for (uint i = 0; i < selectedUIElements.length; ++i)
  147. {
  148. UIElement@ element = GetTopLevelUIElement(selectedUIElements[i]);
  149. if (element !is null)
  150. {
  151. element.Remove();
  152. UpdateHierarchyItem(GetListIndex(element), null, null);
  153. }
  154. }
  155. hierarchyList.ClearSelection();
  156. ClearEditActions();
  157. suppressUIElementChanges = false;
  158. return true;
  159. }
  160. bool CloseAllUILayouts()
  161. {
  162. ui.cursor.shape = CS_BUSY;
  163. if (messageBoxCallback is null)
  164. {
  165. for (uint i = 0; i < editorUIElement.numChildren; ++i)
  166. {
  167. UIElement@ element = editorUIElement.children[i];
  168. if (element !is null && element.vars[MODIFIED_VAR].GetBool())
  169. {
  170. MessageBox@ messageBox = MessageBox("UI layout has been modified.\nContinue to close?", "Warning");
  171. if (messageBox.window !is null)
  172. {
  173. Button@ cancelButton = messageBox.window.GetChild("CancelButton", true);
  174. cancelButton.visible = true;
  175. cancelButton.focus = true;
  176. SubscribeToEvent(messageBox, "MessageACK", "HandleMessageAcknowledgement");
  177. messageBoxCallback = @CloseAllUILayouts;
  178. return false;
  179. }
  180. }
  181. }
  182. }
  183. else
  184. messageBoxCallback = null;
  185. suppressUIElementChanges = true;
  186. editorUIElement.RemoveAllChildren();
  187. UpdateHierarchyItem(editorUIElement, true);
  188. // Reset element ID number generator
  189. uiElementNextID = UI_ELEMENT_BASE_ID + 1;
  190. hierarchyList.ClearSelection();
  191. ClearEditActions();
  192. suppressUIElementChanges = false;
  193. return true;
  194. }
  195. bool SaveUILayout(const String&in fileName)
  196. {
  197. if (fileName.empty)
  198. return false;
  199. ui.cursor.shape = CS_BUSY;
  200. MakeBackup(fileName);
  201. File file(fileName, FILE_WRITE);
  202. if (!file.open)
  203. {
  204. MessageBox("Could not open file.\n" + fileName);
  205. return false;
  206. }
  207. UIElement@ element = GetTopLevelUIElement(editUIElement);
  208. if (element is null)
  209. return false;
  210. XMLFile@ elementData = XMLFile();
  211. XMLElement rootElem = elementData.CreateRoot("element");
  212. bool success = element.SaveXML(rootElem);
  213. RemoveBackup(success, fileName);
  214. if (success)
  215. {
  216. FilterInternalVars(rootElem);
  217. success = elementData.Save(file);
  218. if (success)
  219. {
  220. element.vars[FILENAME_VAR] = fileName;
  221. SetUIElementModified(element, false);
  222. }
  223. }
  224. if (!success)
  225. MessageBox("Could not save UI layout successfully!\nSee Urho3D.log for more detail.");
  226. return success;
  227. }
  228. bool SaveUILayoutWithExistingName()
  229. {
  230. ui.cursor.shape = CS_BUSY;
  231. UIElement@ element = GetTopLevelUIElement(editUIElement);
  232. if (element is null)
  233. return false;
  234. String fileName = element.GetVar(FILENAME_VAR).GetString();
  235. if (fileName.empty)
  236. return PickFile(); // No name yet, so pick one
  237. else
  238. return SaveUILayout(fileName);
  239. }
  240. void LoadChildUIElement(const String&in fileName)
  241. {
  242. if (fileName.empty)
  243. return;
  244. ui.cursor.shape = CS_BUSY;
  245. if (!fileSystem.FileExists(fileName))
  246. {
  247. MessageBox("No such file.\n" + fileName);
  248. return;
  249. }
  250. File file(fileName, FILE_READ);
  251. if (!file.open)
  252. {
  253. MessageBox("Could not open file.\n" + fileName);
  254. return;
  255. }
  256. XMLFile@ xmlFile = XMLFile();
  257. xmlFile.Load(file);
  258. suppressUIElementChanges = true;
  259. if (editUIElement.LoadChildXML(xmlFile, uiElementDefaultStyle !is null ? uiElementDefaultStyle : uiStyle) !is null)
  260. {
  261. XMLElement rootElem = xmlFile.root;
  262. uint index = rootElem.HasAttribute("index") ? rootElem.GetUInt("index") : editUIElement.numChildren - 1;
  263. UIElement@ element = editUIElement.children[index];
  264. ResetSortChildren(element);
  265. RegisterUIElementVar(xmlFile.root);
  266. element.vars[CHILD_ELEMENT_FILENAME_VAR] = fileName;
  267. if (index == editUIElement.numChildren - 1)
  268. UpdateHierarchyItem(element);
  269. else
  270. // If not last child, find the list index of the next sibling as the insertion index
  271. UpdateHierarchyItem(GetListIndex(editUIElement.children[index + 1]), element, hierarchyList.items[GetListIndex(editUIElement)]);
  272. SetUIElementModified(element);
  273. // Create an undo action for the load
  274. CreateUIElementAction action;
  275. action.Define(element);
  276. SaveEditAction(action);
  277. FocusUIElement(element);
  278. }
  279. suppressUIElementChanges = false;
  280. }
  281. bool SaveChildUIElement(const String&in fileName)
  282. {
  283. if (fileName.empty)
  284. return false;
  285. ui.cursor.shape = CS_BUSY;
  286. MakeBackup(fileName);
  287. File file(fileName, FILE_WRITE);
  288. if (!file.open)
  289. {
  290. MessageBox("Could not open file.\n" + fileName);
  291. return false;
  292. }
  293. XMLFile@ elementData = XMLFile();
  294. XMLElement rootElem = elementData.CreateRoot("element");
  295. bool success = editUIElement.SaveXML(rootElem);
  296. RemoveBackup(success, fileName);
  297. if (success)
  298. {
  299. FilterInternalVars(rootElem);
  300. success = elementData.Save(file);
  301. if (success)
  302. editUIElement.vars[CHILD_ELEMENT_FILENAME_VAR] = fileName;
  303. }
  304. if (!success)
  305. MessageBox("Could not save child UI element successfully!\nSee Urho3D.log for more detail.");
  306. return success;
  307. }
  308. void SetUIElementDefaultStyle(const String&in fileName)
  309. {
  310. if (fileName.empty)
  311. return;
  312. ui.cursor.shape = CS_BUSY;
  313. // Always load from the filesystem, not from resource paths
  314. if (!fileSystem.FileExists(fileName))
  315. {
  316. MessageBox("No such file.\n" + fileName);
  317. return;
  318. }
  319. File file(fileName, FILE_READ);
  320. if (!file.open)
  321. {
  322. MessageBox("Could not open file.\n" + fileName);
  323. return;
  324. }
  325. uiElementDefaultStyle = XMLFile();
  326. uiElementDefaultStyle.Load(file);
  327. // Remove the existing style list to ensure it gets repopulated again with the new default style file
  328. availableStyles.Clear();
  329. // Refresh Attribute Inspector when it is currently showing attributes of UI-element item type as the existing styles in the style drop down list are not valid anymore
  330. if (!editUIElements.empty)
  331. attributesFullDirty = true;
  332. }
  333. // Prepare XPath query object only once and use it multiple times
  334. XPathQuery filterInternalVarsQuery("//attribute[@name='Variables']/variant");
  335. void FilterInternalVars(XMLElement source)
  336. {
  337. XPathResultSet resultSet = filterInternalVarsQuery.Evaluate(source);
  338. XMLElement resultElem = resultSet.firstResult;
  339. while (resultElem.notNull)
  340. {
  341. String name = GetVarName(resultElem.GetUInt("hash"));
  342. if (name.empty)
  343. {
  344. XMLElement parent = resultElem.parent;
  345. // If variable name is empty (or unregistered) then it is an internal variable and should be removed
  346. if (parent.RemoveChild(resultElem))
  347. {
  348. // If parent does not have any children anymore then remove the parent also
  349. if (!parent.HasChild("variant"))
  350. parent.parent.RemoveChild(parent);
  351. }
  352. }
  353. else
  354. // If it is registered then it is a user-defined variable, so 'enrich' the XMLElement to store the variable name in plaintext
  355. resultElem.SetAttribute("name", name);
  356. resultElem = resultElem.nextResult;
  357. }
  358. }
  359. XPathQuery registerUIElemenVarsQuery("//attribute[@name='Variables']/variant/@name");
  360. void RegisterUIElementVar(XMLElement source)
  361. {
  362. XPathResultSet resultSet = registerUIElemenVarsQuery.Evaluate(source);
  363. XMLElement resultAttr = resultSet.firstResult; // Since we are selecting attribute, the resultset is in attribute context
  364. while (resultAttr.notNull)
  365. {
  366. String name = resultAttr.GetAttribute();
  367. globalVarNames[name] = name;
  368. resultAttr = resultAttr.nextResult;
  369. }
  370. }
  371. UIElement@ GetTopLevelUIElement(UIElement@ element)
  372. {
  373. // Only top level UI-element contains the FILENAME_VAR
  374. while (element !is null && !element.vars.Contains(FILENAME_VAR))
  375. element = element.parent;
  376. return element;
  377. }
  378. void SetUIElementModified(UIElement@ element, bool flag = true)
  379. {
  380. element = GetTopLevelUIElement(element);
  381. if (element !is null && element.GetVar(MODIFIED_VAR).GetBool() != flag)
  382. {
  383. element.vars[MODIFIED_VAR] = flag;
  384. UpdateHierarchyItemText(GetListIndex(element), element.visible, GetUIElementTitle(element));
  385. }
  386. }
  387. XPathQuery availableStylesXPathQuery("/elements/element[@auto='false']/@type");
  388. void GetAvailableStyles()
  389. {
  390. // Use the predefined UI style if set, otherwise use editor's own UI style
  391. XMLFile@ defaultStyle = uiElementDefaultStyle !is null ? uiElementDefaultStyle : uiStyle;
  392. XMLElement rootElem = defaultStyle.root;
  393. XPathResultSet resultSet = availableStylesXPathQuery.Evaluate(rootElem);
  394. XMLElement resultElem = resultSet.firstResult;
  395. while (resultElem.notNull)
  396. {
  397. availableStyles.Push(resultElem.GetAttribute());
  398. resultElem = resultElem.nextResult;
  399. }
  400. availableStyles.Sort();
  401. }
  402. void PopulateStyleList(DropDownList@ styleList)
  403. {
  404. if (availableStyles.empty)
  405. GetAvailableStyles();
  406. for (uint i = 0; i < availableStyles.length; ++i)
  407. {
  408. Text@ choice = Text();
  409. styleList.AddItem(choice);
  410. choice.style = "EditorEnumAttributeText";
  411. choice.text = availableStyles[i];
  412. }
  413. }
  414. bool UIElementCut()
  415. {
  416. return UIElementCopy() && UIElementDelete();
  417. }
  418. bool UIElementCopy()
  419. {
  420. ui.cursor.shape = CS_BUSY;
  421. uiElementCopyBuffer.Clear();
  422. for (uint i = 0; i < selectedUIElements.length; ++i)
  423. {
  424. XMLFile@ xml = XMLFile();
  425. XMLElement rootElem = xml.CreateRoot("element");
  426. selectedUIElements[i].SaveXML(rootElem);
  427. uiElementCopyBuffer.Push(xml);
  428. }
  429. return true;
  430. }
  431. void ResetDuplicateID(UIElement@ element)
  432. {
  433. // If it is a duplicate copy then the element ID need to be regenerated by resetting it now to empty
  434. if (GetListIndex(element) != NO_ITEM)
  435. element.vars[UI_ELEMENT_ID_VAR] = Variant();
  436. // Perform the action recursively for child elements
  437. for (uint i = 0; i < element.numChildren; ++i)
  438. ResetDuplicateID(element.children[i]);
  439. }
  440. bool UIElementPaste(bool duplication = false)
  441. {
  442. ui.cursor.shape = CS_BUSY;
  443. // Group for storing undo actions
  444. EditActionGroup group;
  445. // Have to update manually because the element ID var is not set yet when the E_ELEMENTADDED event is sent
  446. suppressUIElementChanges = true;
  447. for (uint i = 0; i < uiElementCopyBuffer.length; ++i)
  448. {
  449. XMLElement rootElem = uiElementCopyBuffer[i].root;
  450. UIElement@ pasteElement;
  451. if (!duplication)
  452. pasteElement = editUIElement;
  453. else
  454. {
  455. if (editUIElement.parent !is null)
  456. pasteElement = editUIElement.parent;
  457. else
  458. pasteElement = editUIElement;
  459. }
  460. if (pasteElement.LoadChildXML(rootElem, null) !is null)
  461. {
  462. UIElement@ element = pasteElement.children[pasteElement.numChildren - 1];
  463. ResetDuplicateID(element);
  464. UpdateHierarchyItem(element);
  465. SetUIElementModified(pasteElement);
  466. // Create an undo action
  467. CreateUIElementAction action;
  468. action.Define(element);
  469. group.actions.Push(action);
  470. }
  471. }
  472. SaveEditActionGroup(group);
  473. suppressUIElementChanges = false;
  474. return true;
  475. }
  476. bool UIElementDuplicate()
  477. {
  478. ui.cursor.shape = CS_BUSY;
  479. Array<XMLFile@> copy = uiElementCopyBuffer;
  480. UIElementCopy();
  481. UIElementPaste(true);
  482. uiElementCopyBuffer = copy;
  483. return true;
  484. }
  485. bool UIElementDelete()
  486. {
  487. ui.cursor.shape = CS_BUSY;
  488. BeginSelectionModify();
  489. // Clear the selection now to prevent deleted elements from being reselected
  490. hierarchyList.ClearSelection();
  491. // Group for storing undo actions
  492. EditActionGroup group;
  493. for (uint i = 0; i < selectedUIElements.length; ++i)
  494. {
  495. UIElement@ element = selectedUIElements[i];
  496. if (element.parent is null)
  497. continue; // Already deleted
  498. uint index = GetListIndex(element);
  499. // Create undo action
  500. DeleteUIElementAction action;
  501. action.Define(element);
  502. group.actions.Push(action);
  503. SetUIElementModified(element);
  504. element.Remove();
  505. // If deleting only one element, select the next item in the same index
  506. if (selectedUIElements.length == 1)
  507. hierarchyList.selection = index;
  508. }
  509. SaveEditActionGroup(group);
  510. EndSelectionModify();
  511. return true;
  512. }
  513. bool UIElementSelectAll()
  514. {
  515. BeginSelectionModify();
  516. Array<uint> indices;
  517. uint baseIndex = GetListIndex(editorUIElement);
  518. indices.Push(baseIndex);
  519. int baseIndent = hierarchyList.items[baseIndex].indent;
  520. for (uint i = baseIndex + 1; i < hierarchyList.numItems; ++i)
  521. {
  522. if (hierarchyList.items[i].indent <= baseIndent)
  523. break;
  524. indices.Push(i);
  525. }
  526. hierarchyList.SetSelections(indices);
  527. EndSelectionModify();
  528. return true;
  529. }
  530. bool UIElementResetToDefault()
  531. {
  532. ui.cursor.shape = CS_BUSY;
  533. // Group for storing undo actions
  534. EditActionGroup group;
  535. // Reset selected elements to their default values
  536. for (uint i = 0; i < selectedUIElements.length; ++i)
  537. {
  538. UIElement@ element = selectedUIElements[i];
  539. ResetAttributesAction action;
  540. action.Define(element);
  541. group.actions.Push(action);
  542. element.ResetToDefault();
  543. action.SetInternalVars(element);
  544. element.ApplyAttributes();
  545. for (uint j = 0; j < element.numAttributes; ++j)
  546. PostEditAttribute(element, j);
  547. SetUIElementModified(element);
  548. }
  549. SaveEditActionGroup(group);
  550. attributesFullDirty = true;
  551. return true;
  552. }
  553. bool UIElementChangeParent(UIElement@ sourceElement, UIElement@ targetElement)
  554. {
  555. ReparentUIElementAction action;
  556. action.Define(sourceElement, targetElement);
  557. SaveEditAction(action);
  558. sourceElement.parent = targetElement;
  559. SetUIElementModified(targetElement);
  560. return sourceElement.parent is targetElement;
  561. }
  562. bool UIElementReorder(UIElement@ sourceElement, UIElement@ targetElement)
  563. {
  564. if (sourceElement is null || targetElement is null || sourceElement.parent is null || sourceElement.parent !is targetElement.parent)
  565. return false;
  566. if (sourceElement is targetElement)
  567. return true; // No-op
  568. UIElement@ parent = sourceElement.parent;
  569. uint destIndex = parent.FindChild(targetElement);
  570. Print("Reorder to dest index " + destIndex);
  571. ReorderUIElementAction action;
  572. action.Define(sourceElement, destIndex);
  573. SaveEditAction(action);
  574. PerformReorder(parent, sourceElement, destIndex);
  575. return true;
  576. }
  577. void PerformReorder(UIElement@ parent, UIElement@ child, uint destIndex)
  578. {
  579. suppressSceneChanges = true;
  580. parent.RemoveChild(child);
  581. parent.InsertChild(destIndex, child);
  582. UpdateHierarchyItem(parent); // Force update to make sure the order is current
  583. SetUIElementModified(parent);
  584. suppressSceneChanges = false;
  585. }