EditorUI.as 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385
  1. // Urho3D editor user interface
  2. XMLFile@ uiStyle;
  3. XMLFile@ iconStyle;
  4. UIElement@ uiMenuBar;
  5. UIElement@ quickMenu;
  6. Menu@ recentSceneMenu;
  7. Window@ mruScenesPopup;
  8. Array<QuickMenuItem@> quickMenuItems;
  9. FileSelector@ uiFileSelector;
  10. const ShortStringHash UI_ELEMENT_TYPE("UIElement");
  11. const ShortStringHash WINDOW_TYPE("Window");
  12. const ShortStringHash MENU_TYPE("Menu");
  13. const ShortStringHash TEXT_TYPE("Text");
  14. const ShortStringHash CURSOR_TYPE("Cursor");
  15. const String AUTO_STYLE(""); // Empty string means auto style, i.e. applying style according to UI-element's type automatically
  16. const String TEMP_SCENE_NAME("_tempscene_.xml");
  17. const ShortStringHash CALLBACK_VAR("Callback");
  18. const ShortStringHash INDENT_MODIFIED_BY_ICON_VAR("IconIndented");
  19. const int SHOW_POPUP_INDICATOR = -1;
  20. const uint MAX_QUICK_MENU_ITEMS = 10;
  21. const uint maxRecentSceneCount = 5;
  22. Array<String> uiSceneFilters = {"*.xml", "*.bin", "*.*"};
  23. Array<String> uiElementFilters = {"*.xml"};
  24. Array<String> uiAllFilters = {"*.*"};
  25. Array<String> uiScriptFilters = {"*.as", "*.*"};
  26. Array<String> uiParticleFilters = {"*.xml"};
  27. uint uiSceneFilter = 0;
  28. uint uiElementFilter = 0;
  29. uint uiNodeFilter = 0;
  30. uint uiImportFilter = 0;
  31. uint uiScriptFilter = 0;
  32. uint uiParticleFilter = 0;
  33. String uiScenePath = fileSystem.programDir + "Data/Scenes";
  34. String uiElementPath = fileSystem.programDir + "Data/UI";
  35. String uiNodePath = fileSystem.programDir + "Data/Objects";
  36. String uiImportPath;
  37. String uiScriptPath = fileSystem.programDir + "Data/Scripts";
  38. String uiParticlePath = fileSystem.programDir + "Data/Particles";
  39. Array<String> uiRecentScenes;
  40. bool uiFaded = false;
  41. float uiMinOpacity = 0.3;
  42. float uiMaxOpacity = 0.7;
  43. bool uiHidden = false;
  44. void CreateUI()
  45. {
  46. // Remove all existing UI content in case we are reloading the editor script
  47. /// \todo The console will not be properly recreated as it has already been created once
  48. ui.root.RemoveAllChildren();
  49. uiStyle = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");
  50. ui.root.defaultStyle = uiStyle;
  51. iconStyle = cache.GetResource("XMLFile", "UI/EditorIcons.xml");
  52. CreateCursor();
  53. CreateMenuBar();
  54. CreateToolBar();
  55. CreateSecondaryToolBar();
  56. CreateQuickMenu();
  57. CreateHierarchyWindow();
  58. CreateAttributeInspectorWindow();
  59. CreateEditorSettingsDialog();
  60. CreateEditorPreferencesDialog();
  61. CreateMaterialEditor();
  62. CreateStatsBar();
  63. CreateConsole();
  64. CreateDebugHud();
  65. CreateCamera();
  66. SubscribeToEvent("ScreenMode", "ResizeUI");
  67. SubscribeToEvent("MenuSelected", "HandleMenuSelected");
  68. SubscribeToEvent("KeyDown", "HandleKeyDown");
  69. SubscribeToEvent("KeyUp", "UnfadeUI");
  70. SubscribeToEvent("MouseButtonUp", "UnfadeUI");
  71. }
  72. void ResizeUI()
  73. {
  74. // Resize menu bar
  75. uiMenuBar.SetFixedWidth(graphics.width);
  76. // Resize tool bar
  77. toolBar.SetFixedWidth(graphics.width);
  78. // Resize secondary tool bar
  79. secondaryToolBar.SetFixedHeight(graphics.height);
  80. // Relayout stats bar
  81. Font@ font = cache.GetResource("Font", "Fonts/Anonymous Pro.ttf");
  82. if (graphics.width >= 1200)
  83. {
  84. SetupStatsBarText(editorModeText, font, 35, 64, HA_LEFT, VA_TOP);
  85. SetupStatsBarText(renderStatsText, font, -4, 64, HA_RIGHT, VA_TOP);
  86. }
  87. else
  88. {
  89. SetupStatsBarText(editorModeText, font, 35, 64, HA_LEFT, VA_TOP);
  90. SetupStatsBarText(renderStatsText, font, 35, 78, HA_LEFT, VA_TOP);
  91. }
  92. // Relayout windows
  93. Array<UIElement@> children = ui.root.GetChildren();
  94. for (uint i = 0; i < children.length; ++i)
  95. {
  96. if (children[i].type == WINDOW_TYPE)
  97. AdjustPosition(children[i]);
  98. }
  99. // Relayout root UI element
  100. editorUIElement.SetSize(graphics.width, graphics.height);
  101. }
  102. void AdjustPosition(Window@ window)
  103. {
  104. IntVector2 position = window.position;
  105. IntVector2 size = window.size;
  106. IntVector2 extend = position + size;
  107. if (extend.x > graphics.width)
  108. position.x = Max(10, graphics.width - size.x - 10);
  109. if (extend.y > graphics.height)
  110. position.y = Max(100, graphics.height - size.y - 10);
  111. window.position = position;
  112. }
  113. void CreateCursor()
  114. {
  115. Cursor@ cursor = Cursor("Cursor");
  116. cursor.SetStyleAuto(uiStyle);
  117. cursor.SetPosition(graphics.width / 2, graphics.height / 2);
  118. ui.cursor = cursor;
  119. if (GetPlatform() == "Android" || GetPlatform() == "iOS")
  120. ui.cursor.visible = false;
  121. }
  122. // AngelScript does not support closures (yet), but funcdef should do just fine as a workaround for a few cases here for now
  123. funcdef bool MENU_CALLBACK();
  124. Array<MENU_CALLBACK@> menuCallbacks;
  125. MENU_CALLBACK@ messageBoxCallback;
  126. void HandleQuickSearchChange(StringHash eventType, VariantMap& eventData)
  127. {
  128. LineEdit@ search = eventData["Element"].GetUIElement();
  129. if (search is null)
  130. return;
  131. PerformQuickMenuSearch(search.text.ToLower().Trimmed());
  132. }
  133. void PerformQuickMenuSearch(const String&in query)
  134. {
  135. Menu@ menu = quickMenu.GetChild("ResultsMenu", true);
  136. if (menu is null)
  137. return;
  138. menu.RemoveAllChildren();
  139. uint limit = 0;
  140. if (query.length > 0)
  141. {
  142. int lastIndex = 0;
  143. uint score = 0;
  144. int index = 0;
  145. Array<QuickMenuItem@> filtered;
  146. {
  147. QuickMenuItem@ qi;
  148. for(uint x=0; x < quickMenuItems.length; x++)
  149. {
  150. @qi = quickMenuItems[x];
  151. int find = qi.action.Find(query, 0, false);
  152. if (find > -1)
  153. {
  154. qi.sortScore = find;
  155. filtered.Push(qi);
  156. }
  157. }
  158. }
  159. {
  160. QuickMenuItem@ a;
  161. QuickMenuItem@ b;
  162. for(uint x=0; x < filtered.length; x++)
  163. {
  164. for(uint y=0; y < filtered.length-1; y++)
  165. {
  166. @a = filtered[y];
  167. @b = filtered[y+1];
  168. if(a.sortScore > b.sortScore)
  169. {
  170. @filtered[y+1] = a;
  171. @filtered[y] = b;
  172. }
  173. }
  174. }
  175. }
  176. {
  177. QuickMenuItem@ qi;
  178. limit = filtered.length > MAX_QUICK_MENU_ITEMS ? MAX_QUICK_MENU_ITEMS : filtered.length;
  179. for(uint x=0; x < limit; x++)
  180. {
  181. @qi = filtered[x];
  182. Menu@ item = CreateMenuItem(qi.action, qi.callback);
  183. item.SetMaxSize(1000,16);
  184. menu.AddChild(item);
  185. }
  186. }
  187. }
  188. menu.visible = limit > 0;
  189. menu.SetFixedHeight(limit * 16);
  190. quickMenu.BringToFront();
  191. quickMenu.SetFixedHeight(limit*16 + 62 + (menu.visible ? 6 : 0));
  192. }
  193. class QuickMenuItem
  194. {
  195. String action;
  196. MENU_CALLBACK@ callback;
  197. uint sortScore = 0;
  198. QuickMenuItem(){}
  199. QuickMenuItem(String action, MENU_CALLBACK@ callback)
  200. {
  201. this.action = action;
  202. this.callback = callback;
  203. }
  204. }
  205. /// Create popup search menu.
  206. void CreateQuickMenu()
  207. {
  208. if (quickMenu !is null)
  209. return;
  210. quickMenu = ui.LoadLayout(cache.GetResource("XMLFile", "UI/EditorQuickMenu.xml"));
  211. quickMenu.enabled = false;
  212. quickMenu.visible = false;
  213. // Handle a dummy search in the quick menu to finalize its initial size to empty
  214. PerformQuickMenuSearch("");
  215. ui.root.AddChild(quickMenu);
  216. LineEdit@ search = quickMenu.GetChild("Search", true);
  217. SubscribeToEvent(search, "TextChanged", "HandleQuickSearchChange");
  218. UIElement@ closeButton = quickMenu.GetChild("CloseButton", true);
  219. SubscribeToEvent(closeButton, "Pressed", "ToggleQuickMenu");
  220. }
  221. void ToggleQuickMenu()
  222. {
  223. quickMenu.enabled = !quickMenu.enabled && ui.cursor.visible;
  224. quickMenu.visible = quickMenu.enabled;
  225. if (quickMenu.enabled)
  226. {
  227. quickMenu.position = ui.cursorPosition - IntVector2(20,70);
  228. LineEdit@ search = quickMenu.GetChild("Search", true);
  229. search.text = "";
  230. search.focus = true;
  231. }
  232. }
  233. /// Create top menu bar.
  234. void CreateMenuBar()
  235. {
  236. uiMenuBar = BorderImage("MenuBar");
  237. ui.root.AddChild(uiMenuBar);
  238. uiMenuBar.enabled = true;
  239. uiMenuBar.style = "EditorMenuBar";
  240. uiMenuBar.SetLayout(LM_HORIZONTAL);
  241. uiMenuBar.opacity = uiMaxOpacity;
  242. uiMenuBar.SetFixedWidth(graphics.width);
  243. {
  244. Menu@ menu = CreateMenu("File");
  245. Window@ popup = menu.popup;
  246. popup.AddChild(CreateMenuItem("New scene", @ResetScene, 'N', QUAL_SHIFT | QUAL_CTRL));
  247. popup.AddChild(CreateMenuItem("Open scene...", @PickFile, 'O', QUAL_CTRL));
  248. popup.AddChild(CreateMenuItem("Save scene", @SaveSceneWithExistingName, 'S', QUAL_CTRL));
  249. popup.AddChild(CreateMenuItem("Save scene as...", @PickFile, 'S', QUAL_SHIFT | QUAL_CTRL));
  250. recentSceneMenu = CreateMenuItem("Open recent scene", null, SHOW_POPUP_INDICATOR);
  251. popup.AddChild(recentSceneMenu);
  252. mruScenesPopup = CreatePopup(recentSceneMenu);
  253. PopulateMruScenes();
  254. CreateChildDivider(popup);
  255. Menu@ childMenu = CreateMenuItem("Load node", null, SHOW_POPUP_INDICATOR);
  256. Window@ childPopup = CreatePopup(childMenu);
  257. childPopup.AddChild(CreateMenuItem("As replicated...", @PickFile, 0, 0, true, "Load node as replicated..."));
  258. childPopup.AddChild(CreateMenuItem("As local...", @PickFile, 0, 0, true, "Load node as local..."));
  259. popup.AddChild(childMenu);
  260. popup.AddChild(CreateMenuItem("Save node as...", @PickFile));
  261. CreateChildDivider(popup);
  262. popup.AddChild(CreateMenuItem("Import model...", @PickFile));
  263. popup.AddChild(CreateMenuItem("Import scene...", @PickFile));
  264. CreateChildDivider(popup);
  265. popup.AddChild(CreateMenuItem("Run script...", @PickFile));
  266. popup.AddChild(CreateMenuItem("Set resource path...", @PickFile));
  267. CreateChildDivider(popup);
  268. popup.AddChild(CreateMenuItem("Exit", @Exit));
  269. FinalizedPopupMenu(popup);
  270. uiMenuBar.AddChild(menu);
  271. }
  272. {
  273. Menu@ menu = CreateMenu("Edit");
  274. Window@ popup = menu.popup;
  275. popup.AddChild(CreateMenuItem("Undo", @Undo, 'Z', QUAL_CTRL));
  276. popup.AddChild(CreateMenuItem("Redo", @Redo, 'Y', QUAL_CTRL));
  277. CreateChildDivider(popup);
  278. popup.AddChild(CreateMenuItem("Cut", @Cut, 'X', QUAL_CTRL));
  279. popup.AddChild(CreateMenuItem("Copy", @Copy, 'C', QUAL_CTRL));
  280. popup.AddChild(CreateMenuItem("Paste", @Paste, 'V', QUAL_CTRL));
  281. popup.AddChild(CreateMenuItem("Delete", @Delete, KEY_DELETE, QUAL_ANY));
  282. popup.AddChild(CreateMenuItem("Select all", @SelectAll, 'A', QUAL_CTRL));
  283. CreateChildDivider(popup);
  284. popup.AddChild(CreateMenuItem("Reset to default", @ResetToDefault));
  285. CreateChildDivider(popup);
  286. popup.AddChild(CreateMenuItem("Reset position", @SceneResetPosition));
  287. popup.AddChild(CreateMenuItem("Reset rotation", @SceneResetRotation));
  288. popup.AddChild(CreateMenuItem("Reset scale", @SceneResetScale));
  289. popup.AddChild(CreateMenuItem("Enable/disable", @SceneToggleEnable, 'E', QUAL_CTRL));
  290. popup.AddChild(CreateMenuItem("Unparent", @SceneUnparent, 'U', QUAL_CTRL));
  291. CreateChildDivider(popup);
  292. popup.AddChild(CreateMenuItem("Toggle update", @ToggleSceneUpdate, 'P', QUAL_CTRL));
  293. popup.AddChild(CreateMenuItem("Stop test animation", @StopTestAnimation));
  294. CreateChildDivider(popup);
  295. popup.AddChild(CreateMenuItem("Rebuild navigation data", @SceneRebuildNavigation));
  296. popup.AddChild(CreateMenuItem("Load particle data", @PickFile));
  297. popup.AddChild(CreateMenuItem("Save particle data", @PickFile));
  298. FinalizedPopupMenu(popup);
  299. uiMenuBar.AddChild(menu);
  300. }
  301. {
  302. Menu@ menu = CreateMenu("Create");
  303. Window@ popup = menu.popup;
  304. popup.AddChild(CreateMenuItem("Replicated node", @PickNode, 0, 0, true, "Create Replicated node"));
  305. popup.AddChild(CreateMenuItem("Local node", @PickNode, 0, 0, true, "Create Local node"));
  306. CreateChildDivider(popup);
  307. Menu@ childMenu = CreateMenuItem("Component", null, SHOW_POPUP_INDICATOR);
  308. Window@ childPopup = CreatePopup(childMenu);
  309. String[] objectCategories = GetObjectCategories();
  310. for (uint i = 0; i < objectCategories.length; ++i)
  311. {
  312. // Skip the UI category for the component menus
  313. if (objectCategories[i] == "UI")
  314. continue;
  315. Menu@ menu = CreateMenuItem(objectCategories[i], null, SHOW_POPUP_INDICATOR);
  316. Window@ popup = CreatePopup(menu);
  317. String[] componentTypes = GetObjectsByCategory(objectCategories[i]);
  318. for (uint j = 0; j < componentTypes.length; ++j)
  319. popup.AddChild(CreateIconizedMenuItem(componentTypes[j], @PickComponent, 0, 0, "", true, "Create " + componentTypes[j]));
  320. childPopup.AddChild(menu);
  321. }
  322. FinalizedPopupMenu(childPopup);
  323. popup.AddChild(childMenu);
  324. childMenu = CreateMenuItem("Builtin object", null, SHOW_POPUP_INDICATOR);
  325. childPopup = CreatePopup(childMenu);
  326. String[] objects = { "Box", "Cone", "Cylinder", "Plane", "Pyramid", "Sphere" };
  327. for (uint i = 0; i < objects.length; ++i)
  328. childPopup.AddChild(CreateIconizedMenuItem(objects[i], @PickBuiltinObject, 0, 0, "Node", true, "Create " + objects[i]));
  329. popup.AddChild(childMenu);
  330. CreateChildDivider(popup);
  331. childMenu = CreateMenuItem("UI-element", null, SHOW_POPUP_INDICATOR);
  332. childPopup = CreatePopup(childMenu);
  333. String[] uiElementTypes = GetObjectsByCategory("UI");
  334. for (uint i = 0; i < uiElementTypes.length; ++i)
  335. {
  336. if (uiElementTypes[i] != "UIElement")
  337. childPopup.AddChild(CreateIconizedMenuItem(uiElementTypes[i], @PickUIElement, 0, 0, "", true, "Create " + uiElementTypes[i]));
  338. }
  339. CreateChildDivider(childPopup);
  340. childPopup.AddChild(CreateIconizedMenuItem("UIElement", @PickUIElement));
  341. popup.AddChild(childMenu);
  342. FinalizedPopupMenu(popup);
  343. uiMenuBar.AddChild(menu);
  344. }
  345. {
  346. Menu@ menu = CreateMenu("UI-layout");
  347. Window@ popup = menu.popup;
  348. popup.AddChild(CreateMenuItem("Open UI-layout...", @PickFile, 'O', QUAL_ALT));
  349. popup.AddChild(CreateMenuItem("Save UI-layout", @SaveUILayoutWithExistingName, 'S', QUAL_ALT));
  350. popup.AddChild(CreateMenuItem("Save UI-layout as...", @PickFile));
  351. CreateChildDivider(popup);
  352. popup.AddChild(CreateMenuItem("Close UI-layout", @CloseUILayout, 'C', QUAL_ALT));
  353. popup.AddChild(CreateMenuItem("Close all UI-layouts", @CloseAllUILayouts));
  354. CreateChildDivider(popup);
  355. popup.AddChild(CreateMenuItem("Load child element...", @PickFile));
  356. popup.AddChild(CreateMenuItem("Save child element as...", @PickFile));
  357. CreateChildDivider(popup);
  358. popup.AddChild(CreateMenuItem("Set default style...", @PickFile));
  359. FinalizedPopupMenu(popup);
  360. uiMenuBar.AddChild(menu);
  361. }
  362. {
  363. Menu@ menu = CreateMenu("View");
  364. Window@ popup = menu.popup;
  365. popup.AddChild(CreateMenuItem("Hierarchy", @ShowHierarchyWindow, 'H', QUAL_CTRL));
  366. popup.AddChild(CreateMenuItem("Attribute inspector", @ShowAttributeInspectorWindow, 'I', QUAL_CTRL));
  367. popup.AddChild(CreateMenuItem("Material editor", @ShowMaterialEditor));
  368. popup.AddChild(CreateMenuItem("Editor settings", @ShowEditorSettingsDialog));
  369. popup.AddChild(CreateMenuItem("Editor preferences", @ShowEditorPreferencesDialog));
  370. CreateChildDivider(popup);
  371. popup.AddChild(CreateMenuItem("Hide editor", @ToggleUI, KEY_F12, QUAL_ANY));
  372. FinalizedPopupMenu(popup);
  373. uiMenuBar.AddChild(menu);
  374. }
  375. BorderImage@ spacer = BorderImage("MenuBarSpacer");
  376. uiMenuBar.AddChild(spacer);
  377. spacer.style = "EditorMenuBar";
  378. BorderImage@ logo = BorderImage("Logo");
  379. logo.texture = cache.GetResource("Texture2D", "Textures/Logo.png");
  380. logo.SetFixedWidth(64);
  381. uiMenuBar.AddChild(logo);
  382. }
  383. bool Exit()
  384. {
  385. ui.cursor.shape = CS_BUSY;
  386. if (messageBoxCallback is null)
  387. {
  388. String message;
  389. if (sceneModified)
  390. message = "Scene has been modified.\n";
  391. bool uiLayoutModified = false;
  392. for (uint i = 0; i < editorUIElement.numChildren; ++i)
  393. {
  394. UIElement@ element = editorUIElement.children[i];
  395. if (element !is null && element.vars[MODIFIED_VAR].GetBool())
  396. {
  397. uiLayoutModified = true;
  398. message += "UI layout has been modified.\n";
  399. break;
  400. }
  401. }
  402. if (sceneModified || uiLayoutModified)
  403. {
  404. MessageBox@ messageBox = MessageBox(message + "Continue to exit?", "Warning");
  405. Button@ cancelButton = messageBox.window.GetChild("CancelButton", true);
  406. cancelButton.visible = true;
  407. cancelButton.focus = true;
  408. SubscribeToEvent(messageBox, "MessageACK", "HandleMessageAcknowledgement");
  409. messageBoxCallback = @Exit;
  410. return false;
  411. }
  412. }
  413. else
  414. messageBoxCallback = null;
  415. engine.Exit();
  416. return true;
  417. }
  418. void HandleExitRequested()
  419. {
  420. if (!ui.HasModalElement())
  421. Exit();
  422. }
  423. bool PickFile()
  424. {
  425. Menu@ menu = GetEventSender();
  426. if (menu is null)
  427. return false;
  428. String action = menu.name;
  429. if (action.empty)
  430. return false;
  431. // File (Scene related)
  432. if (action == "Open scene...")
  433. {
  434. CreateFileSelector("Open scene", "Open", "Cancel", uiScenePath, uiSceneFilters, uiSceneFilter);
  435. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleOpenSceneFile");
  436. }
  437. else if (action == "Save scene as..." || action == "Save scene")
  438. {
  439. CreateFileSelector("Save scene as", "Save", "Cancel", uiScenePath, uiSceneFilters, uiSceneFilter);
  440. uiFileSelector.fileName = GetFileNameAndExtension(editorScene.fileName);
  441. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleSaveSceneFile");
  442. }
  443. else if (action == "As replicated..." || action == "Load node as replicated...")
  444. {
  445. instantiateMode = REPLICATED;
  446. CreateFileSelector("Load node", "Load", "Cancel", uiNodePath, uiSceneFilters, uiNodeFilter);
  447. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleLoadNodeFile");
  448. }
  449. else if (action == "As local..." || action == "Load node as local...")
  450. {
  451. instantiateMode = LOCAL;
  452. CreateFileSelector("Load node", "Load", "Cancel", uiNodePath, uiSceneFilters, uiNodeFilter);
  453. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleLoadNodeFile");
  454. }
  455. else if (action == "Save node as...")
  456. {
  457. if (editNode !is null && editNode !is editorScene)
  458. {
  459. CreateFileSelector("Save node", "Save", "Cancel", uiNodePath, uiSceneFilters, uiNodeFilter);
  460. uiFileSelector.fileName = GetFileNameAndExtension(instantiateFileName);
  461. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleSaveNodeFile");
  462. }
  463. }
  464. else if (action == "Import model...")
  465. {
  466. CreateFileSelector("Import model", "Import", "Cancel", uiImportPath, uiAllFilters, uiImportFilter);
  467. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleImportModel");
  468. }
  469. else if (action == "Import scene...")
  470. {
  471. CreateFileSelector("Import scene", "Import", "Cancel", uiImportPath, uiAllFilters, uiImportFilter);
  472. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleImportScene");
  473. }
  474. else if (action == "Run script...")
  475. {
  476. CreateFileSelector("Run script", "Run", "Cancel", uiScriptPath, uiScriptFilters, uiScriptFilter);
  477. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleRunScript");
  478. }
  479. else if (action == "Set resource path...")
  480. {
  481. CreateFileSelector("Set resource path", "Set", "Cancel", sceneResourcePath, uiAllFilters, 0);
  482. uiFileSelector.directoryMode = true;
  483. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleResourcePath");
  484. }
  485. else if (action == "Load particle data")
  486. {
  487. bool hasParticleEmitter = false;
  488. for (uint i = 0; i < editComponents.length; ++i)
  489. {
  490. if (editComponents[i].typeName == "ParticleEmitter")
  491. {
  492. hasParticleEmitter = true;
  493. break;
  494. }
  495. }
  496. if (hasParticleEmitter)
  497. {
  498. CreateFileSelector("Load particle data", "Load", "Cancel", uiParticlePath, uiParticleFilters, uiParticleFilter);
  499. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleLoadParticleData");
  500. }
  501. }
  502. else if (action == "Save particle data")
  503. {
  504. if (editComponents.length == 1 && editComponents[0].typeName == "ParticleEmitter")
  505. {
  506. CreateFileSelector("Save particle data", "Save", "Cancel", uiParticlePath, uiParticleFilters, uiParticleFilter);
  507. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleSaveParticleData");
  508. }
  509. }
  510. // UI-element
  511. else if (action == "Open UI-layout...")
  512. {
  513. CreateFileSelector("Open UI-layout", "Open", "Cancel", uiElementPath, uiElementFilters, uiElementFilter);
  514. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleOpenUILayoutFile");
  515. }
  516. else if (action == "Save UI-layout as..." || action == "Save UI-layout")
  517. {
  518. if (editUIElement !is null)
  519. {
  520. UIElement@ element = GetTopLevelUIElement(editUIElement);
  521. if (element is null)
  522. return false;
  523. CreateFileSelector("Save UI-layout as", "Save", "Cancel", uiElementPath, uiElementFilters, uiElementFilter);
  524. uiFileSelector.fileName = GetFileNameAndExtension(element.GetVar(FILENAME_VAR).GetString());
  525. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleSaveUILayoutFile");
  526. }
  527. }
  528. else if (action == "Load child element...")
  529. {
  530. if (editUIElement !is null)
  531. {
  532. CreateFileSelector("Load child element", "Load", "Cancel", uiElementPath, uiElementFilters, uiElementFilter);
  533. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleLoadChildUIElementFile");
  534. }
  535. }
  536. else if (action == "Save child element as...")
  537. {
  538. if (editUIElement !is null)
  539. {
  540. CreateFileSelector("Save child element", "Save", "Cancel", uiElementPath, uiElementFilters, uiElementFilter);
  541. uiFileSelector.fileName = GetFileNameAndExtension(editUIElement.GetVar(CHILD_ELEMENT_FILENAME_VAR).GetString());
  542. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleSaveChildUIElementFile");
  543. }
  544. }
  545. else if (action == "Set default style...")
  546. {
  547. CreateFileSelector("Set default style", "Set", "Cancel", uiElementPath, uiElementFilters, uiElementFilter);
  548. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleUIElementDefaultStyle");
  549. }
  550. return true;
  551. }
  552. bool PickNode()
  553. {
  554. Menu@ menu = GetEventSender();
  555. if (menu is null)
  556. return false;
  557. String action = GetActionName(menu.name);
  558. if (action.empty)
  559. return false;
  560. CreateNode(action == "Replicated node" ? REPLICATED : LOCAL);
  561. return true;
  562. }
  563. bool PickComponent()
  564. {
  565. if (editNodes.empty)
  566. return false;
  567. Menu@ menu = GetEventSender();
  568. if (menu is null)
  569. return false;
  570. String action = GetActionName(menu.name);
  571. if (action.empty)
  572. return false;
  573. CreateComponent(action);
  574. return true;
  575. }
  576. bool PickBuiltinObject()
  577. {
  578. Menu@ menu = GetEventSender();
  579. if (menu is null)
  580. return false;
  581. String action = GetActionName(menu.name);
  582. if (action.empty)
  583. return false;
  584. CreateBuiltinObject(action);
  585. return true;
  586. }
  587. bool PickUIElement()
  588. {
  589. Menu@ menu = GetEventSender();
  590. if (menu is null)
  591. return false;
  592. String action = GetActionName(menu.name);
  593. if (action.empty)
  594. return false;
  595. return NewUIElement(action);
  596. }
  597. // When calling items from the quick menu, they have "Create" prepended for clarity. Strip that now to get the object name to create
  598. String GetActionName(const String&in name)
  599. {
  600. if (name.StartsWith("Create"))
  601. return name.Substring(7);
  602. else
  603. return name;
  604. }
  605. void HandleMenuSelected(StringHash eventType, VariantMap& eventData)
  606. {
  607. Menu@ menu = eventData["Element"].GetUIElement();
  608. if (menu is null)
  609. return;
  610. HandlePopup(menu);
  611. quickMenu.visible = false;
  612. quickMenu.enabled = false;
  613. // Execute the callback if available
  614. Variant variant = menu.GetVar(CALLBACK_VAR);
  615. if (!variant.empty)
  616. menuCallbacks[variant.GetUInt()]();
  617. }
  618. Menu@ CreateMenuItem(const String&in title, MENU_CALLBACK@ callback = null, int accelKey = 0, int accelQual = 0, bool addToQuickMenu = true, String quickMenuText="")
  619. {
  620. Menu@ menu = Menu(title);
  621. menu.defaultStyle = uiStyle;
  622. menu.style = AUTO_STYLE;
  623. menu.SetLayout(LM_HORIZONTAL, 0, IntRect(8, 2, 8, 2));
  624. if (accelKey > 0)
  625. menu.SetAccelerator(accelKey, accelQual);
  626. if (callback !is null)
  627. {
  628. menu.vars[CALLBACK_VAR] = menuCallbacks.length;
  629. menuCallbacks.Push(callback);
  630. }
  631. Text@ menuText = Text();
  632. menu.AddChild(menuText);
  633. menuText.style = "EditorMenuText";
  634. menuText.text = title;
  635. if (addToQuickMenu)
  636. AddQuickMenuItem(callback, quickMenuText.empty ? title : quickMenuText);
  637. if (accelKey != 0)
  638. {
  639. UIElement@ spacer = UIElement();
  640. spacer.minWidth = menuText.indentSpacing;
  641. spacer.height = menuText.height;
  642. menu.AddChild(spacer);
  643. menu.AddChild(CreateAccelKeyText(accelKey, accelQual));
  644. }
  645. return menu;
  646. }
  647. void AddQuickMenuItem(MENU_CALLBACK@ callback, String text)
  648. {
  649. if (callback is null)
  650. return;
  651. bool exists = false;
  652. for (uint i=0;i<quickMenuItems.length;i++)
  653. {
  654. if (quickMenuItems[i].action == text)
  655. {
  656. exists = true;
  657. break;
  658. }
  659. }
  660. if (!exists)
  661. quickMenuItems.Push(QuickMenuItem(text, callback));
  662. }
  663. Menu@ CreateIconizedMenuItem(const String&in title, MENU_CALLBACK@ callback = null, int accelKey = 0, int accelQual = 0, const String&in iconType = "", bool addToQuickMenu=true, String quickMenuText="")
  664. {
  665. Menu@ menu = Menu(title);
  666. menu.defaultStyle = uiStyle;
  667. menu.style = AUTO_STYLE;
  668. menu.SetLayout(LM_VERTICAL, 0, IntRect(8, 2, 8, 2));
  669. if (accelKey > 0)
  670. menu.SetAccelerator(accelKey, accelQual);
  671. if (callback !is null)
  672. {
  673. menu.vars[CALLBACK_VAR] = menuCallbacks.length;
  674. menuCallbacks.Push(callback);
  675. }
  676. Text@ menuText = Text();
  677. menu.AddChild(menuText);
  678. menuText.style = "EditorMenuText";
  679. menuText.text = title;
  680. // If icon type is not provided, use the title instead
  681. IconizeUIElement(menuText, iconType.empty ? title : iconType);
  682. if (addToQuickMenu)
  683. AddQuickMenuItem(callback, quickMenuText.empty ? title : quickMenuText);
  684. if (accelKey != 0)
  685. {
  686. menuText.layoutMode = LM_HORIZONTAL;
  687. menuText.AddChild(CreateAccelKeyText(accelKey, accelQual));
  688. }
  689. return menu;
  690. }
  691. /// Create a child divider in parent with vertical layout mode. It works on other parent element as well, not just parent menu.
  692. void CreateChildDivider(UIElement@ parent)
  693. {
  694. BorderImage@ divider = parent.CreateChild("BorderImage", "Divider");
  695. divider.style = "EditorDivider";
  696. }
  697. Window@ CreatePopup(Menu@ baseMenu)
  698. {
  699. Window@ popup = Window();
  700. popup.defaultStyle = uiStyle;
  701. popup.style = AUTO_STYLE;
  702. popup.SetLayout(LM_VERTICAL, 1, IntRect(2, 6, 2, 6));
  703. baseMenu.popup = popup;
  704. baseMenu.popupOffset = IntVector2(0, baseMenu.height);
  705. return popup;
  706. }
  707. Menu@ CreateMenu(const String&in title)
  708. {
  709. Menu@ menu = CreateMenuItem(title);
  710. menu.SetFixedWidth(menu.width);
  711. CreatePopup(menu);
  712. return menu;
  713. }
  714. Text@ CreateAccelKeyText(int accelKey, int accelQual)
  715. {
  716. Text@ accelKeyText = Text();
  717. accelKeyText.defaultStyle = uiStyle;
  718. accelKeyText.style = "EditorMenuText";
  719. accelKeyText.textAlignment = HA_RIGHT;
  720. String text;
  721. if (accelKey == KEY_DELETE)
  722. text = "Del";
  723. else if (accelKey == KEY_SPACE)
  724. text = "Space";
  725. // Cannot use range as the key constants below do not appear to be in sequence
  726. else if (accelKey == KEY_F1)
  727. text = "F1";
  728. else if (accelKey == KEY_F2)
  729. text = "F2";
  730. else if (accelKey == KEY_F3)
  731. text = "F3";
  732. else if (accelKey == KEY_F4)
  733. text = "F4";
  734. else if (accelKey == KEY_F5)
  735. text = "F5";
  736. else if (accelKey == KEY_F6)
  737. text = "F6";
  738. else if (accelKey == KEY_F7)
  739. text = "F7";
  740. else if (accelKey == KEY_F8)
  741. text = "F8";
  742. else if (accelKey == KEY_F9)
  743. text = "F9";
  744. else if (accelKey == KEY_F10)
  745. text = "F10";
  746. else if (accelKey == KEY_F11)
  747. text = "F11";
  748. else if (accelKey == KEY_F12)
  749. text = "F12";
  750. else if (accelKey == SHOW_POPUP_INDICATOR)
  751. text = ">";
  752. else
  753. text.AppendUTF8(accelKey);
  754. if (accelQual & QUAL_ALT > 0)
  755. text = "Alt+" + text;
  756. if (accelQual & QUAL_SHIFT > 0)
  757. text = "Shift+" + text;
  758. if (accelQual & QUAL_CTRL > 0)
  759. text = "Ctrl+" + text;
  760. accelKeyText.text = text;
  761. return accelKeyText;
  762. }
  763. void FinalizedPopupMenu(Window@ popup)
  764. {
  765. // Find the maximum menu text width
  766. Array<UIElement@> children = popup.GetChildren();
  767. int maxWidth = 0;
  768. for (uint i = 0; i < children.length; ++i)
  769. {
  770. UIElement@ element = children[i];
  771. if (element.type != MENU_TYPE) // Skip if not menu item
  772. continue;
  773. int width = element.children[0].width;
  774. if (width > maxWidth)
  775. maxWidth = width;
  776. }
  777. // Adjust the indent spacing to slightly wider than the maximum width
  778. maxWidth += 20;
  779. for (uint i = 0; i < children.length; ++i)
  780. {
  781. UIElement@ element = children[i];
  782. if (element.type != MENU_TYPE)
  783. continue;
  784. Menu@ menu = element;
  785. Text@ menuText = menu.children[0];
  786. if (menuText.numChildren == 1) // Skip if menu text does not have accel
  787. menuText.children[0].indentSpacing = maxWidth;
  788. // Adjust the popup offset taking the indentation into effect
  789. if (menu.popup !is null)
  790. menu.popupOffset = IntVector2(menu.width, 0);
  791. }
  792. }
  793. void CreateFileSelector(const String&in title, const String&in ok, const String&in cancel, const String&in initialPath, Array<String>@ filters,
  794. uint initialFilter)
  795. {
  796. // Within the editor UI, the file selector is a kind of a "singleton". When the previous one is overwritten, also
  797. // the events subscribed from it are disconnected, so new ones are safe to subscribe.
  798. uiFileSelector = FileSelector();
  799. uiFileSelector.defaultStyle = uiStyle;
  800. uiFileSelector.title = title;
  801. uiFileSelector.path = initialPath;
  802. uiFileSelector.SetButtonTexts(ok, cancel);
  803. uiFileSelector.SetFilters(filters, initialFilter);
  804. CenterDialog(uiFileSelector.window);
  805. }
  806. void CloseFileSelector(uint&out filterIndex, String&out path)
  807. {
  808. // Save filter & path for next time
  809. filterIndex = uiFileSelector.filterIndex;
  810. path = uiFileSelector.path;
  811. uiFileSelector = null;
  812. }
  813. void CloseFileSelector()
  814. {
  815. uiFileSelector = null;
  816. }
  817. void CreateConsole()
  818. {
  819. Console@ console = engine.CreateConsole();
  820. console.defaultStyle = uiStyle;
  821. console.numRows = 16;
  822. }
  823. void CreateDebugHud()
  824. {
  825. engine.CreateDebugHud();
  826. debugHud.defaultStyle = uiStyle;
  827. debugHud.mode = DEBUGHUD_SHOW_NONE;
  828. }
  829. void CenterDialog(UIElement@ element)
  830. {
  831. IntVector2 size = element.size;
  832. element.SetPosition((graphics.width - size.x) / 2, (graphics.height - size.y) / 2);
  833. }
  834. void UpdateWindowTitle()
  835. {
  836. String sceneName = GetFileNameAndExtension(editorScene.fileName);
  837. if (sceneName.empty || sceneName == TEMP_SCENE_NAME)
  838. sceneName = "Untitled";
  839. if (sceneModified)
  840. sceneName += "*";
  841. graphics.windowTitle = "Urho3D editor - " + sceneName;
  842. }
  843. void HandlePopup(Menu@ menu)
  844. {
  845. // Close the top level menu now unless the selected menu item has another popup
  846. if (menu.popup !is null)
  847. return;
  848. for (;;)
  849. {
  850. UIElement@ menuParent = menu.parent;
  851. if (menuParent is null)
  852. break;
  853. Menu@ nextMenu = menuParent.vars["Origin"].GetUIElement();
  854. if (nextMenu is null)
  855. break;
  856. else
  857. menu = nextMenu;
  858. }
  859. if (menu.parent is uiMenuBar)
  860. menu.showPopup = false;
  861. }
  862. String ExtractFileName(VariantMap& eventData, bool forSave = false)
  863. {
  864. String fileName;
  865. // Check for OK
  866. if (eventData["OK"].GetBool())
  867. {
  868. String filter = eventData["Filter"].GetString();
  869. fileName = eventData["FileName"].GetString();
  870. // Add default extension for saving if not specified
  871. if (GetExtension(fileName).empty && forSave && filter != "*.*")
  872. fileName = fileName + filter.Substring(1);
  873. }
  874. return fileName;
  875. }
  876. void HandleOpenSceneFile(StringHash eventType, VariantMap& eventData)
  877. {
  878. CloseFileSelector(uiSceneFilter, uiScenePath);
  879. LoadScene(ExtractFileName(eventData));
  880. }
  881. void HandleSaveSceneFile(StringHash eventType, VariantMap& eventData)
  882. {
  883. CloseFileSelector(uiSceneFilter, uiScenePath);
  884. SaveScene(ExtractFileName(eventData, true));
  885. }
  886. void HandleLoadNodeFile(StringHash eventType, VariantMap& eventData)
  887. {
  888. CloseFileSelector(uiNodeFilter, uiNodePath);
  889. LoadNode(ExtractFileName(eventData));
  890. }
  891. void HandleSaveNodeFile(StringHash eventType, VariantMap& eventData)
  892. {
  893. CloseFileSelector(uiNodeFilter, uiNodePath);
  894. SaveNode(ExtractFileName(eventData, true));
  895. }
  896. void HandleImportModel(StringHash eventType, VariantMap& eventData)
  897. {
  898. CloseFileSelector(uiImportFilter, uiImportPath);
  899. ImportModel(ExtractFileName(eventData));
  900. }
  901. void HandleImportScene(StringHash eventType, VariantMap& eventData)
  902. {
  903. CloseFileSelector(uiImportFilter, uiImportPath);
  904. ImportScene(ExtractFileName(eventData));
  905. }
  906. void HandleLoadParticleData(StringHash eventType, VariantMap& eventData)
  907. {
  908. CloseFileSelector(uiParticleFilter, uiParticlePath);
  909. LoadParticleData(ExtractFileName(eventData));
  910. }
  911. void HandleSaveParticleData(StringHash eventType, VariantMap& eventData)
  912. {
  913. CloseFileSelector(uiParticleFilter, uiParticlePath);
  914. SaveParticleData(ExtractFileName(eventData, true));
  915. }
  916. void ExecuteScript(const String&in fileName)
  917. {
  918. if (fileName.empty)
  919. return;
  920. File@ file = File(fileName, FILE_READ);
  921. if (file.open)
  922. {
  923. String scriptCode;
  924. while (!file.eof)
  925. scriptCode += file.ReadLine() + "\n";
  926. file.Close();
  927. if (script.Execute(scriptCode))
  928. log.Info("Script " + fileName + " ran successfully");
  929. }
  930. }
  931. void HandleRunScript(StringHash eventType, VariantMap& eventData)
  932. {
  933. CloseFileSelector(uiScriptFilter, uiScriptPath);
  934. ExecuteScript(ExtractFileName(eventData));
  935. }
  936. void HandleResourcePath(StringHash eventType, VariantMap& eventData)
  937. {
  938. CloseFileSelector();
  939. SetResourcePath(ExtractFileName(eventData), false);
  940. }
  941. void HandleOpenUILayoutFile(StringHash eventType, VariantMap& eventData)
  942. {
  943. CloseFileSelector(uiElementFilter, uiElementPath);
  944. OpenUILayout(ExtractFileName(eventData));
  945. }
  946. void HandleSaveUILayoutFile(StringHash eventType, VariantMap& eventData)
  947. {
  948. CloseFileSelector(uiElementFilter, uiElementPath);
  949. SaveUILayout(ExtractFileName(eventData, true));
  950. }
  951. void HandleLoadChildUIElementFile(StringHash eventType, VariantMap& eventData)
  952. {
  953. CloseFileSelector(uiElementFilter, uiElementPath);
  954. LoadChildUIElement(ExtractFileName(eventData));
  955. }
  956. void HandleSaveChildUIElementFile(StringHash eventType, VariantMap& eventData)
  957. {
  958. CloseFileSelector(uiElementFilter, uiElementPath);
  959. SaveChildUIElement(ExtractFileName(eventData, true));
  960. }
  961. void HandleUIElementDefaultStyle(StringHash eventType, VariantMap& eventData)
  962. {
  963. CloseFileSelector(uiElementFilter, uiElementPath);
  964. SetUIElementDefaultStyle(ExtractFileName(eventData));
  965. }
  966. void HandleKeyDown(StringHash eventType, VariantMap& eventData)
  967. {
  968. int key = eventData["Key"].GetInt();
  969. int viewDirection = eventData["Qualifiers"].GetInt() == QUAL_CTRL ? -1 : 1;
  970. if (key == KEY_ESC)
  971. {
  972. if (uiHidden)
  973. UnhideUI();
  974. else if (console.visible)
  975. console.visible = false;
  976. else if (quickMenu.visible)
  977. {
  978. quickMenu.visible = false;
  979. quickMenu.enabled = false;
  980. }
  981. else
  982. {
  983. UIElement@ front = ui.frontElement;
  984. if (front is settingsDialog || front is preferencesDialog)
  985. {
  986. ui.focusElement = null;
  987. front.visible = false;
  988. }
  989. }
  990. }
  991. // Ignore other keys when UI has a modal element
  992. else if (ui.HasModalElement())
  993. return;
  994. else if (key == KEY_F1)
  995. console.Toggle();
  996. else if (key == KEY_F2)
  997. ToggleRenderingDebug();
  998. else if (key == KEY_F3)
  999. TogglePhysicsDebug();
  1000. else if (key == KEY_F4)
  1001. ToggleOctreeDebug();
  1002. else if (key == KEY_NUMPAD1 && ui.focusElement is null) // Front view
  1003. {
  1004. Vector3 pos = cameraNode.position;
  1005. pos.z = -pos.length * viewDirection;
  1006. pos.x = 0;
  1007. pos.y = 0;
  1008. cameraNode.position = pos;
  1009. cameraNode.direction = Vector3(0, 0, viewDirection);
  1010. ReacquireCameraYawPitch();
  1011. }
  1012. else if (key == KEY_NUMPAD3 && ui.focusElement is null) // Side view
  1013. {
  1014. Vector3 pos = cameraNode.position;
  1015. pos.x = pos.length * viewDirection;
  1016. pos.y = 0;
  1017. pos.z = 0;
  1018. cameraNode.position = pos;
  1019. cameraNode.direction = Vector3(-viewDirection, 0, 0);
  1020. ReacquireCameraYawPitch();
  1021. }
  1022. else if (key == KEY_NUMPAD7 && ui.focusElement is null) // Top view
  1023. {
  1024. Vector3 pos = cameraNode.position;
  1025. pos.y = pos.length * viewDirection;
  1026. pos.x = 0;
  1027. pos.z = 0;
  1028. cameraNode.position = pos;
  1029. cameraNode.direction = Vector3(0, -viewDirection, 0);
  1030. ReacquireCameraYawPitch();
  1031. }
  1032. else if (eventData["Qualifiers"].GetInt() == QUAL_CTRL)
  1033. {
  1034. if (key == '1')
  1035. editMode = EDIT_MOVE;
  1036. else if (key == '2')
  1037. editMode = EDIT_ROTATE;
  1038. else if (key == '3')
  1039. editMode = EDIT_SCALE;
  1040. else if (key == '4')
  1041. editMode = EDIT_SELECT;
  1042. else if (key == '5')
  1043. axisMode = AxisMode(axisMode ^ AXIS_LOCAL);
  1044. else if (key == '6')
  1045. {
  1046. --pickMode;
  1047. if (pickMode < PICK_GEOMETRIES)
  1048. pickMode = MAX_PICK_MODES - 1;
  1049. }
  1050. else if (key == '7')
  1051. {
  1052. ++pickMode;
  1053. if (pickMode >= MAX_PICK_MODES)
  1054. pickMode = PICK_GEOMETRIES;
  1055. }
  1056. else if (key == 'W')
  1057. {
  1058. fillMode = FillMode(fillMode + 1);
  1059. if (fillMode > FILL_POINT)
  1060. fillMode = FILL_SOLID;
  1061. // Update camera fill mode
  1062. SetFillMode(fillMode);
  1063. }
  1064. else if (key == KEY_SPACE)
  1065. {
  1066. if (ui.cursor.visible)
  1067. ToggleQuickMenu();
  1068. }
  1069. else
  1070. SteppedObjectManipulation(key);
  1071. toolBarDirty = true;
  1072. }
  1073. }
  1074. void UnfadeUI()
  1075. {
  1076. FadeUI(false);
  1077. }
  1078. void FadeUI(bool fade = true)
  1079. {
  1080. if (uiHidden || uiFaded == fade)
  1081. return;
  1082. float opacity = (uiFaded = fade) ? uiMinOpacity : uiMaxOpacity;
  1083. Array<UIElement@> children = ui.root.GetChildren();
  1084. for (uint i = 0; i < children.length; ++i)
  1085. {
  1086. // Texts, popup&modal windows (which are anyway only in ui.modalRoot), and editorUIElement are excluded
  1087. if (children[i].type != TEXT_TYPE && children[i] !is editorUIElement)
  1088. children[i].opacity = opacity;
  1089. }
  1090. }
  1091. bool ToggleUI()
  1092. {
  1093. HideUI(!uiHidden);
  1094. return true;
  1095. }
  1096. void UnhideUI()
  1097. {
  1098. HideUI(false);
  1099. }
  1100. void HideUI(bool hide = true)
  1101. {
  1102. if (uiHidden == hide)
  1103. return;
  1104. bool visible = !(uiHidden = hide);
  1105. Array<UIElement@> children = ui.root.GetChildren();
  1106. for (uint i = 0; i < children.length; ++i)
  1107. {
  1108. // Cursor and editorUIElement are excluded
  1109. if (children[i].type != CURSOR_TYPE && children[i] !is editorUIElement)
  1110. {
  1111. if (visible)
  1112. {
  1113. if (!children[i].visible)
  1114. children[i].visible = children[i].vars["HideUI"].GetBool();
  1115. }
  1116. else
  1117. {
  1118. children[i].vars["HideUI"] = children[i].visible;
  1119. children[i].visible = false;
  1120. }
  1121. }
  1122. }
  1123. }
  1124. void IconizeUIElement(UIElement@ element, const String&in iconType)
  1125. {
  1126. // Check if the icon has been created before
  1127. BorderImage@ icon = element.GetChild("Icon");
  1128. // If iconType is empty, it is a request to remove the existing icon
  1129. if (iconType.empty)
  1130. {
  1131. // Remove the icon if it exists
  1132. if (icon !is null)
  1133. icon.Remove();
  1134. // Revert back the indent but only if it is indented by this function
  1135. if (element.vars[INDENT_MODIFIED_BY_ICON_VAR].GetBool())
  1136. element.indent = 0;
  1137. return;
  1138. }
  1139. // The UI element must itself has been indented to reserve the space for the icon
  1140. if (element.indent == 0)
  1141. {
  1142. element.indent = 1;
  1143. element.vars[INDENT_MODIFIED_BY_ICON_VAR] = true;
  1144. }
  1145. // If no icon yet then create one with the correct indent and size in respect to the UI element
  1146. if (icon is null)
  1147. {
  1148. // The icon is placed at one indent level less than the UI element
  1149. icon = BorderImage("Icon");
  1150. icon.indent = element.indent - 1;
  1151. icon.SetFixedSize(element.indentWidth - 2, 14);
  1152. element.InsertChild(0, icon); // Ensure icon is added as the first child
  1153. }
  1154. // Set the icon type
  1155. if (!icon.SetStyle(iconType, iconStyle))
  1156. icon.SetStyle("Unknown", iconStyle); // If fails then use an 'unknown' icon type
  1157. icon.color = Color(1,1,1,1); // Reset to enabled color
  1158. }
  1159. void SetIconEnabledColor(UIElement@ element, bool enabled, bool partial = false)
  1160. {
  1161. BorderImage@ icon = element.GetChild("Icon");
  1162. if (icon !is null)
  1163. {
  1164. if (partial)
  1165. {
  1166. icon.colors[C_TOPLEFT] = Color(1,1,1,1);
  1167. icon.colors[C_BOTTOMLEFT] = Color(1,1,1,1);
  1168. icon.colors[C_TOPRIGHT] = Color(1,0,0,1);
  1169. icon.colors[C_BOTTOMRIGHT] = Color(1,0,0,1);
  1170. }
  1171. else
  1172. icon.color = enabled ? Color(1,1,1,1) : Color(1,0,0,1);
  1173. }
  1174. }
  1175. void UpdateDirtyUI()
  1176. {
  1177. UpdateDirtyToolBar();
  1178. // Perform hierarchy selection latently after the new selections are finalized (used in undo/redo action)
  1179. if (!hierarchyUpdateSelections.empty)
  1180. {
  1181. hierarchyList.SetSelections(hierarchyUpdateSelections);
  1182. hierarchyUpdateSelections.Clear();
  1183. }
  1184. // Perform some event-triggered updates latently in case a large hierarchy was changed
  1185. if (attributesFullDirty || attributesDirty)
  1186. UpdateAttributeInspector(attributesFullDirty);
  1187. }
  1188. void HandleMessageAcknowledgement(StringHash eventType, VariantMap& eventData)
  1189. {
  1190. if (eventData["Ok"].GetBool())
  1191. messageBoxCallback();
  1192. else
  1193. messageBoxCallback = null;
  1194. }
  1195. void PopulateMruScenes()
  1196. {
  1197. mruScenesPopup.RemoveAllChildren();
  1198. if (uiRecentScenes.length > 0)
  1199. {
  1200. recentSceneMenu.enabled = true;
  1201. for (uint i=0; i < uiRecentScenes.length; ++i)
  1202. mruScenesPopup.AddChild(CreateMenuItem(uiRecentScenes[i], @LoadMostRecentScene, 0, 0, false));
  1203. }
  1204. else
  1205. recentSceneMenu.enabled = false;
  1206. }
  1207. bool LoadMostRecentScene()
  1208. {
  1209. Menu@ menu = GetEventSender();
  1210. if (menu is null)
  1211. return false;
  1212. Text@ text = menu.GetChildren()[0];
  1213. if (text is null)
  1214. return false;
  1215. return LoadScene(text.text);
  1216. }