EditorUI.as 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394
  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. // Set new viewport area and reset the viewport layout
  102. viewportArea = IntRect(0, 0, graphics.width, graphics.height);
  103. SetViewportMode(viewportMode);
  104. }
  105. void AdjustPosition(Window@ window)
  106. {
  107. IntVector2 position = window.position;
  108. IntVector2 size = window.size;
  109. IntVector2 extend = position + size;
  110. if (extend.x > graphics.width)
  111. position.x = Max(10, graphics.width - size.x - 10);
  112. if (extend.y > graphics.height)
  113. position.y = Max(100, graphics.height - size.y - 10);
  114. window.position = position;
  115. }
  116. void CreateCursor()
  117. {
  118. Cursor@ cursor = Cursor("Cursor");
  119. cursor.SetStyleAuto(uiStyle);
  120. cursor.SetPosition(graphics.width / 2, graphics.height / 2);
  121. ui.cursor = cursor;
  122. if (GetPlatform() == "Android" || GetPlatform() == "iOS")
  123. ui.cursor.visible = false;
  124. }
  125. // AngelScript does not support closures (yet), but funcdef should do just fine as a workaround for a few cases here for now
  126. funcdef bool MENU_CALLBACK();
  127. Array<MENU_CALLBACK@> menuCallbacks;
  128. MENU_CALLBACK@ messageBoxCallback;
  129. void HandleQuickSearchChange(StringHash eventType, VariantMap& eventData)
  130. {
  131. LineEdit@ search = eventData["Element"].GetPtr();
  132. if (search is null)
  133. return;
  134. PerformQuickMenuSearch(search.text.ToLower().Trimmed());
  135. }
  136. void PerformQuickMenuSearch(const String&in query)
  137. {
  138. Menu@ menu = quickMenu.GetChild("ResultsMenu", true);
  139. if (menu is null)
  140. return;
  141. menu.RemoveAllChildren();
  142. uint limit = 0;
  143. if (query.length > 0)
  144. {
  145. int lastIndex = 0;
  146. uint score = 0;
  147. int index = 0;
  148. Array<QuickMenuItem@> filtered;
  149. {
  150. QuickMenuItem@ qi;
  151. for (uint x=0; x < quickMenuItems.length; x++)
  152. {
  153. @qi = quickMenuItems[x];
  154. int find = qi.action.Find(query, 0, false);
  155. if (find > -1)
  156. {
  157. qi.sortScore = find;
  158. filtered.Push(qi);
  159. }
  160. }
  161. }
  162. filtered.Sort();
  163. {
  164. QuickMenuItem@ qi;
  165. limit = filtered.length > MAX_QUICK_MENU_ITEMS ? MAX_QUICK_MENU_ITEMS : filtered.length;
  166. for (uint x=0; x < limit; x++)
  167. {
  168. @qi = filtered[x];
  169. Menu@ item = CreateMenuItem(qi.action, qi.callback);
  170. item.SetMaxSize(1000,16);
  171. menu.AddChild(item);
  172. }
  173. }
  174. }
  175. menu.visible = limit > 0;
  176. menu.SetFixedHeight(limit * 16);
  177. quickMenu.BringToFront();
  178. quickMenu.SetFixedHeight(limit*16 + 62 + (menu.visible ? 6 : 0));
  179. }
  180. class QuickMenuItem
  181. {
  182. String action;
  183. MENU_CALLBACK@ callback;
  184. uint sortScore = 0;
  185. QuickMenuItem(){}
  186. QuickMenuItem(String action, MENU_CALLBACK@ callback)
  187. {
  188. this.action = action;
  189. this.callback = callback;
  190. }
  191. int opCmp(QuickMenuItem@ b)
  192. {
  193. return sortScore - b.sortScore;
  194. }
  195. }
  196. /// Create popup search menu.
  197. void CreateQuickMenu()
  198. {
  199. if (quickMenu !is null)
  200. return;
  201. quickMenu = ui.LoadLayout(cache.GetResource("XMLFile", "UI/EditorQuickMenu.xml"));
  202. quickMenu.enabled = false;
  203. quickMenu.visible = false;
  204. // Handle a dummy search in the quick menu to finalize its initial size to empty
  205. PerformQuickMenuSearch("");
  206. ui.root.AddChild(quickMenu);
  207. LineEdit@ search = quickMenu.GetChild("Search", true);
  208. SubscribeToEvent(search, "TextChanged", "HandleQuickSearchChange");
  209. UIElement@ closeButton = quickMenu.GetChild("CloseButton", true);
  210. SubscribeToEvent(closeButton, "Pressed", "ToggleQuickMenu");
  211. }
  212. void ToggleQuickMenu()
  213. {
  214. quickMenu.enabled = !quickMenu.enabled && ui.cursor.visible;
  215. quickMenu.visible = quickMenu.enabled;
  216. if (quickMenu.enabled)
  217. {
  218. quickMenu.position = ui.cursorPosition - IntVector2(20,70);
  219. LineEdit@ search = quickMenu.GetChild("Search", true);
  220. search.text = "";
  221. search.focus = true;
  222. }
  223. }
  224. /// Create top menu bar.
  225. void CreateMenuBar()
  226. {
  227. uiMenuBar = BorderImage("MenuBar");
  228. ui.root.AddChild(uiMenuBar);
  229. uiMenuBar.enabled = true;
  230. uiMenuBar.style = "EditorMenuBar";
  231. uiMenuBar.SetLayout(LM_HORIZONTAL);
  232. uiMenuBar.opacity = uiMaxOpacity;
  233. uiMenuBar.SetFixedWidth(graphics.width);
  234. {
  235. Menu@ menu = CreateMenu("File");
  236. Window@ popup = menu.popup;
  237. popup.AddChild(CreateMenuItem("New scene", @ResetScene, 'N', QUAL_SHIFT | QUAL_CTRL));
  238. popup.AddChild(CreateMenuItem("Open scene...", @PickFile, 'O', QUAL_CTRL));
  239. popup.AddChild(CreateMenuItem("Save scene", @SaveSceneWithExistingName, 'S', QUAL_CTRL));
  240. popup.AddChild(CreateMenuItem("Save scene as...", @PickFile, 'S', QUAL_SHIFT | QUAL_CTRL));
  241. recentSceneMenu = CreateMenuItem("Open recent scene", null, SHOW_POPUP_INDICATOR);
  242. popup.AddChild(recentSceneMenu);
  243. mruScenesPopup = CreatePopup(recentSceneMenu);
  244. PopulateMruScenes();
  245. CreateChildDivider(popup);
  246. Menu@ childMenu = CreateMenuItem("Load node", null, SHOW_POPUP_INDICATOR);
  247. Window@ childPopup = CreatePopup(childMenu);
  248. childPopup.AddChild(CreateMenuItem("As replicated...", @PickFile, 0, 0, true, "Load node as replicated..."));
  249. childPopup.AddChild(CreateMenuItem("As local...", @PickFile, 0, 0, true, "Load node as local..."));
  250. popup.AddChild(childMenu);
  251. popup.AddChild(CreateMenuItem("Save node as...", @PickFile));
  252. CreateChildDivider(popup);
  253. popup.AddChild(CreateMenuItem("Import model...", @PickFile));
  254. popup.AddChild(CreateMenuItem("Import scene...", @PickFile));
  255. CreateChildDivider(popup);
  256. popup.AddChild(CreateMenuItem("Run script...", @PickFile));
  257. popup.AddChild(CreateMenuItem("Set resource path...", @PickFile));
  258. CreateChildDivider(popup);
  259. popup.AddChild(CreateMenuItem("Exit", @Exit));
  260. FinalizedPopupMenu(popup);
  261. uiMenuBar.AddChild(menu);
  262. }
  263. {
  264. Menu@ menu = CreateMenu("Edit");
  265. Window@ popup = menu.popup;
  266. popup.AddChild(CreateMenuItem("Undo", @Undo, 'Z', QUAL_CTRL));
  267. popup.AddChild(CreateMenuItem("Redo", @Redo, 'Y', QUAL_CTRL));
  268. CreateChildDivider(popup);
  269. popup.AddChild(CreateMenuItem("Cut", @Cut, 'X', QUAL_CTRL));
  270. popup.AddChild(CreateMenuItem("Copy", @Copy, 'C', QUAL_CTRL));
  271. popup.AddChild(CreateMenuItem("Paste", @Paste, 'V', QUAL_CTRL));
  272. popup.AddChild(CreateMenuItem("Delete", @Delete, KEY_DELETE, QUAL_ANY));
  273. popup.AddChild(CreateMenuItem("Select all", @SelectAll, 'A', QUAL_CTRL));
  274. CreateChildDivider(popup);
  275. popup.AddChild(CreateMenuItem("Reset to default", @ResetToDefault));
  276. CreateChildDivider(popup);
  277. popup.AddChild(CreateMenuItem("Reset position", @SceneResetPosition));
  278. popup.AddChild(CreateMenuItem("Reset rotation", @SceneResetRotation));
  279. popup.AddChild(CreateMenuItem("Reset scale", @SceneResetScale));
  280. popup.AddChild(CreateMenuItem("Enable/disable", @SceneToggleEnable, 'E', QUAL_CTRL));
  281. popup.AddChild(CreateMenuItem("Unparent", @SceneUnparent, 'U', QUAL_CTRL));
  282. CreateChildDivider(popup);
  283. popup.AddChild(CreateMenuItem("Toggle update", @ToggleSceneUpdate, 'P', QUAL_CTRL));
  284. popup.AddChild(CreateMenuItem("Stop test animation", @StopTestAnimation));
  285. CreateChildDivider(popup);
  286. popup.AddChild(CreateMenuItem("Rebuild navigation data", @SceneRebuildNavigation));
  287. popup.AddChild(CreateMenuItem("Load particle data", @PickFile));
  288. popup.AddChild(CreateMenuItem("Save particle data", @PickFile));
  289. FinalizedPopupMenu(popup);
  290. uiMenuBar.AddChild(menu);
  291. }
  292. {
  293. Menu@ menu = CreateMenu("Create");
  294. Window@ popup = menu.popup;
  295. popup.AddChild(CreateMenuItem("Replicated node", @PickNode, 0, 0, true, "Create Replicated node"));
  296. popup.AddChild(CreateMenuItem("Local node", @PickNode, 0, 0, true, "Create Local node"));
  297. CreateChildDivider(popup);
  298. Menu@ childMenu = CreateMenuItem("Component", null, SHOW_POPUP_INDICATOR);
  299. Window@ childPopup = CreatePopup(childMenu);
  300. String[] objectCategories = GetObjectCategories();
  301. for (uint i = 0; i < objectCategories.length; ++i)
  302. {
  303. // Skip the UI category for the component menus
  304. if (objectCategories[i] == "UI")
  305. continue;
  306. Menu@ menu = CreateMenuItem(objectCategories[i], null, SHOW_POPUP_INDICATOR);
  307. Window@ popup = CreatePopup(menu);
  308. String[] componentTypes = GetObjectsByCategory(objectCategories[i]);
  309. for (uint j = 0; j < componentTypes.length; ++j)
  310. popup.AddChild(CreateIconizedMenuItem(componentTypes[j], @PickComponent, 0, 0, "", true, "Create " + componentTypes[j]));
  311. childPopup.AddChild(menu);
  312. }
  313. FinalizedPopupMenu(childPopup);
  314. popup.AddChild(childMenu);
  315. childMenu = CreateMenuItem("Builtin object", null, SHOW_POPUP_INDICATOR);
  316. childPopup = CreatePopup(childMenu);
  317. String[] objects = { "Box", "Cone", "Cylinder", "Plane", "Pyramid", "Sphere", "TeaPot", "Torus" };
  318. for (uint i = 0; i < objects.length; ++i)
  319. childPopup.AddChild(CreateIconizedMenuItem(objects[i], @PickBuiltinObject, 0, 0, "Node", true, "Create " + objects[i]));
  320. popup.AddChild(childMenu);
  321. CreateChildDivider(popup);
  322. childMenu = CreateMenuItem("UI-element", null, SHOW_POPUP_INDICATOR);
  323. childPopup = CreatePopup(childMenu);
  324. String[] uiElementTypes = GetObjectsByCategory("UI");
  325. for (uint i = 0; i < uiElementTypes.length; ++i)
  326. {
  327. if (uiElementTypes[i] != "UIElement")
  328. childPopup.AddChild(CreateIconizedMenuItem(uiElementTypes[i], @PickUIElement, 0, 0, "", true, "Create " + uiElementTypes[i]));
  329. }
  330. CreateChildDivider(childPopup);
  331. childPopup.AddChild(CreateIconizedMenuItem("UIElement", @PickUIElement));
  332. popup.AddChild(childMenu);
  333. FinalizedPopupMenu(popup);
  334. uiMenuBar.AddChild(menu);
  335. }
  336. {
  337. Menu@ menu = CreateMenu("UI-layout");
  338. Window@ popup = menu.popup;
  339. popup.AddChild(CreateMenuItem("Open UI-layout...", @PickFile, 'O', QUAL_ALT));
  340. popup.AddChild(CreateMenuItem("Save UI-layout", @SaveUILayoutWithExistingName, 'S', QUAL_ALT));
  341. popup.AddChild(CreateMenuItem("Save UI-layout as...", @PickFile));
  342. CreateChildDivider(popup);
  343. popup.AddChild(CreateMenuItem("Close UI-layout", @CloseUILayout, 'C', QUAL_ALT));
  344. popup.AddChild(CreateMenuItem("Close all UI-layouts", @CloseAllUILayouts));
  345. CreateChildDivider(popup);
  346. popup.AddChild(CreateMenuItem("Load child element...", @PickFile));
  347. popup.AddChild(CreateMenuItem("Save child element as...", @PickFile));
  348. CreateChildDivider(popup);
  349. popup.AddChild(CreateMenuItem("Set default style...", @PickFile));
  350. FinalizedPopupMenu(popup);
  351. uiMenuBar.AddChild(menu);
  352. }
  353. {
  354. Menu@ menu = CreateMenu("View");
  355. Window@ popup = menu.popup;
  356. popup.AddChild(CreateMenuItem("Hierarchy", @ShowHierarchyWindow, 'H', QUAL_CTRL));
  357. popup.AddChild(CreateMenuItem("Attribute inspector", @ShowAttributeInspectorWindow, 'I', QUAL_CTRL));
  358. popup.AddChild(CreateMenuItem("Material editor", @ShowMaterialEditor));
  359. popup.AddChild(CreateMenuItem("Editor settings", @ShowEditorSettingsDialog));
  360. popup.AddChild(CreateMenuItem("Editor preferences", @ShowEditorPreferencesDialog));
  361. CreateChildDivider(popup);
  362. popup.AddChild(CreateMenuItem("Hide editor", @ToggleUI, KEY_F12, QUAL_ANY));
  363. FinalizedPopupMenu(popup);
  364. uiMenuBar.AddChild(menu);
  365. }
  366. BorderImage@ spacer = BorderImage("MenuBarSpacer");
  367. uiMenuBar.AddChild(spacer);
  368. spacer.style = "EditorMenuBar";
  369. BorderImage@ logo = BorderImage("Logo");
  370. logo.texture = cache.GetResource("Texture2D", "Textures/Logo.png");
  371. logo.SetFixedWidth(64);
  372. uiMenuBar.AddChild(logo);
  373. }
  374. bool Exit()
  375. {
  376. ui.cursor.shape = CS_BUSY;
  377. if (messageBoxCallback is null)
  378. {
  379. String message;
  380. if (sceneModified)
  381. message = "Scene has been modified.\n";
  382. bool uiLayoutModified = false;
  383. for (uint i = 0; i < editorUIElement.numChildren; ++i)
  384. {
  385. UIElement@ element = editorUIElement.children[i];
  386. if (element !is null && element.vars[MODIFIED_VAR].GetBool())
  387. {
  388. uiLayoutModified = true;
  389. message += "UI layout has been modified.\n";
  390. break;
  391. }
  392. }
  393. if (sceneModified || uiLayoutModified)
  394. {
  395. MessageBox@ messageBox = MessageBox(message + "Continue to exit?", "Warning");
  396. if (messageBox.window !is null)
  397. {
  398. Button@ cancelButton = messageBox.window.GetChild("CancelButton", true);
  399. cancelButton.visible = true;
  400. cancelButton.focus = true;
  401. SubscribeToEvent(messageBox, "MessageACK", "HandleMessageAcknowledgement");
  402. messageBoxCallback = @Exit;
  403. return false;
  404. }
  405. }
  406. }
  407. else
  408. messageBoxCallback = null;
  409. engine.Exit();
  410. return true;
  411. }
  412. void HandleExitRequested()
  413. {
  414. if (!ui.HasModalElement())
  415. Exit();
  416. }
  417. bool PickFile()
  418. {
  419. Menu@ menu = GetEventSender();
  420. if (menu is null)
  421. return false;
  422. String action = menu.name;
  423. if (action.empty)
  424. return false;
  425. // File (Scene related)
  426. if (action == "Open scene...")
  427. {
  428. CreateFileSelector("Open scene", "Open", "Cancel", uiScenePath, uiSceneFilters, uiSceneFilter);
  429. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleOpenSceneFile");
  430. }
  431. else if (action == "Save scene as..." || action == "Save scene")
  432. {
  433. CreateFileSelector("Save scene as", "Save", "Cancel", uiScenePath, uiSceneFilters, uiSceneFilter);
  434. uiFileSelector.fileName = GetFileNameAndExtension(editorScene.fileName);
  435. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleSaveSceneFile");
  436. }
  437. else if (action == "As replicated..." || action == "Load node as replicated...")
  438. {
  439. instantiateMode = REPLICATED;
  440. CreateFileSelector("Load node", "Load", "Cancel", uiNodePath, uiSceneFilters, uiNodeFilter);
  441. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleLoadNodeFile");
  442. }
  443. else if (action == "As local..." || action == "Load node as local...")
  444. {
  445. instantiateMode = LOCAL;
  446. CreateFileSelector("Load node", "Load", "Cancel", uiNodePath, uiSceneFilters, uiNodeFilter);
  447. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleLoadNodeFile");
  448. }
  449. else if (action == "Save node as...")
  450. {
  451. if (editNode !is null && editNode !is editorScene)
  452. {
  453. CreateFileSelector("Save node", "Save", "Cancel", uiNodePath, uiSceneFilters, uiNodeFilter);
  454. uiFileSelector.fileName = GetFileNameAndExtension(instantiateFileName);
  455. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleSaveNodeFile");
  456. }
  457. }
  458. else if (action == "Import model...")
  459. {
  460. CreateFileSelector("Import model", "Import", "Cancel", uiImportPath, uiAllFilters, uiImportFilter);
  461. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleImportModel");
  462. }
  463. else if (action == "Import scene...")
  464. {
  465. CreateFileSelector("Import scene", "Import", "Cancel", uiImportPath, uiAllFilters, uiImportFilter);
  466. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleImportScene");
  467. }
  468. else if (action == "Run script...")
  469. {
  470. CreateFileSelector("Run script", "Run", "Cancel", uiScriptPath, uiScriptFilters, uiScriptFilter);
  471. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleRunScript");
  472. }
  473. else if (action == "Set resource path...")
  474. {
  475. CreateFileSelector("Set resource path", "Set", "Cancel", sceneResourcePath, uiAllFilters, 0);
  476. uiFileSelector.directoryMode = true;
  477. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleResourcePath");
  478. }
  479. else if (action == "Load particle data")
  480. {
  481. bool hasParticleEmitter = false;
  482. for (uint i = 0; i < editComponents.length; ++i)
  483. {
  484. if (editComponents[i].typeName == "ParticleEmitter")
  485. {
  486. hasParticleEmitter = true;
  487. break;
  488. }
  489. }
  490. if (hasParticleEmitter)
  491. {
  492. CreateFileSelector("Load particle data", "Load", "Cancel", uiParticlePath, uiParticleFilters, uiParticleFilter);
  493. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleLoadParticleData");
  494. }
  495. }
  496. else if (action == "Save particle data")
  497. {
  498. if (editComponents.length == 1 && editComponents[0].typeName == "ParticleEmitter")
  499. {
  500. CreateFileSelector("Save particle data", "Save", "Cancel", uiParticlePath, uiParticleFilters, uiParticleFilter);
  501. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleSaveParticleData");
  502. }
  503. }
  504. // UI-element
  505. else if (action == "Open UI-layout...")
  506. {
  507. CreateFileSelector("Open UI-layout", "Open", "Cancel", uiElementPath, uiElementFilters, uiElementFilter);
  508. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleOpenUILayoutFile");
  509. }
  510. else if (action == "Save UI-layout as..." || action == "Save UI-layout")
  511. {
  512. if (editUIElement !is null)
  513. {
  514. UIElement@ element = GetTopLevelUIElement(editUIElement);
  515. if (element is null)
  516. return false;
  517. CreateFileSelector("Save UI-layout as", "Save", "Cancel", uiElementPath, uiElementFilters, uiElementFilter);
  518. uiFileSelector.fileName = GetFileNameAndExtension(element.GetVar(FILENAME_VAR).GetString());
  519. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleSaveUILayoutFile");
  520. }
  521. }
  522. else if (action == "Load child element...")
  523. {
  524. if (editUIElement !is null)
  525. {
  526. CreateFileSelector("Load child element", "Load", "Cancel", uiElementPath, uiElementFilters, uiElementFilter);
  527. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleLoadChildUIElementFile");
  528. }
  529. }
  530. else if (action == "Save child element as...")
  531. {
  532. if (editUIElement !is null)
  533. {
  534. CreateFileSelector("Save child element", "Save", "Cancel", uiElementPath, uiElementFilters, uiElementFilter);
  535. uiFileSelector.fileName = GetFileNameAndExtension(editUIElement.GetVar(CHILD_ELEMENT_FILENAME_VAR).GetString());
  536. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleSaveChildUIElementFile");
  537. }
  538. }
  539. else if (action == "Set default style...")
  540. {
  541. CreateFileSelector("Set default style", "Set", "Cancel", uiElementPath, uiElementFilters, uiElementFilter);
  542. SubscribeToEvent(uiFileSelector, "FileSelected", "HandleUIElementDefaultStyle");
  543. }
  544. return true;
  545. }
  546. bool PickNode()
  547. {
  548. Menu@ menu = GetEventSender();
  549. if (menu is null)
  550. return false;
  551. String action = GetActionName(menu.name);
  552. if (action.empty)
  553. return false;
  554. CreateNode(action == "Replicated node" ? REPLICATED : LOCAL);
  555. return true;
  556. }
  557. bool PickComponent()
  558. {
  559. if (editNodes.empty)
  560. return false;
  561. Menu@ menu = GetEventSender();
  562. if (menu is null)
  563. return false;
  564. String action = GetActionName(menu.name);
  565. if (action.empty)
  566. return false;
  567. CreateComponent(action);
  568. return true;
  569. }
  570. bool PickBuiltinObject()
  571. {
  572. Menu@ menu = GetEventSender();
  573. if (menu is null)
  574. return false;
  575. String action = GetActionName(menu.name);
  576. if (action.empty)
  577. return false;
  578. CreateBuiltinObject(action);
  579. return true;
  580. }
  581. bool PickUIElement()
  582. {
  583. Menu@ menu = GetEventSender();
  584. if (menu is null)
  585. return false;
  586. String action = GetActionName(menu.name);
  587. if (action.empty)
  588. return false;
  589. return NewUIElement(action);
  590. }
  591. // When calling items from the quick menu, they have "Create" prepended for clarity. Strip that now to get the object name to create
  592. String GetActionName(const String&in name)
  593. {
  594. if (name.StartsWith("Create"))
  595. return name.Substring(7);
  596. else
  597. return name;
  598. }
  599. void HandleMenuSelected(StringHash eventType, VariantMap& eventData)
  600. {
  601. Menu@ menu = eventData["Element"].GetPtr();
  602. if (menu is null)
  603. return;
  604. HandlePopup(menu);
  605. quickMenu.visible = false;
  606. quickMenu.enabled = false;
  607. // Execute the callback if available
  608. Variant variant = menu.GetVar(CALLBACK_VAR);
  609. if (!variant.empty)
  610. menuCallbacks[variant.GetUInt()]();
  611. }
  612. Menu@ CreateMenuItem(const String&in title, MENU_CALLBACK@ callback = null, int accelKey = 0, int accelQual = 0, bool addToQuickMenu = true, String quickMenuText="")
  613. {
  614. Menu@ menu = Menu(title);
  615. menu.defaultStyle = uiStyle;
  616. menu.style = AUTO_STYLE;
  617. menu.SetLayout(LM_HORIZONTAL, 0, IntRect(8, 2, 8, 2));
  618. if (accelKey > 0)
  619. menu.SetAccelerator(accelKey, accelQual);
  620. if (callback !is null)
  621. {
  622. menu.vars[CALLBACK_VAR] = menuCallbacks.length;
  623. menuCallbacks.Push(callback);
  624. }
  625. Text@ menuText = Text();
  626. menu.AddChild(menuText);
  627. menuText.style = "EditorMenuText";
  628. menuText.text = title;
  629. if (addToQuickMenu)
  630. AddQuickMenuItem(callback, quickMenuText.empty ? title : quickMenuText);
  631. if (accelKey != 0)
  632. {
  633. UIElement@ spacer = UIElement();
  634. spacer.minWidth = menuText.indentSpacing;
  635. spacer.height = menuText.height;
  636. menu.AddChild(spacer);
  637. menu.AddChild(CreateAccelKeyText(accelKey, accelQual));
  638. }
  639. return menu;
  640. }
  641. void AddQuickMenuItem(MENU_CALLBACK@ callback, String text)
  642. {
  643. if (callback is null)
  644. return;
  645. bool exists = false;
  646. for (uint i=0;i<quickMenuItems.length;i++)
  647. {
  648. if (quickMenuItems[i].action == text)
  649. {
  650. exists = true;
  651. break;
  652. }
  653. }
  654. if (!exists)
  655. quickMenuItems.Push(QuickMenuItem(text, callback));
  656. }
  657. 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="")
  658. {
  659. Menu@ menu = Menu(title);
  660. menu.defaultStyle = uiStyle;
  661. menu.style = AUTO_STYLE;
  662. menu.SetLayout(LM_VERTICAL, 0, IntRect(8, 2, 8, 2));
  663. if (accelKey > 0)
  664. menu.SetAccelerator(accelKey, accelQual);
  665. if (callback !is null)
  666. {
  667. menu.vars[CALLBACK_VAR] = menuCallbacks.length;
  668. menuCallbacks.Push(callback);
  669. }
  670. Text@ menuText = Text();
  671. menu.AddChild(menuText);
  672. menuText.style = "EditorMenuText";
  673. menuText.text = title;
  674. // If icon type is not provided, use the title instead
  675. IconizeUIElement(menuText, iconType.empty ? title : iconType);
  676. if (addToQuickMenu)
  677. AddQuickMenuItem(callback, quickMenuText.empty ? title : quickMenuText);
  678. if (accelKey != 0)
  679. {
  680. menuText.layoutMode = LM_HORIZONTAL;
  681. menuText.AddChild(CreateAccelKeyText(accelKey, accelQual));
  682. }
  683. return menu;
  684. }
  685. /// Create a child divider in parent with vertical layout mode. It works on other parent element as well, not just parent menu.
  686. void CreateChildDivider(UIElement@ parent)
  687. {
  688. BorderImage@ divider = parent.CreateChild("BorderImage", "Divider");
  689. divider.style = "EditorDivider";
  690. }
  691. Window@ CreatePopup(Menu@ baseMenu)
  692. {
  693. Window@ popup = Window();
  694. popup.defaultStyle = uiStyle;
  695. popup.style = AUTO_STYLE;
  696. popup.SetLayout(LM_VERTICAL, 1, IntRect(2, 6, 2, 6));
  697. baseMenu.popup = popup;
  698. baseMenu.popupOffset = IntVector2(0, baseMenu.height);
  699. return popup;
  700. }
  701. Menu@ CreateMenu(const String&in title)
  702. {
  703. Menu@ menu = CreateMenuItem(title);
  704. menu.SetFixedWidth(menu.width);
  705. CreatePopup(menu);
  706. return menu;
  707. }
  708. Text@ CreateAccelKeyText(int accelKey, int accelQual)
  709. {
  710. Text@ accelKeyText = Text();
  711. accelKeyText.defaultStyle = uiStyle;
  712. accelKeyText.style = "EditorMenuText";
  713. accelKeyText.textAlignment = HA_RIGHT;
  714. String text;
  715. if (accelKey == KEY_DELETE)
  716. text = "Del";
  717. else if (accelKey == KEY_SPACE)
  718. text = "Space";
  719. // Cannot use range as the key constants below do not appear to be in sequence
  720. else if (accelKey == KEY_F1)
  721. text = "F1";
  722. else if (accelKey == KEY_F2)
  723. text = "F2";
  724. else if (accelKey == KEY_F3)
  725. text = "F3";
  726. else if (accelKey == KEY_F4)
  727. text = "F4";
  728. else if (accelKey == KEY_F5)
  729. text = "F5";
  730. else if (accelKey == KEY_F6)
  731. text = "F6";
  732. else if (accelKey == KEY_F7)
  733. text = "F7";
  734. else if (accelKey == KEY_F8)
  735. text = "F8";
  736. else if (accelKey == KEY_F9)
  737. text = "F9";
  738. else if (accelKey == KEY_F10)
  739. text = "F10";
  740. else if (accelKey == KEY_F11)
  741. text = "F11";
  742. else if (accelKey == KEY_F12)
  743. text = "F12";
  744. else if (accelKey == SHOW_POPUP_INDICATOR)
  745. text = ">";
  746. else
  747. text.AppendUTF8(accelKey);
  748. if (accelQual & QUAL_ALT > 0)
  749. text = "Alt+" + text;
  750. if (accelQual & QUAL_SHIFT > 0)
  751. text = "Shift+" + text;
  752. if (accelQual & QUAL_CTRL > 0)
  753. text = "Ctrl+" + text;
  754. accelKeyText.text = text;
  755. return accelKeyText;
  756. }
  757. void FinalizedPopupMenu(Window@ popup)
  758. {
  759. // Find the maximum menu text width
  760. Array<UIElement@> children = popup.GetChildren();
  761. int maxWidth = 0;
  762. for (uint i = 0; i < children.length; ++i)
  763. {
  764. UIElement@ element = children[i];
  765. if (element.type != MENU_TYPE) // Skip if not menu item
  766. continue;
  767. int width = element.children[0].width;
  768. if (width > maxWidth)
  769. maxWidth = width;
  770. }
  771. // Adjust the indent spacing to slightly wider than the maximum width
  772. maxWidth += 20;
  773. for (uint i = 0; i < children.length; ++i)
  774. {
  775. UIElement@ element = children[i];
  776. if (element.type != MENU_TYPE)
  777. continue;
  778. Menu@ menu = element;
  779. Text@ menuText = menu.children[0];
  780. if (menuText.numChildren == 1) // Skip if menu text does not have accel
  781. menuText.children[0].indentSpacing = maxWidth;
  782. // Adjust the popup offset taking the indentation into effect
  783. if (menu.popup !is null)
  784. menu.popupOffset = IntVector2(menu.width, 0);
  785. }
  786. }
  787. void CreateFileSelector(const String&in title, const String&in ok, const String&in cancel, const String&in initialPath, Array<String>@ filters,
  788. uint initialFilter)
  789. {
  790. // Within the editor UI, the file selector is a kind of a "singleton". When the previous one is overwritten, also
  791. // the events subscribed from it are disconnected, so new ones are safe to subscribe.
  792. uiFileSelector = FileSelector();
  793. uiFileSelector.defaultStyle = uiStyle;
  794. uiFileSelector.title = title;
  795. uiFileSelector.path = initialPath;
  796. uiFileSelector.SetButtonTexts(ok, cancel);
  797. uiFileSelector.SetFilters(filters, initialFilter);
  798. CenterDialog(uiFileSelector.window);
  799. }
  800. void CloseFileSelector(uint&out filterIndex, String&out path)
  801. {
  802. // Save filter & path for next time
  803. filterIndex = uiFileSelector.filterIndex;
  804. path = uiFileSelector.path;
  805. uiFileSelector = null;
  806. }
  807. void CloseFileSelector()
  808. {
  809. uiFileSelector = null;
  810. }
  811. void CreateConsole()
  812. {
  813. Console@ console = engine.CreateConsole();
  814. console.defaultStyle = uiStyle;
  815. console.numRows = 16;
  816. }
  817. void CreateDebugHud()
  818. {
  819. engine.CreateDebugHud();
  820. debugHud.defaultStyle = uiStyle;
  821. debugHud.mode = DEBUGHUD_SHOW_NONE;
  822. }
  823. void CenterDialog(UIElement@ element)
  824. {
  825. IntVector2 size = element.size;
  826. element.SetPosition((graphics.width - size.x) / 2, (graphics.height - size.y) / 2);
  827. }
  828. void UpdateWindowTitle()
  829. {
  830. String sceneName = GetFileNameAndExtension(editorScene.fileName);
  831. if (sceneName.empty || sceneName == TEMP_SCENE_NAME)
  832. sceneName = "Untitled";
  833. if (sceneModified)
  834. sceneName += "*";
  835. graphics.windowTitle = "Urho3D editor - " + sceneName;
  836. }
  837. void HandlePopup(Menu@ menu)
  838. {
  839. // Close the top level menu now unless the selected menu item has another popup
  840. if (menu.popup !is null)
  841. return;
  842. for (;;)
  843. {
  844. UIElement@ menuParent = menu.parent;
  845. if (menuParent is null)
  846. break;
  847. Menu@ nextMenu = menuParent.vars["Origin"].GetPtr();
  848. if (nextMenu is null)
  849. break;
  850. else
  851. menu = nextMenu;
  852. }
  853. if (menu.parent is uiMenuBar)
  854. menu.showPopup = false;
  855. }
  856. String ExtractFileName(VariantMap& eventData, bool forSave = false)
  857. {
  858. String fileName;
  859. // Check for OK
  860. if (eventData["OK"].GetBool())
  861. {
  862. String filter = eventData["Filter"].GetString();
  863. fileName = eventData["FileName"].GetString();
  864. // Add default extension for saving if not specified
  865. if (GetExtension(fileName).empty && forSave && filter != "*.*")
  866. fileName = fileName + filter.Substring(1);
  867. }
  868. return fileName;
  869. }
  870. void HandleOpenSceneFile(StringHash eventType, VariantMap& eventData)
  871. {
  872. CloseFileSelector(uiSceneFilter, uiScenePath);
  873. LoadScene(ExtractFileName(eventData));
  874. }
  875. void HandleSaveSceneFile(StringHash eventType, VariantMap& eventData)
  876. {
  877. CloseFileSelector(uiSceneFilter, uiScenePath);
  878. SaveScene(ExtractFileName(eventData, true));
  879. }
  880. void HandleLoadNodeFile(StringHash eventType, VariantMap& eventData)
  881. {
  882. CloseFileSelector(uiNodeFilter, uiNodePath);
  883. LoadNode(ExtractFileName(eventData));
  884. }
  885. void HandleSaveNodeFile(StringHash eventType, VariantMap& eventData)
  886. {
  887. CloseFileSelector(uiNodeFilter, uiNodePath);
  888. SaveNode(ExtractFileName(eventData, true));
  889. }
  890. void HandleImportModel(StringHash eventType, VariantMap& eventData)
  891. {
  892. CloseFileSelector(uiImportFilter, uiImportPath);
  893. ImportModel(ExtractFileName(eventData));
  894. }
  895. void HandleImportScene(StringHash eventType, VariantMap& eventData)
  896. {
  897. CloseFileSelector(uiImportFilter, uiImportPath);
  898. ImportScene(ExtractFileName(eventData));
  899. }
  900. void HandleLoadParticleData(StringHash eventType, VariantMap& eventData)
  901. {
  902. CloseFileSelector(uiParticleFilter, uiParticlePath);
  903. LoadParticleData(ExtractFileName(eventData));
  904. }
  905. void HandleSaveParticleData(StringHash eventType, VariantMap& eventData)
  906. {
  907. CloseFileSelector(uiParticleFilter, uiParticlePath);
  908. SaveParticleData(ExtractFileName(eventData, true));
  909. }
  910. void ExecuteScript(const String&in fileName)
  911. {
  912. if (fileName.empty)
  913. return;
  914. File@ file = File(fileName, FILE_READ);
  915. if (file.open)
  916. {
  917. String scriptCode;
  918. while (!file.eof)
  919. scriptCode += file.ReadLine() + "\n";
  920. file.Close();
  921. if (script.Execute(scriptCode))
  922. log.Info("Script " + fileName + " ran successfully");
  923. }
  924. }
  925. void HandleRunScript(StringHash eventType, VariantMap& eventData)
  926. {
  927. CloseFileSelector(uiScriptFilter, uiScriptPath);
  928. ExecuteScript(ExtractFileName(eventData));
  929. }
  930. void HandleResourcePath(StringHash eventType, VariantMap& eventData)
  931. {
  932. String pathName = uiFileSelector.path;
  933. CloseFileSelector();
  934. if (eventData["OK"].GetBool())
  935. SetResourcePath(pathName, false);
  936. }
  937. void HandleOpenUILayoutFile(StringHash eventType, VariantMap& eventData)
  938. {
  939. CloseFileSelector(uiElementFilter, uiElementPath);
  940. OpenUILayout(ExtractFileName(eventData));
  941. }
  942. void HandleSaveUILayoutFile(StringHash eventType, VariantMap& eventData)
  943. {
  944. CloseFileSelector(uiElementFilter, uiElementPath);
  945. SaveUILayout(ExtractFileName(eventData, true));
  946. }
  947. void HandleLoadChildUIElementFile(StringHash eventType, VariantMap& eventData)
  948. {
  949. CloseFileSelector(uiElementFilter, uiElementPath);
  950. LoadChildUIElement(ExtractFileName(eventData));
  951. }
  952. void HandleSaveChildUIElementFile(StringHash eventType, VariantMap& eventData)
  953. {
  954. CloseFileSelector(uiElementFilter, uiElementPath);
  955. SaveChildUIElement(ExtractFileName(eventData, true));
  956. }
  957. void HandleUIElementDefaultStyle(StringHash eventType, VariantMap& eventData)
  958. {
  959. CloseFileSelector(uiElementFilter, uiElementPath);
  960. SetUIElementDefaultStyle(ExtractFileName(eventData));
  961. }
  962. void HandleKeyDown(StringHash eventType, VariantMap& eventData)
  963. {
  964. int key = eventData["Key"].GetInt();
  965. int viewDirection = eventData["Qualifiers"].GetInt() == QUAL_CTRL ? -1 : 1;
  966. if (key == KEY_ESC)
  967. {
  968. if (uiHidden)
  969. UnhideUI();
  970. else if (console.visible)
  971. console.visible = false;
  972. else if (quickMenu.visible)
  973. {
  974. quickMenu.visible = false;
  975. quickMenu.enabled = false;
  976. }
  977. else
  978. {
  979. UIElement@ front = ui.frontElement;
  980. if (front is settingsDialog || front is preferencesDialog)
  981. {
  982. ui.focusElement = null;
  983. front.visible = false;
  984. }
  985. }
  986. }
  987. // Ignore other keys when UI has a modal element
  988. else if (ui.HasModalElement())
  989. return;
  990. else if (key == KEY_F1)
  991. console.Toggle();
  992. else if (key == KEY_F2)
  993. ToggleRenderingDebug();
  994. else if (key == KEY_F3)
  995. TogglePhysicsDebug();
  996. else if (key == KEY_F4)
  997. ToggleOctreeDebug();
  998. else if (key == KEY_NUMPAD1 && ui.focusElement is null) // Front view
  999. {
  1000. Vector3 pos = cameraNode.position;
  1001. pos.z = -pos.length * viewDirection;
  1002. pos.x = 0;
  1003. pos.y = 0;
  1004. cameraNode.position = pos;
  1005. cameraNode.direction = Vector3(0, 0, viewDirection);
  1006. ReacquireCameraYawPitch();
  1007. }
  1008. else if (key == KEY_NUMPAD3 && ui.focusElement is null) // Side view
  1009. {
  1010. Vector3 pos = cameraNode.position;
  1011. pos.x = pos.length * viewDirection;
  1012. pos.y = 0;
  1013. pos.z = 0;
  1014. cameraNode.position = pos;
  1015. cameraNode.direction = Vector3(-viewDirection, 0, 0);
  1016. ReacquireCameraYawPitch();
  1017. }
  1018. else if (key == KEY_NUMPAD7 && ui.focusElement is null) // Top view
  1019. {
  1020. Vector3 pos = cameraNode.position;
  1021. pos.y = pos.length * viewDirection;
  1022. pos.x = 0;
  1023. pos.z = 0;
  1024. cameraNode.position = pos;
  1025. cameraNode.direction = Vector3(0, -viewDirection, 0);
  1026. ReacquireCameraYawPitch();
  1027. }
  1028. else if (key == KEY_NUMPAD5 && ui.focusElement is null)
  1029. {
  1030. activeViewport.ToggleOrthographic();
  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. }
  1217. void HandleErrorEvent(StringHash eventType, VariantMap& eventData)
  1218. {
  1219. // Open console if it not yet open
  1220. if (!console.visible)
  1221. console.visible = true;
  1222. }