EditorSceneWindow.as 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965
  1. // Urho3D editor scene hierarchy window handling
  2. const int ITEM_NONE = 0;
  3. const int ITEM_NODE = 1;
  4. const int ITEM_COMPONENT = 2;
  5. const uint NO_ITEM = 0xffffffff;
  6. Window@ sceneWindow;
  7. Array<XMLFile@> copyBuffer;
  8. bool copyBufferLocal = false;
  9. bool copyBufferExpanded = false;
  10. void CreateSceneWindow()
  11. {
  12. if (sceneWindow !is null)
  13. return;
  14. @sceneWindow = ui.LoadLayout(cache.GetResource("XMLFile", "UI/EditorSceneWindow.xml"), uiStyle);
  15. ui.root.AddChild(sceneWindow);
  16. int height = Min(ui.root.height - 60, 500);
  17. sceneWindow.SetSize(300, height);
  18. sceneWindow.SetPosition(20, 40);
  19. sceneWindow.BringToFront();
  20. UpdateSceneWindow();
  21. DropDownList@ newNodeList = sceneWindow.GetChild("NewNodeList", true);
  22. Array<String> newNodeChoices = {"Replicated", "Local"};
  23. for (uint i = 0; i < newNodeChoices.length; ++i)
  24. {
  25. Text@ choice = Text();
  26. choice.SetStyle(uiStyle, "FileSelectorFilterText");
  27. choice.text = newNodeChoices[i];
  28. newNodeList.AddItem(choice);
  29. }
  30. DropDownList@ newComponentList = sceneWindow.GetChild("NewComponentList", true);
  31. Array<String> componentTypes = GetAvailableComponents();
  32. for (uint i = 0; i < componentTypes.length; ++i)
  33. {
  34. Text@ choice = Text();
  35. choice.SetStyle(uiStyle, "FileSelectorFilterText");
  36. choice.text = componentTypes[i];
  37. newComponentList.AddItem(choice);
  38. }
  39. // Set drag & drop target mode on the node list background, which is used to parent
  40. // nodes back to the root node
  41. ListView@ list = sceneWindow.GetChild("NodeList");
  42. list.contentElement.dragDropMode = DD_TARGET;
  43. list.scrollPanel.dragDropMode = DD_TARGET;
  44. SubscribeToEvent(sceneWindow.GetChild("CloseButton", true), "Released", "HideSceneWindow");
  45. SubscribeToEvent(sceneWindow.GetChild("ExpandAllButton", true), "Released", "ExpandSceneHierarchy");
  46. SubscribeToEvent(sceneWindow.GetChild("CollapseAllButton", true), "Released", "CollapseSceneHierarchy");
  47. SubscribeToEvent(sceneWindow.GetChild("NodeList", true), "ItemSelected", "HandleNodeListSelectionChange");
  48. SubscribeToEvent(sceneWindow.GetChild("NodeList", true), "ItemDeselected", "HandleNodeListSelectionChange");
  49. SubscribeToEvent(sceneWindow.GetChild("NodeList", true), "ItemDoubleClicked", "HandleNodeListItemDoubleClick");
  50. SubscribeToEvent(sceneWindow.GetChild("NodeList", true), "UnhandledKey", "HandleNodeListKey");
  51. SubscribeToEvent(newNodeList, "ItemSelected", "HandleCreateNode");
  52. SubscribeToEvent(newComponentList, "ItemSelected", "HandleCreateComponent");
  53. SubscribeToEvent("DragDropTest", "HandleDragDropTest");
  54. SubscribeToEvent("DragDropFinish", "HandleDragDropFinish");
  55. SubscribeToEvent("BoneHierarchyCreated", "HandleBoneHierarchyCreated");
  56. }
  57. void ShowSceneWindow()
  58. {
  59. sceneWindow.visible = true;
  60. sceneWindow.BringToFront();
  61. }
  62. void HideSceneWindow()
  63. {
  64. sceneWindow.visible = false;
  65. }
  66. void ExpandSceneHierarchy()
  67. {
  68. ListView@ list = sceneWindow.GetChild("NodeList", true);
  69. list.SetChildItemsVisible(true);
  70. }
  71. void CollapseSceneHierarchy()
  72. {
  73. ListView@ list = sceneWindow.GetChild("NodeList", true);
  74. list.contentElement.DisableLayoutUpdate();
  75. // Show root-level nodes, but no components
  76. for (uint i = 0; i < list.numItems; ++i)
  77. {
  78. UIElement@ item = list.items[i];
  79. int indent = item.vars["Indent"].GetInt();
  80. int type = item.vars["Type"].GetInt();
  81. if (type == ITEM_COMPONENT || indent > 1)
  82. item.visible = false;
  83. else
  84. item.visible = true;
  85. }
  86. list.contentElement.EnableLayoutUpdate();
  87. list.contentElement.UpdateLayout();
  88. }
  89. void ClearSceneWindow()
  90. {
  91. if (sceneWindow is null)
  92. return;
  93. ListView@ list = sceneWindow.GetChild("NodeList", true);
  94. list.RemoveAllItems();
  95. }
  96. void UpdateSceneWindow()
  97. {
  98. ClearSceneWindow();
  99. UpdateSceneWindowNode(0, editorScene);
  100. // Clear copybuffer when whole window refreshed
  101. copyBuffer.Clear();
  102. }
  103. uint UpdateSceneWindowNode(uint itemIndex, Node@ node)
  104. {
  105. ListView@ list = sceneWindow.GetChild("NodeList", true);
  106. // Whenever we're updating the root, disable layout update to optimize speed
  107. if (node is editorScene)
  108. list.contentElement.DisableLayoutUpdate();
  109. // Remove old item if exists
  110. if (itemIndex < list.numItems && (node is null || (list.items[itemIndex].vars["Type"].GetInt() == ITEM_NODE &&
  111. list.items[itemIndex].vars["NodeID"].GetUInt() == node.id)))
  112. list.RemoveItem(itemIndex);
  113. if (node is null)
  114. return itemIndex;
  115. int indent = GetNodeIndent(node);
  116. Text@ text = Text();
  117. text.SetStyle(uiStyle, "FileSelectorListText");
  118. text.vars["Type"] = ITEM_NODE;
  119. text.vars["NodeID"] = node.id;
  120. text.vars["Indent"] = indent;
  121. text.text = GetNodeTitle(node, indent);
  122. // Nodes can be moved by drag and drop. The root node (scene) can not.
  123. if (node.typeName == "Node")
  124. text.dragDropMode = DD_SOURCE_AND_TARGET;
  125. else
  126. text.dragDropMode = DD_TARGET;
  127. list.InsertItem(itemIndex, text);
  128. ++itemIndex;
  129. // Update components first
  130. for (uint j = 0; j < node.numComponents; ++j)
  131. {
  132. Component@ component = node.components[j];
  133. AddComponentToSceneWindow(component, indent + 1, itemIndex);
  134. ++itemIndex;
  135. }
  136. // Then update child nodes recursively
  137. for (uint i = 0; i < node.numChildren; ++i)
  138. {
  139. Node@ childNode = node.children[i];
  140. itemIndex = UpdateSceneWindowNode(itemIndex, childNode);
  141. }
  142. // Re-enable layout update (and do manual layout) now
  143. if (node is editorScene)
  144. {
  145. list.contentElement.EnableLayoutUpdate();
  146. list.contentElement.UpdateLayout();
  147. }
  148. return itemIndex;
  149. }
  150. void UpdateSceneWindowNodeOnly(uint itemIndex, Node@ node)
  151. {
  152. ListView@ list = sceneWindow.GetChild("NodeList", true);
  153. int indent = GetNodeIndent(node);
  154. Text@ text = list.items[itemIndex];
  155. if (text is null)
  156. return;
  157. text.text = GetNodeTitle(node, indent);
  158. }
  159. void UpdateSceneWindowNode(Node@ node)
  160. {
  161. uint index = GetNodeListIndex(node);
  162. UpdateSceneWindowNode(index, node);
  163. }
  164. void UpdateSceneWindowNodeOnly(Node@ node)
  165. {
  166. uint index = GetNodeListIndex(node);
  167. UpdateSceneWindowNodeOnly(index, node);
  168. }
  169. void AddComponentToSceneWindow(Component@ component, int indent, uint compItemIndex)
  170. {
  171. ListView@ list = sceneWindow.GetChild("NodeList", true);
  172. Text@ text = Text();
  173. text.SetStyle(uiStyle, "FileSelectorListText");
  174. text.vars["Type"] = ITEM_COMPONENT;
  175. text.vars["NodeID"] = component.node.id;
  176. text.vars["ComponentID"] = component.id;
  177. text.vars["Indent"] = indent;
  178. text.text = GetComponentTitle(component, indent);
  179. list.InsertItem(compItemIndex, text);
  180. }
  181. uint GetNodeListIndex(Node@ node)
  182. {
  183. if (node is null)
  184. return NO_ITEM;
  185. ListView@ list = sceneWindow.GetChild("NodeList", true);
  186. uint numItems = list.numItems;
  187. uint nodeID = node.id;
  188. for (uint i = 0; i < numItems; ++i)
  189. {
  190. UIElement@ item = list.items[i];
  191. if (item.vars["Type"].GetInt() == ITEM_NODE && item.vars["NodeID"].GetUInt() == nodeID)
  192. return i;
  193. }
  194. return NO_ITEM;
  195. }
  196. uint GetParentAddIndex(Node@ node)
  197. {
  198. if (node is null || node.parent is null)
  199. return NO_ITEM;
  200. ListView@ list = sceneWindow.GetChild("NodeList", true);
  201. uint numItems = list.numItems;
  202. uint parentID = node.parent.id;
  203. for (uint i = 0; i < numItems; ++i)
  204. {
  205. UIElement@ item = list.items[i];
  206. if (item.vars["Type"].GetInt() == ITEM_NODE && item.vars["NodeID"].GetUInt() == parentID)
  207. {
  208. int indent = item.vars["Indent"].GetInt();
  209. for (uint j = i + 1; j < numItems; ++j)
  210. {
  211. // Scan for the next node on this or lower level; that is the place to insert the new child node
  212. if (list.items[j].vars["Indent"].GetInt() <= indent)
  213. return j;
  214. }
  215. return numItems;
  216. }
  217. }
  218. return NO_ITEM;
  219. }
  220. uint GetNodeListIndex(Node@ node, uint startPos)
  221. {
  222. if (node is null)
  223. return NO_ITEM;
  224. ListView@ list = sceneWindow.GetChild("NodeList", true);
  225. uint numItems = list.numItems;
  226. uint nodeID = node.id;
  227. for (uint i = startPos; i < numItems; --i)
  228. {
  229. UIElement@ item = list.items[i];
  230. if (item.vars["Type"].GetInt() == ITEM_NODE && item.vars["NodeID"].GetInt() == int(nodeID))
  231. return i;
  232. }
  233. return NO_ITEM;
  234. }
  235. Node@ GetListNode(uint index)
  236. {
  237. ListView@ list = sceneWindow.GetChild("NodeList", true);
  238. UIElement@ item = list.items[index];
  239. if (item is null)
  240. return null;
  241. return editorScene.GetNode(item.vars["NodeID"].GetUInt());
  242. }
  243. Component@ GetListComponent(uint index)
  244. {
  245. ListView@ list = sceneWindow.GetChild("NodeList", true);
  246. UIElement@ item = list.items[index];
  247. return GetListComponent(item);
  248. }
  249. Component@ GetListComponent(UIElement@ item)
  250. {
  251. if (item is null)
  252. return null;
  253. if (item.vars["Type"].GetInt() != ITEM_COMPONENT)
  254. return null;
  255. return editorScene.GetComponent(item.vars["ComponentID"].GetUInt());
  256. }
  257. uint GetComponentListIndex(Component@ component)
  258. {
  259. if (component is null)
  260. return NO_ITEM;
  261. ListView@ list = sceneWindow.GetChild("NodeList", true);
  262. uint numItems = list.numItems;
  263. for (uint i = 0; i < numItems; ++i)
  264. {
  265. UIElement@ item = list.items[i];
  266. if (item.vars["Type"].GetInt() == ITEM_COMPONENT && item.vars["ComponentID"].GetUInt() == component.id)
  267. return i;
  268. }
  269. return NO_ITEM;
  270. }
  271. int GetNodeIndent(Node@ node)
  272. {
  273. int indent = 0;
  274. for (;;)
  275. {
  276. if (node.parent is null)
  277. break;
  278. ++indent;
  279. node = node.parent;
  280. }
  281. return indent;
  282. }
  283. String GetNodeTitle(Node@ node, int indent)
  284. {
  285. String indentStr;
  286. indentStr.Resize(indent);
  287. for (int i = 0; i < indent; ++i)
  288. indentStr[i] = ' ';
  289. String idStr;
  290. if (node.id >= FIRST_LOCAL_ID)
  291. idStr = "Local " + String(node.id - FIRST_LOCAL_ID);
  292. else
  293. idStr = String(node.id);
  294. if (node.name.empty)
  295. return indentStr + node.typeName + " (" + idStr + ")";
  296. else
  297. return indentStr + node.name + " (" + idStr + ")";
  298. }
  299. String GetComponentTitle(Component@ component, int indent)
  300. {
  301. String indentStr;
  302. String localStr;
  303. indentStr.Resize(indent);
  304. for (int i = 0; i < indent; ++i)
  305. indentStr[i] = ' ';
  306. if (component.id >= FIRST_LOCAL_ID)
  307. localStr = " (Local)";
  308. return indentStr + component.typeName + localStr;
  309. }
  310. void SelectNode(Node@ node, bool multiselect)
  311. {
  312. ListView@ list = sceneWindow.GetChild("NodeList", true);
  313. if (node is null && !multiselect)
  314. {
  315. list.ClearSelection();
  316. return;
  317. }
  318. uint nodeItem = GetNodeListIndex(node);
  319. // Go in the parent chain up to the first non-root level to make sure the chain is expanded
  320. for (;;)
  321. {
  322. Node@ parent = node.parent;
  323. if (node is editorScene || parent is editorScene || parent is null)
  324. break;
  325. node = parent;
  326. }
  327. uint numItems = list.numItems;
  328. uint parentItem = GetNodeListIndex(node);
  329. if (nodeItem < numItems)
  330. {
  331. // Expand the node chain now, but do not expand the whole scene in case the component was in the root
  332. if (!multiselect || !list.IsSelected(nodeItem))
  333. {
  334. list.items[nodeItem].visible = true;
  335. if (parentItem != 0 && parentItem < numItems)
  336. list.SetChildItemsVisible(parentItem, true);
  337. }
  338. // This causes an event to be sent, in response we set the node/component selections, and refresh editors
  339. if (!multiselect)
  340. list.selection = nodeItem;
  341. else
  342. list.ToggleSelection(nodeItem);
  343. }
  344. else
  345. {
  346. if (!multiselect)
  347. list.ClearSelection();
  348. }
  349. }
  350. void SelectComponent(Component@ component, bool multiselect)
  351. {
  352. ListView@ list = sceneWindow.GetChild("NodeList", true);
  353. if (component is null)
  354. {
  355. if (!multiselect)
  356. list.ClearSelection();
  357. return;
  358. }
  359. Node@ node = component.node;
  360. if (node is null)
  361. {
  362. if (!multiselect)
  363. list.ClearSelection();
  364. return;
  365. }
  366. // Go in the parent chain up to the first non-root level to make sure the chain is expanded
  367. for (;;)
  368. {
  369. Node@ parent = node.parent;
  370. if (node is editorScene || parent is editorScene || parent is null)
  371. break;
  372. node = parent;
  373. }
  374. uint numItems = list.numItems;
  375. uint nodeItem = GetNodeListIndex(node);
  376. uint componentItem = GetComponentListIndex(component);
  377. if (nodeItem >= list.numItems)
  378. {
  379. if (!multiselect)
  380. list.ClearSelection();
  381. return;
  382. }
  383. if (nodeItem < numItems && componentItem < numItems)
  384. {
  385. // Expand the node chain now, but do not expand the whole scene in case the component was in the root
  386. if (!multiselect || !list.IsSelected(componentItem))
  387. {
  388. list.items[nodeItem].visible = true;
  389. if (nodeItem != 0)
  390. list.SetChildItemsVisible(nodeItem, true);
  391. list.items[componentItem].visible = true;
  392. }
  393. // This causes an event to be sent, in response we set the node/component selections, and refresh editors
  394. if (!multiselect)
  395. list.selection = componentItem;
  396. else
  397. list.ToggleSelection(componentItem);
  398. }
  399. else
  400. {
  401. if (!multiselect)
  402. list.ClearSelection();
  403. }
  404. }
  405. void HandleNodeListSelectionChange()
  406. {
  407. ClearSelection();
  408. ListView@ list = sceneWindow.GetChild("NodeList", true);
  409. Array<uint> indices = list.selections;
  410. for (uint i = 0; i < indices.length; ++i)
  411. {
  412. uint index = indices[i];
  413. UIElement@ item = list.items[index];
  414. int type = item.vars["Type"].GetInt();
  415. if (type == ITEM_COMPONENT)
  416. {
  417. Component@ comp = GetListComponent(index);
  418. if (comp !is null)
  419. selectedComponents.Push(comp);
  420. }
  421. else if (type == ITEM_NODE)
  422. {
  423. Node@ node = GetListNode(index);
  424. if (node !is null)
  425. selectedNodes.Push(node);
  426. }
  427. }
  428. // If only one node selected, use it for editing
  429. if (selectedNodes.length == 1)
  430. editNode = selectedNodes[0];
  431. // If selection contains only components, and they have a common node, use it for editing
  432. if (selectedNodes.empty && !selectedComponents.empty)
  433. {
  434. Node@ commonNode;
  435. for (uint i = 0; i < selectedComponents.length; ++i)
  436. {
  437. if (i == 0)
  438. commonNode = selectedComponents[i].node;
  439. else
  440. {
  441. if (selectedComponents[i].node !is commonNode)
  442. commonNode = null;
  443. }
  444. }
  445. editNode = commonNode;
  446. }
  447. // Now check if the component(s) can be edited. If many selected, must have same type
  448. if (!selectedComponents.empty)
  449. {
  450. ShortStringHash compType = selectedComponents[0].type;
  451. bool sameType = true;
  452. for (uint i = 1; i < selectedComponents.length; ++i)
  453. {
  454. if (selectedComponents[i].type != compType)
  455. {
  456. sameType = false;
  457. break;
  458. }
  459. }
  460. if (sameType)
  461. editComponents = selectedComponents;
  462. }
  463. // If just nodes selected, and no components, show the first component(s) for editing if possible
  464. if (!selectedNodes.empty && selectedComponents.empty && selectedNodes[0].numComponents > 0)
  465. {
  466. ShortStringHash compType = selectedNodes[0].components[0].type;
  467. bool sameType = true;
  468. for (uint i = 1; i < selectedNodes.length; ++i)
  469. {
  470. if (selectedNodes[i].numComponents == 0 || selectedNodes[i].components[0].type != compType)
  471. {
  472. sameType = false;
  473. break;
  474. }
  475. }
  476. if (sameType)
  477. {
  478. for (uint i = 0; i < selectedNodes.length; ++i)
  479. editComponents.Push(selectedNodes[i].components[0]);
  480. }
  481. }
  482. UpdateNodeWindow();
  483. }
  484. void HandleNodeListItemDoubleClick(StringHash eventType, VariantMap& eventData)
  485. {
  486. ListView@ list = sceneWindow.GetChild("NodeList", true);
  487. uint index = eventData["Selection"].GetUInt();
  488. if (index == 0)
  489. {
  490. if (list.numItems > 1 && list.items[1].visible == true)
  491. CollapseSceneHierarchy();
  492. else
  493. list.ToggleChildItemsVisible(index);
  494. }
  495. else
  496. list.ToggleChildItemsVisible(index);
  497. }
  498. void HandleNodeListKey(StringHash eventType, VariantMap& eventData)
  499. {
  500. int key = eventData["Key"].GetInt();
  501. }
  502. void HandleDragDropTest(StringHash eventType, VariantMap& eventData)
  503. {
  504. UIElement@ source = eventData["Source"].GetUIElement();
  505. UIElement@ target = eventData["Target"].GetUIElement();
  506. eventData["Accept"] = TestSceneWindowElements(source, target);
  507. }
  508. void HandleDragDropFinish(StringHash eventType, VariantMap& eventData)
  509. {
  510. UIElement@ source = eventData["Source"].GetUIElement();
  511. UIElement@ target = eventData["Target"].GetUIElement();
  512. bool accept = TestSceneWindowElements(source, target);
  513. eventData["Accept"] = accept;
  514. if (!accept)
  515. return;
  516. Node@ sourceNode = editorScene.GetNode(source.vars["NodeID"].GetUInt());
  517. Node@ targetNode = editorScene.GetNode(target.vars["NodeID"].GetUInt());
  518. // If target is null, parent to scene
  519. if (targetNode is null)
  520. targetNode = editorScene;
  521. // Perform the reparenting
  522. // Set transform so that the world transform stays through the parent change
  523. BeginModify(targetNode.id);
  524. BeginModify(sourceNode.id);
  525. Vector3 newPos;
  526. Quaternion newRot;
  527. Vector3 newScale;
  528. CalculateNewTransform(sourceNode, targetNode, newPos, newRot, newScale);
  529. sourceNode.parent = targetNode;
  530. // Verify success
  531. if (sourceNode.parent !is targetNode)
  532. {
  533. EndModify(sourceNode.id);
  534. EndModify(targetNode.id);
  535. return;
  536. }
  537. sourceNode.SetTransform(newPos, newRot, newScale);
  538. EndModify(sourceNode.id);
  539. EndModify(targetNode.id);
  540. // Update the node list now. If a node was moved into the root, this potentially refreshes the whole scene window.
  541. // Therefore disable layout update first
  542. ListView@ list = sceneWindow.GetChild("NodeList", true);
  543. uint sourceIndex = GetNodeListIndex(sourceNode);
  544. bool expanded = SaveExpandedStatus(sourceIndex);
  545. list.RemoveItem(sourceIndex);
  546. uint addIndex = GetParentAddIndex(sourceNode);
  547. UpdateSceneWindowNode(addIndex, sourceNode);
  548. RestoreExpandedStatus(addIndex, expanded);
  549. }
  550. bool TestSceneWindowElements(UIElement@ source, UIElement@ target)
  551. {
  552. // Test for validity of reparenting by drag and drop
  553. Node@ sourceNode;
  554. Node@ targetNode;
  555. if (source.vars.Contains("NodeID"))
  556. sourceNode = editorScene.GetNode(source.vars["NodeID"].GetUInt());
  557. if (target.vars.Contains("NodeID"))
  558. editorScene.GetNode(target.vars["NodeID"].GetUInt());
  559. if (sourceNode is null)
  560. return false;
  561. if (sourceNode is editorScene)
  562. return false;
  563. if (targetNode !is null)
  564. {
  565. if (sourceNode.parent is targetNode)
  566. return false;
  567. if (targetNode.parent is sourceNode)
  568. return false;
  569. }
  570. return true;
  571. }
  572. void CalculateNewTransform(Node@ source, Node@ target, Vector3& pos, Quaternion& rot, Vector3& scale)
  573. {
  574. Vector3 sourceWorldPos = source.worldPosition;
  575. Quaternion sourceWorldRot = source.worldRotation;
  576. Vector3 sourceWorldScale = source.worldScale;
  577. Quaternion inverseTargetWorldRot = target.worldRotation.Inverse();
  578. Vector3 inverseTargetWorldScale = Vector3(1, 1, 1) / target.worldScale;
  579. scale = inverseTargetWorldScale * sourceWorldScale;
  580. rot = inverseTargetWorldRot * sourceWorldRot;
  581. pos = inverseTargetWorldScale * (inverseTargetWorldRot * (sourceWorldPos - target.worldPosition));
  582. }
  583. void UpdateAndFocusNode(Node@ node)
  584. {
  585. UpdateSceneWindowNode(node);
  586. uint index = GetNodeListIndex(node);
  587. ListView@ list = sceneWindow.GetChild("NodeList", true);
  588. list.selection = index;
  589. }
  590. void UpdateAndFocusComponent(Component@ component)
  591. {
  592. UpdateSceneWindowNode(component.node);
  593. uint index = GetComponentListIndex(component);
  594. ListView@ list = sceneWindow.GetChild("NodeList", true);
  595. list.selection = index;
  596. }
  597. void HandleCreateNode(StringHash eventType, VariantMap& eventData)
  598. {
  599. DropDownList@ list = eventData["Element"].GetUIElement();
  600. uint mode = list.selection;
  601. if (mode >= list.numItems)
  602. return;
  603. Node@ newNode = editorScene.CreateChild("", mode == 0 ? REPLICATED : LOCAL);
  604. // Set the new node a certain distance from the camera
  605. newNode.position = GetNewNodePosition();
  606. UpdateAndFocusNode(newNode);
  607. }
  608. void HandleCreateComponent(StringHash eventType, VariantMap& eventData)
  609. {
  610. if (editNode is null)
  611. return;
  612. DropDownList@ list = eventData["Element"].GetUIElement();
  613. Text@ text = list.selectedItem;
  614. if (text is null)
  615. return;
  616. // If this is the root node, do not allow to create duplicate scene-global components
  617. if (editNode is editorScene && CheckForExistingGlobalComponent(editNode, text.text))
  618. return;
  619. // For now, make a local node's all components local
  620. /// \todo Allow to specify the createmode
  621. Component@ newComponent = editNode.CreateComponent(text.text, editNode.id < FIRST_LOCAL_ID ? REPLICATED : LOCAL);
  622. UpdateAndFocusComponent(newComponent);
  623. }
  624. void HandleBoneHierarchyCreated(StringHash eventType, VariantMap& eventData)
  625. {
  626. ListView@ list = sceneWindow.GetChild("NodeList", true);
  627. if (list.numItems > 0)
  628. UpdateSceneWindowNode(eventData["Node"].GetNode());
  629. }
  630. bool CheckSceneWindowFocus()
  631. {
  632. // When we do scene operations based on key shortcuts, make sure either the 3D scene or the node list is focused,
  633. // not for example a file selector
  634. ListView@ list = sceneWindow.GetChild("NodeList", true);
  635. if (ui.focusElement is list || ui.focusElement is null)
  636. return true;
  637. else
  638. return false;
  639. }
  640. bool CheckForExistingGlobalComponent(Node@ node, const String&in typeName)
  641. {
  642. if (typeName != "Octree" && typeName != "PhysicsWorld" && typeName != "DebugRenderer")
  643. return false;
  644. else
  645. return node.HasComponent(typeName);
  646. }
  647. bool SceneDelete()
  648. {
  649. if (!CheckSceneWindowFocus() || (selectedComponents.empty && selectedNodes.empty))
  650. return false;
  651. ListView@ list = sceneWindow.GetChild("NodeList", true);
  652. // Remove components first
  653. for (uint i = 0; i < selectedComponents.length; ++i)
  654. {
  655. // Do not allow to remove the Octree, PhysicsWorld or DebugRenderer from the root node
  656. Component@ component = selectedComponents[i];
  657. Node@ node = component.node;
  658. uint index = GetComponentListIndex(component);
  659. uint nodeIndex = GetNodeListIndex(node);
  660. if (index == NO_ITEM || nodeIndex == NO_ITEM)
  661. continue;
  662. if (node is editorScene && (component.typeName == "Octree" || component.typeName == "PhysicsWorld" ||
  663. component.typeName == "DebugRenderer"))
  664. continue;
  665. uint id = node.id;
  666. BeginModify(id);
  667. node.RemoveComponent(component);
  668. EndModify(id);
  669. UpdateSceneWindowNode(nodeIndex, node);
  670. // If deleting only one component, select the next item in the same index
  671. if (selectedComponents.length == 1 && selectedNodes.empty)
  672. {
  673. list.selection = index;
  674. return true;
  675. }
  676. }
  677. // Remove (parented) nodes last
  678. for (uint i = 0; i < selectedNodes.length; ++i)
  679. {
  680. Node@ node = selectedNodes[i];
  681. if (node.parent is null)
  682. continue;
  683. uint id = node.id;
  684. uint nodeIndex = GetNodeListIndex(node);
  685. BeginModify(id);
  686. node.Remove();
  687. EndModify(id);
  688. UpdateSceneWindowNode(nodeIndex, null);
  689. // Select the next item in the same index
  690. // If deleting only one node, select the next item in the same index
  691. if (selectedNodes.length == 1 && selectedComponents.empty)
  692. {
  693. list.selection = nodeIndex;
  694. return true;
  695. }
  696. }
  697. // If any kind of multi-delete was performed, the list selection should be clear now.
  698. // Unfortunately that also means we did not get selection change events, so must update the selection arrays manually.
  699. // Otherwise nodes/components may be left in the scene even after delete, as the selection arrays keep them alive.
  700. HandleNodeListSelectionChange();
  701. return true;
  702. }
  703. bool SceneCut()
  704. {
  705. if (SceneCopy())
  706. return SceneDelete();
  707. else
  708. return false;
  709. }
  710. bool SceneCopy()
  711. {
  712. if ((selectedNodes.empty && selectedComponents.empty) || !CheckSceneWindowFocus())
  713. return false;
  714. // Must have either only components, or only nodes
  715. if (!selectedNodes.empty && !selectedComponents.empty)
  716. return false;
  717. ListView@ list = sceneWindow.GetChild("NodeList", true);
  718. copyBuffer.Clear();
  719. // Copy components
  720. if (!selectedComponents.empty)
  721. {
  722. for (uint i = 0; i < selectedComponents.length; ++i)
  723. {
  724. XMLFile@ xml = XMLFile();
  725. XMLElement rootElem = xml.CreateRoot("component");
  726. selectedComponents[i].SaveXML(rootElem);
  727. // Note: component type has to be saved manually
  728. rootElem.SetString("type", selectedComponents[i].typeName);
  729. rootElem.SetBool("local", selectedComponents[i].id >= FIRST_LOCAL_ID);
  730. copyBuffer.Push(xml);
  731. }
  732. return true;
  733. }
  734. // Copy node. The root node can not be copied
  735. else
  736. {
  737. for (uint i = 0; i < selectedNodes.length; ++i)
  738. {
  739. if (selectedNodes[i] is editorScene)
  740. return false;
  741. }
  742. for (uint i = 0; i < selectedNodes.length; ++i)
  743. {
  744. XMLFile@ xml = XMLFile();
  745. XMLElement rootElem = xml.CreateRoot("node");
  746. selectedNodes[i].SaveXML(rootElem);
  747. rootElem.SetBool("local", selectedNodes[i].id >= FIRST_LOCAL_ID);
  748. copyBuffer.Push(xml);
  749. }
  750. copyBufferExpanded = SaveExpandedStatus(GetNodeListIndex(selectedNodes[0]));
  751. return true;
  752. }
  753. }
  754. bool ScenePaste()
  755. {
  756. if (editNode is null || !CheckSceneWindowFocus() || copyBuffer.empty)
  757. return false;
  758. ListView@ list = sceneWindow.GetChild("NodeList", true);
  759. bool pasteComponents = false;
  760. for (uint i = 0; i < copyBuffer.length; ++i)
  761. {
  762. XMLElement rootElem = copyBuffer[i].root;
  763. String mode = rootElem.name;
  764. if (mode == "component")
  765. {
  766. pasteComponents = true;
  767. // If this is the root node, do not allow to create duplicate scene-global components
  768. if (editNode is editorScene && CheckForExistingGlobalComponent(editNode, rootElem.GetAttribute("type")))
  769. return false;
  770. BeginModify(editNode.id);
  771. // If copied component was local, make the new local too
  772. Component@ newComponent = editNode.CreateComponent(rootElem.GetAttribute("type"), rootElem.GetBool("local") ? LOCAL :
  773. REPLICATED);
  774. if (newComponent is null)
  775. {
  776. EndModify(editNode.id);
  777. return false;
  778. }
  779. newComponent.LoadXML(rootElem);
  780. newComponent.ApplyAttributes();
  781. EndModify(editNode.id);
  782. }
  783. else if (mode == "node")
  784. {
  785. // Make the paste go always to the root node, no matter of the selected node
  786. BeginModify(editorScene.id);
  787. // If copied node was local, make the new local too
  788. Node@ newNode = editorScene.CreateChild("", rootElem.GetBool("local") ? LOCAL : REPLICATED);
  789. BeginModify(newNode.id);
  790. newNode.LoadXML(rootElem);
  791. newNode.ApplyAttributes();
  792. EndModify(newNode.id);
  793. EndModify(editorScene.id);
  794. uint addIndex = GetParentAddIndex(newNode);
  795. UpdateSceneWindowNode(addIndex, newNode);
  796. RestoreExpandedStatus(addIndex, copyBufferExpanded);
  797. }
  798. }
  799. if (pasteComponents)
  800. UpdateSceneWindowNode(editNode);
  801. return true;
  802. }
  803. bool SaveExpandedStatus(uint itemIndex)
  804. {
  805. ListView@ list = sceneWindow.GetChild("NodeList", true);
  806. uint nextIndex = itemIndex + 1;
  807. if (nextIndex < list.numItems && list.items[nextIndex].vars["Indent"].GetInt() > list.items[itemIndex].vars["Indent"].GetInt()
  808. && list.items[nextIndex].visible == false)
  809. return false;
  810. else
  811. return true;
  812. }
  813. void RestoreExpandedStatus(uint itemIndex, bool expanded)
  814. {
  815. ListView@ list = sceneWindow.GetChild("NodeList", true);
  816. list.SetChildItemsVisible(itemIndex, expanded);
  817. }