Ide.hx 49 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739
  1. package hide;
  2. class IdeCache {
  3. public var getTextureCache : Map<String, h3d.mat.Texture> = [];
  4. public function new() {};
  5. }
  6. @:expose
  7. class Ide extends hide.tools.IdeData {
  8. public var initializing(default,null) : Bool;
  9. public var mouseX : Int = 0;
  10. public var mouseY : Int = 0;
  11. public var isWindows(get, never) : Bool;
  12. public var isFocused(get, never) : Bool;
  13. public var shaderLoader : hide.tools.ShaderLoader;
  14. public var isCDB = false;
  15. public var isDebugger = false;
  16. public var gamePad(default,null) : hxd.Pad;
  17. public var localStorage(get,never) : js.html.Storage;
  18. var window : nw.Window;
  19. var saveMenu : nw.Menu;
  20. var layout : golden.Layout;
  21. var currentLayout : { name : String, state : Config.LayoutState };
  22. var defaultLayout : { name : String, state : Config.LayoutState };
  23. var currentFullScreen(default,set) : hide.ui.View<Dynamic>;
  24. var maximized : Bool;
  25. var fullscreen : Bool;
  26. var updates : Array<Void->Void> = [];
  27. var views : Array<hide.ui.View<Dynamic>> = [];
  28. var lastClosedTabStates : Array<Dynamic> = [];
  29. var renderers : Array<h3d.mat.MaterialSetup>;
  30. var subView : { component : String, state : Dynamic, events : {} };
  31. var scripts : Map<String,Array<Void->Void>> = new Map();
  32. var hasReloaded = false;
  33. var hideRoot : hide.Element;
  34. var statusBar : hide.Element;
  35. var goldenContainer : hide.Element;
  36. var statusIcons : hide.Element;
  37. public var show3DIcons = true;
  38. public var show3DIconsCategory : Map<hrt.impl.EditorTools.IconCategory, Bool> = new Map();
  39. static var firstInit = true;
  40. var customMenus : Array<nw.MenuItem> = [];
  41. var filePickerElement : hide.Element;
  42. function new() {
  43. super();
  44. initPad();
  45. isCDB = Sys.getEnv("HIDE_START_CDB") == "1" || nw.App.manifest.name == "CDB";
  46. isDebugger = Sys.getEnv("HIDE_DEBUG") == "1";
  47. function wait() {
  48. if( monaco.ScriptEditor == null ) {
  49. haxe.Timer.delay(wait, 10);
  50. return;
  51. }
  52. startup();
  53. }
  54. wait();
  55. }
  56. function get_localStorage() {
  57. return js.Browser.window.localStorage;
  58. }
  59. override function getAppDataPath() {
  60. return nw.App.dataPath;
  61. }
  62. function initPad() {
  63. gamePad = hxd.Pad.createDummy();
  64. hxd.Pad.wait((p) -> gamePad = p);
  65. }
  66. function startup() {
  67. inst = this;
  68. window = nw.Window.get();
  69. var cwd = Sys.getCwd();
  70. initConfig(cwd);
  71. var current = ideConfig.currentProject;
  72. if( StringTools.endsWith(cwd,"package.nw") && sys.FileSystem.exists(cwd.substr(0,-10)+"res") )
  73. cwd = cwd.substr(0,-11);
  74. if( current == "" ) cwd;
  75. var args = js.Browser.document.URL.split("?")[1];
  76. if( args != null ) {
  77. var parts = args.split("&");
  78. var vars = new Map();
  79. for( p in parts ) {
  80. var p = p.split("=");
  81. vars.set(p[0],StringTools.urlDecode(p[1]));
  82. }
  83. var sub = vars.get("subView");
  84. if( sub != null ) {
  85. var obj = untyped global.sharedRefs.get(Std.parseInt(vars.get("sid")));
  86. subView = { component : sub, state : obj.state, events : obj.events };
  87. }
  88. }
  89. nw.Screen.Init();
  90. var xMax = 1;
  91. var yMax = 1;
  92. for( s in nw.Screen.screens ) {
  93. if( s.work_area.x + s.work_area.width > xMax )
  94. xMax = s.work_area.x + s.work_area.width;
  95. if( s.work_area.y + s.work_area.height > yMax )
  96. yMax = s.work_area.y + s.work_area.height;
  97. }
  98. if( subView == null ) {
  99. var wp = ideConfig.windowPos;
  100. if( wp != null ) {
  101. if( wp.w > 400 && wp.h > 300 )
  102. window.resizeBy(wp.w - Std.int(window.window.outerWidth), wp.h - Std.int(window.window.outerHeight));
  103. if( wp.x >= 0 && wp.y >= 0 && wp.x < xMax && wp.y < yMax)
  104. window.moveTo(wp.x, wp.y);
  105. if( wp.max ) {
  106. window.maximize();
  107. maximized = true;
  108. }
  109. }
  110. }
  111. window.show(true);
  112. if( config.global.get("hide") == null )
  113. error("Failed to load defaultProps.json");
  114. if( !sys.FileSystem.exists(current) || !sys.FileSystem.isDirectory(current) ) {
  115. if( current != "" ) js.Browser.alert(current+" no longer exists");
  116. current = cwd;
  117. }
  118. setProject(current);
  119. loadProject();
  120. window.window.document.addEventListener("mousedown", function(e) {
  121. mouseX = e.x;
  122. mouseY = e.y;
  123. });
  124. window.window.document.addEventListener("mousemove", function(e) {
  125. mouseX = e.x;
  126. mouseY = e.y;
  127. });
  128. window.on('maximize', function() {
  129. if(fullscreen) return;
  130. maximized = true;
  131. onWindowChange();
  132. });
  133. window.on('restore', function() {
  134. if(fullscreen) return;
  135. maximized = false;
  136. onWindowChange();
  137. });
  138. window.on('move', function() haxe.Timer.delay(onWindowChange,100));
  139. window.on('resize', function() haxe.Timer.delay(onWindowChange,100));
  140. window.on('close', function() {
  141. if( hasReloaded ) return;
  142. if( !isDebugger )
  143. for( v in views )
  144. if( !v.onBeforeClose() )
  145. return;
  146. window.close(true);
  147. });
  148. window.on("blur", function() { if( h3d.Engine.getCurrent() != null && !hasReloaded ) hxd.Key.initialize(); });
  149. // handle commandline parameters
  150. nw.App.on("open", function(cmd) {
  151. if( hasReloaded ) return;
  152. ~/"([^"]+)"/g.map(cmd, function(r) {
  153. var file = r.matched(1);
  154. if( sys.FileSystem.exists(file) ) openFile(file);
  155. return "";
  156. });
  157. });
  158. var body = window.window.document.body;
  159. window.on("focus", function() {
  160. // handle cancel on type=file
  161. if (filePickerElement != null && filePickerElement.data("allownull") != null) {
  162. haxe.Timer.delay(() -> {
  163. if (filePickerElement != null) {
  164. filePickerElement.change();
  165. }
  166. }, 100);
  167. }
  168. if(fileExists(databaseFile) && getFileText(databaseFile) != lastDBContent) {
  169. if(js.Browser.window.confirm(databaseFile + " has changed outside of Hide. Do you want to reload?")) {
  170. loadDatabase(true);
  171. hide.comp.cdb.Editor.refreshAll(true);
  172. };
  173. }
  174. });
  175. function dragFunc(drop : Bool, e:js.html.DragEvent) {
  176. syncMousePosition(e);
  177. var view = getViewAt(mouseX, mouseY);
  178. var items : Array<String> = [for(f in e.dataTransfer.files) Reflect.field(f, "path")];
  179. if(view != null && view.onDragDrop(items, drop, e)) {
  180. e.preventDefault();
  181. e.stopPropagation();
  182. return true;
  183. }
  184. return false;
  185. }
  186. body.ondragover = function(e:js.html.DragEvent) {
  187. dragFunc(false, e);
  188. return false;
  189. };
  190. body.ondrop = function(e:js.html.DragEvent) {
  191. if(!dragFunc(true, e)) {
  192. for( f in e.dataTransfer.files )
  193. openFile(Reflect.field(f,"path"));
  194. e.preventDefault();
  195. }
  196. return false;
  197. }
  198. if( subView != null ) body.className +=" hide-subview";
  199. // Listen to FileTree dnd
  200. function treeDragFun(data,drop, event) {
  201. var nodeIds : Array<String> = cast data.data.nodes;
  202. if(data.data.jstree == null) return false;
  203. for( ft in getViews(hide.view.FileTree) ) {
  204. var paths = [];
  205. @:privateAccess {
  206. if (ft.tree == null || data.data.origin == null) continue;
  207. if(ft.tree.element[0] != data.data.origin.element[0]) continue;
  208. for(id in nodeIds) {
  209. var item = ft.tree.map.get(id);
  210. if(item != null)
  211. paths.push(item.value);
  212. }
  213. }
  214. if(paths.length == 0)
  215. continue;
  216. var view = getViewAt(mouseX, mouseY);
  217. if(view != null)
  218. return view.onDragDrop(paths, drop, event);
  219. }
  220. return false;
  221. }
  222. new Element(window.window.document).on("dnd_move.vakata.jstree", function(e, data:Dynamic) {
  223. var el = (data.helper:hide.Element);
  224. var drag = treeDragFun(data,false,data.event.originalEvent);
  225. var icon = el.find(new Element(".jstree-icon"));
  226. el.css(drag ? { filter : "brightness(120%)", opacity : 1 } : { filter : "", opacity : 0.5 });
  227. icon.toggleClass("jstree-er", !drag);
  228. icon.toggleClass("jstree-ok", drag);
  229. });
  230. new Element(window.window.document).on("dnd_stop.vakata.jstree", function(e, data:Dynamic) {
  231. treeDragFun(data,true, data.event.originalEvent);
  232. });
  233. // dispatch global keys based on mouse position
  234. new Element(body).keydown(function(e) {
  235. var view = getViewAt(mouseX, mouseY);
  236. if(view != null) view.processKeyEvent(e);
  237. });
  238. hrt.impl.EditorTools.setupIconCategories();
  239. refreshFont();
  240. }
  241. public function getViews<K,T:hide.ui.View<K>>( cl : Class<T> ) {
  242. return [for( v in views ) { var t = Std.downcast(v,cl); if( t != null ) t; }];
  243. }
  244. function getViewAt(x : Float, y : Float) {
  245. var pickedEl = js.Browser.document.elementFromPoint(x, y);
  246. for( v in views ) {
  247. var viewEl = v.element[0];
  248. var el = pickedEl;
  249. while(el != null) {
  250. if(el == viewEl) return v;
  251. el = el.parentElement;
  252. }
  253. }
  254. return null;
  255. }
  256. function syncMousePosition(e:js.html.MouseEvent) {
  257. mouseX = e.clientX;
  258. mouseY = e.clientY;
  259. for( c in new Element("canvas") ) {
  260. var s : hide.comp.Scene = (c:Dynamic).__scene;
  261. if( s != null ) @:privateAccess {
  262. if (s.window != null) {
  263. s.window.curMouseX = mouseX;
  264. s.window.curMouseY = mouseY;
  265. }
  266. }
  267. }
  268. }
  269. function get_isWindows() {
  270. return Sys.systemName() == "Windows";
  271. }
  272. function get_isFocused() {
  273. return js.Browser.document.hasFocus();
  274. }
  275. public function focus() {
  276. window.focus();
  277. }
  278. public function blur() {
  279. window.blur();
  280. }
  281. function onWindowChange() {
  282. if( hasReloaded )
  283. return;
  284. if( ideConfig.windowPos == null ) ideConfig.windowPos = { x : 0, y : 0, w : 0, h : 0, max : false };
  285. ideConfig.windowPos.max = maximized;
  286. if( !maximized ) {
  287. ideConfig.windowPos.x = window.x;
  288. ideConfig.windowPos.y = window.y;
  289. ideConfig.windowPos.w = Std.int(window.window.outerWidth);
  290. ideConfig.windowPos.h = Std.int(window.window.outerHeight);
  291. }
  292. if( subView == null )
  293. config.global.save();
  294. for (v in views)
  295. v.onResize();
  296. }
  297. function initLayout( ?state : { name : String, state : Config.LayoutState } ) {
  298. initializing = true;
  299. if( layout != null ) {
  300. layout.destroy();
  301. layout = null;
  302. }
  303. defaultLayout = null;
  304. var layoutName = isCDB ? "CDB" : "Default";
  305. var emptyLayout : Config.LayoutState = { content : [], fullScreen : null };
  306. for( p in projectConfig.layouts )
  307. if( p.name == layoutName ) {
  308. if( p.state.content == null ) continue; // old version
  309. defaultLayout = p;
  310. break;
  311. }
  312. if( defaultLayout == null ) {
  313. defaultLayout = { name : layoutName, state : emptyLayout };
  314. projectConfig.layouts.push(defaultLayout);
  315. config.current.sync();
  316. config.user.save();
  317. }
  318. if( state == null )
  319. state = defaultLayout;
  320. if( subView != null )
  321. state = { name : "SubView", state : emptyLayout };
  322. this.currentLayout = state;
  323. var config : golden.Config = {
  324. content: state.state.content,
  325. settings: {
  326. // Default to false
  327. reorderEnabled : config.user.get('layout.reorderEnabled', true) == true,
  328. constrainDragToHeader : config.user.get('layout.constrainDragToHeader', true) == true,
  329. showPopoutIcon : config.user.get('layout.showPopoutIcon') == true,
  330. showMaximiseIcon : config.user.get('layout.showMaximiseIcon') == true
  331. }
  332. };
  333. var comps = new Map();
  334. for( vcl in hide.ui.View.viewClasses )
  335. comps.set(vcl.name, true);
  336. function checkRec(i:golden.Config.ItemConfig) {
  337. if( i.componentName != null && !comps.exists(i.componentName) ) {
  338. i.componentState.deletedComponent = i.componentName;
  339. i.componentName = "hide.view.Unknown";
  340. }
  341. if( i.content != null ) for( i in i.content ) checkRec(i);
  342. }
  343. for( i in config.content ) checkRec(i);
  344. if (hideRoot != null) {
  345. hideRoot.remove();
  346. hideRoot = null;
  347. }
  348. hideRoot = new Element('<div class="hide-root"></div>').appendTo(new Element("body"));
  349. goldenContainer = new Element('<div class="golden-layout-root"></div>').appendTo(hideRoot);
  350. statusBar = new Element('<div class="status-bar"></div>').appendTo(hideRoot);
  351. statusIcons = new Element('<div id="status-icons"></div>').appendTo(statusBar);
  352. var commitHash = getGitCommitHashAndDate();
  353. if (commitHash.length > 0) {
  354. new Element('<span class="build">hide $commitHash</span>').appendTo(statusBar);
  355. }
  356. layout = new golden.Layout(config, goldenContainer.get(0));
  357. var resizeTimer : haxe.Timer = null;
  358. var observer = new hide.comp.ResizeObserver((elts, observer) -> {
  359. if (resizeTimer != null) {
  360. resizeTimer.stop();
  361. }
  362. resizeTimer = new haxe.Timer(20);
  363. resizeTimer.run = () -> {
  364. var rect = goldenContainer.get(0).getBoundingClientRect();
  365. layout.updateSize(Std.int(rect.width), Std.int(rect.height));
  366. resizeTimer.stop();
  367. resizeTimer = null;
  368. };
  369. });
  370. observer.observe(goldenContainer.get(0));
  371. var initViews = [];
  372. function initView(view:hide.ui.View<Dynamic>) {
  373. if( isDebugger ) view.rebuild() else try view.rebuild() catch( e : Dynamic ) error(view+":"+e);
  374. }
  375. for( vcl in hide.ui.View.viewClasses )
  376. layout.registerComponent(vcl.name,function(cont,state) {
  377. var view = Type.createInstance(vcl.cl,[state]);
  378. view.setContainer(cont);
  379. if( initializing )
  380. initViews.push(view);
  381. else
  382. initView(view);
  383. });
  384. layout.init();
  385. layout.on('stateChanged', onLayoutChanged);
  386. var waitCount = 0;
  387. function waitInit() {
  388. waitCount++;
  389. if( !layout.isInitialised ) {
  390. if( waitCount > 20 ) {
  391. // timeout : error recovery if invalid component
  392. state.state = emptyLayout;
  393. initLayout();
  394. return;
  395. }
  396. haxe.Timer.delay(waitInit, 50);
  397. return;
  398. }
  399. if( state.state.fullScreen != null ) {
  400. var fs = state.state.fullScreen;
  401. var found = [for( v in views ) if( v.viewClass == fs.name ) v];
  402. if( found.length == 1 )
  403. found[0].fullScreen = true;
  404. else {
  405. for( f in found )
  406. if( haxe.Json.stringify(f.state) == haxe.Json.stringify(fs.state) ) {
  407. f.fullScreen = true;
  408. break;
  409. }
  410. }
  411. }
  412. initializing = false;
  413. for( v in initViews )
  414. initView(v);
  415. initViews = null;
  416. if( subView == null && views.length == 0 ) {
  417. if( isCDB )
  418. open("hide.view.CdbTable",{}, function(v) v.fullScreen = true);
  419. else
  420. open("hide.view.FileTree",{path:""});
  421. }
  422. if( firstInit ) {
  423. firstInit = false;
  424. for( file in nw.App.argv ) {
  425. if( !sys.FileSystem.exists(file) ) continue;
  426. openFile(file);
  427. }
  428. if( subView != null )
  429. open(subView.component, subView.state);
  430. }
  431. };
  432. waitInit();
  433. hxd.System.setLoop(mainLoop);
  434. }
  435. function mainLoop() {
  436. hxd.Timer.update();
  437. @:privateAccess hxd.Pad.syncPads();
  438. for( f in updates )
  439. f();
  440. }
  441. public function setFullscreen(b : Bool) {
  442. if (b) {
  443. fullscreen = true;
  444. window.maximize();
  445. saveMenu = window.menu;
  446. window.menu = null;
  447. window.enterFullscreen();
  448. } else {
  449. window.menu = saveMenu;
  450. window.leaveFullscreen();
  451. // NWJS bug: changing fullscreen triggers spurious "restore" events
  452. haxe.Timer.delay(function() {
  453. fullscreen = false;
  454. if(maximized)
  455. window.maximize();
  456. }, 150);
  457. }
  458. }
  459. function set_currentFullScreen(v) {
  460. var old = currentFullScreen;
  461. currentFullScreen = v;
  462. if( old != null ) old.fullScreen = false;
  463. onLayoutChanged();
  464. return v;
  465. }
  466. function onLayoutChanged() {
  467. if( initializing || !ideConfig.autoSaveLayout || isCDB )
  468. return;
  469. defaultLayout.state = saveLayout();
  470. if( subView == null ) this.config.user.save();
  471. }
  472. function saveLayout() : Config.LayoutState {
  473. return {
  474. content : layout.toConfig().content,
  475. fullScreen : currentFullScreen == null ? null : { name : currentFullScreen.viewClass, state : currentFullScreen.state }
  476. };
  477. }
  478. public function setClipboard( data : String, type: nw.Clipboard.ClipboardType = Text ) {
  479. nw.Clipboard.get().set([{data: data, type: type }]);
  480. }
  481. public function setClipboardMultiple( datas: Array<nw.Clipboard.ClipboardData> ) {
  482. nw.Clipboard.get().set(datas);
  483. }
  484. public function getClipboard(type: nw.Clipboard.ClipboardType = Text) {
  485. return nw.Clipboard.get().get(type);
  486. }
  487. public function registerUpdate( updateFun ) {
  488. updates.push(updateFun);
  489. }
  490. public function unregisterUpdate( updateFun ) {
  491. for( u in updates )
  492. if( Reflect.compareMethods(u,updateFun) ) {
  493. updates.remove(u);
  494. return true;
  495. }
  496. return false;
  497. }
  498. public function makeSignature( content : String ) {
  499. var sign = js.node.Crypto.createHash(js.node.Crypto.CryptoAlgorithm.MD5);
  500. return sign.update(content).digest("base64");
  501. }
  502. public function cleanObject( v : Dynamic ) {
  503. for( f in Reflect.fields(v) )
  504. if( Reflect.field(v, f) == null )
  505. Reflect.deleteField(v, f);
  506. }
  507. static var textureCacheKey = "TextureCache";
  508. public function getHideResPath(basePath:String) {
  509. return getPath("${HIDE}/res/" + basePath);
  510. }
  511. // Get a texture from a file on disk. Cache the results
  512. public function getTexture(fullPath:String) {
  513. if (fullPath == null)
  514. return null;
  515. var engine = h3d.Engine.getCurrent();
  516. var cache : IdeCache = cast @:privateAccess engine.resCache.get(IdeCache);
  517. if(cache == null) {
  518. cache = new IdeCache();
  519. @:privateAccess engine.resCache.set(IdeCache, cache);
  520. }
  521. var texCache = cache.getTextureCache;
  522. var tex = texCache[fullPath];
  523. if (tex != null)
  524. return tex;
  525. var data = sys.io.File.getBytes(fullPath);
  526. var res = hxd.res.Any.fromBytes(fullPath, data);
  527. tex = res.toImage().toTexture();
  528. texCache.set(fullPath, tex);
  529. return tex;
  530. }
  531. var showErrors = true;
  532. var errorWindow :Element = null;
  533. override function error( e : Dynamic ) {
  534. if( showErrors ) {
  535. onIdeError(e);
  536. if( !js.Browser.window.confirm(e) )
  537. showErrors = false;
  538. }
  539. if (!showErrors) {
  540. if (errorWindow == null) {
  541. statusBar.toggleClass("error");
  542. errorWindow = new Element('<div class="error-suppressed">
  543. <button class="reload"><i class="icon ico ico-refresh"></i>Reload</button>
  544. <span>Errors are currently suppressed in the editor. Please save your work and reload.</span>
  545. </div>
  546. ');
  547. errorWindow.insertAfter(statusIcons);
  548. var btnSaveReload = errorWindow.find(".reload");
  549. btnSaveReload.click(function(_) {
  550. this.reload();
  551. });
  552. }
  553. }
  554. js.Browser.console.error(e);
  555. }
  556. public function quickError( msg : Dynamic, timeoutSeconds : Float = 5.0 ) {
  557. var str = StringTools.htmlEscape(Std.string(msg));
  558. str = StringTools.replace(str, "\n", "<br/>");
  559. var e = new Element('
  560. <div class="message error">
  561. <div class="icon ico ico-warning"></div>
  562. <div class="text">${str}</div>
  563. </div>');
  564. js.Browser.console.error(msg);
  565. globalMessage(e, timeoutSeconds);
  566. }
  567. function loadProject() {
  568. var dir = ideConfig.currentProject;
  569. setProgress();
  570. shaderLoader = new hide.tools.ShaderLoader();
  571. hxsl.Cache.clear();
  572. var localDir = sys.FileSystem.exists(resourceDir) ? resourceDir : projectDir;
  573. var fsconf = config.current.get("fs.config", "default");
  574. hxd.res.Loader.currentInstance = new CustomLoader(new hxd.fs.LocalFileSystem(localDir,fsconf));
  575. hxd.res.Image.ASYNC_LOADER = new hxd.impl.AsyncLoader.NodeLoader();
  576. renderers = [
  577. new hide.Renderer.MaterialSetup("Default"),
  578. new hide.Renderer.PbrSetup("PBR"),
  579. ];
  580. var plugins : Array<String> = config.current.get("plugins");
  581. for ( plugin in plugins )
  582. loadPlugin(plugin, function() {});
  583. loadDatabase();
  584. if( config.project.get("debug.displayErrors") ) {
  585. js.Browser.window.onerror = function(msg, url, line, col, error) {
  586. if( error == null ) return true; // some internal chrome errors are only a msg, skip
  587. var e = error.stack;
  588. e = ~/\(?chrome-extension:\/\/[a-z0-9\-\.\/]+.js:[0-9]+:[0-9]+\)?/g.replace(e,"");
  589. e = ~/at ([A-Za-z0-9_\.\$]+)/g.map(e,function(r) { var path = r.matched(1); path = path.split("$hx_exports.").pop().split("$hxClasses.").pop(); return path; });
  590. e = e.split("\t").join(" ");
  591. this.error(e);
  592. return true;
  593. };
  594. } else
  595. Reflect.deleteField(js.Browser.window, "onerror");
  596. waitScripts(function() {
  597. var extraRenderers = config.current.get("renderers");
  598. for( name in Reflect.fields(extraRenderers) ) {
  599. var clName = Reflect.field(extraRenderers, name);
  600. var cl = try js.Lib.eval(clName) catch( e : Dynamic ) null;
  601. if( cl == null ) {
  602. error(clName+" could not be found");
  603. return;
  604. }
  605. renderers.push(Type.createInstance(cl,[]));
  606. }
  607. var render = renderers[0];
  608. if( projectConfig.renderer == null )
  609. projectConfig.renderer = config.current.get("defaultRenderer");
  610. for( r in renderers ) {
  611. var name = r.displayName == null ? r.name : r.displayName;
  612. if( name == projectConfig.renderer ) {
  613. render = r;
  614. break;
  615. }
  616. }
  617. h3d.mat.MaterialSetup.current = render;
  618. initMenu();
  619. initLayout();
  620. });
  621. }
  622. function waitScripts( f : Void -> Void ) {
  623. if( !isScriptLoading() ) {
  624. f();
  625. return;
  626. }
  627. var wait = scripts.get("");
  628. if( wait == null ) {
  629. wait = [];
  630. scripts.set("",wait);
  631. }
  632. wait.push(f);
  633. }
  634. function isScriptLoading() {
  635. for( s in scripts.keys() )
  636. if( s != "" && scripts.get(s).length > 0 )
  637. return true;
  638. return false;
  639. }
  640. public function injectCss(data: String) {
  641. var head = new Element("head");
  642. var style = new Element("<style>").appendTo(head);
  643. style.text(data);
  644. }
  645. function loadPlugin( file : String, callb : Void -> Void, ?forceType : String ) {
  646. file = getPath(file);
  647. var wait = scripts.get(file);
  648. if( wait != null ) {
  649. if( wait.length == 0 )
  650. callb();
  651. else
  652. wait.push(callb);
  653. return;
  654. }
  655. wait = [callb];
  656. scripts.set(file, wait);
  657. function onLoad() {
  658. scripts.set(file, []);
  659. for( w in wait )
  660. w();
  661. if( !isScriptLoading() ) {
  662. wait = scripts.get("");
  663. scripts.set("",[]);
  664. for( w in wait ) w();
  665. }
  666. }
  667. function onError() {
  668. error("Error while loading "+file);
  669. }
  670. var type = forceType == null ? haxe.io.Path.extension(file).toLowerCase() : forceType;
  671. switch ( type ) {
  672. case "js":
  673. var e = js.Browser.document.createScriptElement();
  674. e.addEventListener("load", onLoad);
  675. e.addEventListener("error", onError);
  676. e.async = false;
  677. e.type = "text/javascript";
  678. e.src = "file://"+file.split("\\").join("/");
  679. js.Browser.document.body.appendChild(e);
  680. fileWatcher.register(file,reload);
  681. case "css":
  682. var e = js.Browser.document.createLinkElement();
  683. e.addEventListener("load", onLoad);
  684. e.addEventListener("error", onError);
  685. e.rel = "stylesheet";
  686. e.type = "text/css";
  687. e.href = "file://" + file.split("\\").join("/");
  688. js.Browser.document.body.appendChild(e);
  689. fileWatcher.register(file, () -> reloadCss());
  690. default: error('Unknown plugin type $type for file $file');
  691. }
  692. }
  693. inline function loadScript( file : String, callb : Void -> Void ) {
  694. loadPlugin(file, callb);
  695. }
  696. public function reload() {
  697. hasReloaded = true;
  698. fileWatcher.dispose();
  699. hide.view.RemoteConsoleView.onBeforeReload();
  700. js.Browser.location.reload();
  701. }
  702. public function reloadCss(path: String = null) {
  703. var css = new js.jquery.JQuery('link[type="text/css"]');
  704. css.each(function(i, e) : Void {
  705. var link : js.html.LinkElement = cast e;
  706. if (path == null || StringTools.contains(link.href, path)) {
  707. link.href = link.href + "?" + haxe.Timer.stamp();
  708. }
  709. });
  710. }
  711. public function getCDBContent<T>( sheetName : String ) : Array<T> {
  712. for( s in database.sheets )
  713. if( s.name == sheetName ) {
  714. var s = Reflect.copy(@:privateAccess s.realSheet.sheet);
  715. s.lines = [for( l in s.lines ) Reflect.copy(l)];
  716. @:privateAccess cdb.Types.Index.initLines(s);
  717. return cast s.lines;
  718. }
  719. return null;
  720. }
  721. public function getUnCachedUrl( path : String ) {
  722. return "file://" + getPath(path) + "?t=" + fileWatcher.getVersion(path);
  723. }
  724. public static var IMG_EXTS = ["jpg", "jpeg", "gif", "png", "raw", "dds", "hdr", "tga"];
  725. public function chooseImage( onSelect, allowNull=false ) {
  726. chooseFile(IMG_EXTS, onSelect, allowNull);
  727. }
  728. public function chooseFileOptions(onSelect: Null<Array<String>> -> Void, options: {
  729. ?exts : Array<String>,
  730. ?workingDir: String,
  731. ?onlyDirectory: Bool,
  732. ?saveAs: String,
  733. ?isAbsolute: Bool,
  734. ?allowNull: Bool,
  735. ?multiple: Bool,
  736. } = null) {
  737. options = options ?? {};
  738. function callback() {
  739. if (filePickerElement != null)
  740. filePickerElement.remove();
  741. var args : Array<String> = [];
  742. if (options.allowNull == true)
  743. args.push("data-allownull='true'");
  744. if (options.saveAs != null)
  745. args.push('nwsaveas="${options.saveAs}"');
  746. if (options.exts != null)
  747. args.push('accept="${[for( e in options.exts ) "."+e].join(",")}"');
  748. if (options.onlyDirectory == true)
  749. args.push("nwdirectory");
  750. if (options.multiple == true)
  751. args.push('multiple="multiple"');
  752. if (options.workingDir != null && options.workingDir != "#MISSING") {
  753. var pathArray = getPath(options.workingDir).split("/");
  754. var c = isWindows ? "\\" : "/";
  755. var workingDirPath = pathArray.join(c);
  756. args.push('nwworkingdir="$workingDirPath"');
  757. }
  758. var argsString = args.join(" ");
  759. var buildString = '<input type="file" style="visibility:hidden; position:fixed; top:0px;" value="" $argsString/>';
  760. filePickerElement = new Element(buildString).appendTo(window.window.document.body);
  761. filePickerElement.change(function(_) {
  762. var file = filePickerElement.val();
  763. filePickerElement.remove();
  764. filePickerElement = null;
  765. if( file == "" && !options.allowNull ) return;
  766. if (file == "") {
  767. onSelect(null);
  768. } else {
  769. var files = file.split(";");
  770. if (options.isAbsolute != true) {
  771. for (i => file in files) {
  772. files[i] = makeRelative(file);
  773. }
  774. }
  775. onSelect(files);
  776. }
  777. });
  778. filePickerElement.click();
  779. }
  780. if (options.allowNull) {
  781. haxe.Timer.delay(callback, 100);
  782. }
  783. else {
  784. callback();
  785. }
  786. }
  787. /**
  788. Adds an element to the ide status bar
  789. **/
  790. public function addStatusIcon(e: hide.Element) {
  791. function wait() {
  792. if( statusIcons == null ) {
  793. haxe.Timer.delay(wait, 10);
  794. return;
  795. }
  796. var wrapper = new hide.Element('<div class="statusbar-icon"></div>').appendTo(statusIcons);
  797. wrapper.append(e);
  798. }
  799. wait();
  800. }
  801. public function chooseFiles( exts : Array<String>, onSelect : Array<String> -> Void, allowNull=false ) {
  802. chooseFileOptions(onSelect, {exts: exts, allowNull: allowNull, multiple: true});
  803. }
  804. public function chooseFile( exts : Array<String>, onSelect : Null<String> -> Void, allowNull = false, workingdir:String = null) {
  805. chooseFileOptions((files) -> onSelect(files != null ? files.pop() : null), {exts: exts, allowNull: allowNull, workingDir: workingdir});
  806. }
  807. public function chooseFileSave( defaultPath : String, onSelect : String -> Void, allowNull=false ) {
  808. chooseFileOptions((files) -> onSelect(files != null ? files.pop() : null),{saveAs: defaultPath,allowNull: allowNull,});
  809. }
  810. public function chooseDirectory( onSelect : String -> Void, ?isAbsolute = false, allowNull=false ) {
  811. chooseFileOptions((files) -> onSelect(files != null ? files.pop() : null),{isAbsolute: isAbsolute,onlyDirectory: true,allowNull: allowNull,});
  812. }
  813. public function findPathRefs(path: String) {
  814. var refs : Array<{str: String, ?goto: () -> Void}> = [];
  815. function filter(ctx: FilterPathContext) {
  816. if (ctx.valueCurrent == path) {
  817. refs.push(ctx.getRef());
  818. }
  819. }
  820. filterPaths(filter);
  821. open("hide.view.RefViewer", null, null, function(view) {
  822. var refViewer : hide.view.RefViewer = cast view;
  823. refViewer.showRefs(refs, 'Number of references to "$path"');
  824. });
  825. }
  826. /**
  827. Iterate throught all the strings in the project that could contain a path, replacing
  828. the value by what `callb` returns. The callb must call `changed()` if it changed the path.
  829. **/
  830. public function filterPaths(callb: (ctx : FilterPathContext) -> Void) {
  831. var context = new FilterPathContext(callb);
  832. var adaptedFilter = function(obj: String) {
  833. return context.filter(obj);
  834. }
  835. function filterContent(content:Dynamic) {
  836. var visited = new Map<Dynamic, Bool>();
  837. function browseRec(obj:Dynamic) : Dynamic {
  838. switch( Type.typeof(obj) ) {
  839. case TObject:
  840. if( visited.exists(obj)) return null;
  841. visited.set(obj, true);
  842. for( f in Reflect.fields(obj) ) {
  843. var v : Dynamic = Reflect.field(obj, f);
  844. v = browseRec(v);
  845. if( v != null ) Reflect.setField(obj, f, v);
  846. }
  847. case TClass(Array):
  848. if( visited.exists(obj)) return null;
  849. visited.set(obj, true);
  850. var arr : Array<Dynamic> = obj;
  851. for( i in 0...arr.length ) {
  852. var v : Dynamic = arr[i];
  853. v = browseRec(v);
  854. if( v != null ) arr[i] = v;
  855. }
  856. case TClass(String):
  857. return context.filter(obj);
  858. default:
  859. }
  860. return null;
  861. }
  862. for( f in Reflect.fields(content) ) {
  863. if (f == "children")
  864. continue;
  865. var v = browseRec(Reflect.field(content,f));
  866. if( v != null ) Reflect.setField(content,f,v);
  867. }
  868. }
  869. {
  870. var currentPath : String = null;
  871. var currentPrefab: hrt.prefab.Prefab = null;
  872. context.getRef = () -> {
  873. var p = currentPath; // needed capture
  874. var cp = currentPrefab; // needed capture
  875. return {str: '$p:${cp.getAbsPath()}', goto: () -> openFile(getPath(p), null, (view) -> {
  876. var pref = Std.downcast(view, hide.view.Prefab);
  877. if (pref != null) {
  878. pref.delaySceneEditor(() -> {
  879. pref.sceneEditor.selectElementsIndirect([cp]);
  880. });
  881. }
  882. else {
  883. var fx = Std.downcast(view, hide.view.FXEditor);
  884. fx.delaySceneEditor(() -> {
  885. @:privateAccess fx.sceneEditor.selectElementsIndirect([cp]);
  886. });
  887. }
  888. })};
  889. };
  890. filterPrefabs(function(p:hrt.prefab.Prefab, path: String) {
  891. context.changed = false;
  892. currentPath = path;
  893. currentPrefab = p;
  894. p.source = context.filter(p.source);
  895. var h = p.getHideProps();
  896. if( h.onResourceRenamed != null )
  897. h.onResourceRenamed(adaptedFilter);
  898. else {
  899. filterContent(p);
  900. }
  901. return context.changed;
  902. });
  903. }
  904. {
  905. var currentPath : String = null;
  906. context.getRef = () -> {
  907. var p = currentPath;
  908. return {str: p, goto : Ide.showFileInExplorer.bind(getPath(p))};
  909. }
  910. filterProps(function(content:Dynamic, path: String) {
  911. context.changed = false;
  912. currentPath = path;
  913. filterContent(content);
  914. return context.changed;
  915. });
  916. }
  917. context.changed = false;
  918. var tmpSheets = [];
  919. var currentSheet : cdb.Sheet = null;
  920. var currentColumn : String = null;
  921. var currentObject : Dynamic = null;
  922. context.getRef = () -> {
  923. var cs = currentSheet;
  924. var cc = currentColumn;
  925. var sheets = cdb.Sheet.getSheetPath(cs, cc);
  926. var path = hide.comp.cdb.Editor.splitPath({s: sheets, o: currentObject});
  927. return {str: sheets[0].s.name+"."+path.pathNames.join("."), goto: hide.comp.cdb.Editor.openReference2.bind(sheets[0].s, path.pathParts)};
  928. };
  929. for( sheet in database.sheets ) {
  930. if( sheet.props.dataFiles != null && sheet.lines == null ) {
  931. // we already updated prefabs, no need to load data files
  932. tmpSheets.push(sheet);
  933. @:privateAccess sheet.sheet.lines = [];
  934. }
  935. for( c in sheet.columns ) {
  936. switch( c.type ) {
  937. case TFile:
  938. var sheets = cdb.Sheet.getSheetPath(sheet, c.name);
  939. for( obj in sheet.getObjects() ) {
  940. currentSheet = sheet;
  941. currentColumn = c.name;
  942. currentObject = obj;
  943. var path = Reflect.field(obj.path[obj.path.length - 1], c.name);
  944. var v : Dynamic = context.filter(path);
  945. if( v != null ) Reflect.setField(obj.path[obj.path.length - 1], c.name, v);
  946. }
  947. case TTilePos:
  948. var sheets = cdb.Sheet.getSheetPath(sheet, c.name);
  949. for( obj in sheet.getObjects() ) {
  950. currentSheet = sheet;
  951. currentColumn = c.name;
  952. currentObject = obj;
  953. var tilePos : cdb.Types.TilePos = Reflect.field(obj.path[obj.path.length - 1], c.name);
  954. if (tilePos != null) {
  955. var v : Dynamic = context.filter(tilePos.file);
  956. if (v != null) Reflect.setField(tilePos, 'file', v);
  957. }
  958. }
  959. default:
  960. }
  961. }
  962. }
  963. if( context.changed ) {
  964. saveDatabase();
  965. hide.comp.cdb.Editor.refreshAll(true);
  966. }
  967. for( sheet in tmpSheets )
  968. @:privateAccess sheet.sheet.lines = null;
  969. for (customFilter in customFilepathRefFilters) {
  970. customFilter(context);
  971. }
  972. }
  973. public var customFilepathRefFilters : Array<(ctx : FilterPathContext) -> Void> = [];
  974. public function refreshFont() {
  975. var font = ideConfig.useAlternateFont ? "Verdana" : "Inter";
  976. var size = ideConfig.useAlternateFont ? "9pt" : "9.5pt";
  977. js.Browser.document.documentElement.style.setProperty("--default-font", font);
  978. js.Browser.document.documentElement.style.setProperty("--default-font-size", size);
  979. }
  980. public function filterPrefabs( callb : (hrt.prefab.Prefab, path: String) -> Bool) {
  981. var exts = Lambda.array({iterator : @:privateAccess hrt.prefab.Prefab.extensionRegistry.keys });
  982. exts.push("prefab");
  983. var todo = [];
  984. browseFiles(function(path) {
  985. var ext = path.split(".").pop();
  986. if( exts.indexOf(ext) < 0 ) return;
  987. var prefab = loadPrefab(path);
  988. var changed = false;
  989. function filterRec(p) {
  990. if( callb(p, path) ) changed = true;
  991. for( ps in p.children )
  992. filterRec(ps);
  993. }
  994. filterRec(prefab);
  995. if( !changed ) return;
  996. @:privateAccess todo.push(function() sys.io.File.saveContent(getPath(path), toJSON(prefab.serialize())));
  997. });
  998. for( t in todo )
  999. t();
  1000. }
  1001. public function filterProps( callb : (data: Dynamic, path: String) -> Bool ) {
  1002. var exts = ["props", "json"];
  1003. var todo = [];
  1004. browseFiles(function(path) {
  1005. var ext = path.split(".").pop();
  1006. if( exts.indexOf(ext) < 0 ) return;
  1007. try {
  1008. var content = parseJSON(sys.io.File.getContent(getPath(path)));
  1009. var changed = callb(content, path);
  1010. if( !changed ) return;
  1011. todo.push(function() sys.io.File.saveContent(getPath(path), toJSON(content)));
  1012. } catch (e) {};
  1013. });
  1014. for( t in todo )
  1015. t();
  1016. }
  1017. function browseFiles( callb : String -> Void ) {
  1018. function browseRec(path) {
  1019. if( path == ".tmp" ) return;
  1020. for( p in sys.FileSystem.readDirectory(resourceDir + "/" + path) ) {
  1021. var p = path == "" ? p : path + "/" + p;
  1022. if( sys.FileSystem.isDirectory(resourceDir+"/"+p) ) {
  1023. browseRec(p);
  1024. continue;
  1025. }
  1026. callb(p);
  1027. }
  1028. }
  1029. browseRec("");
  1030. }
  1031. public function setProgress( ?text : String ) {
  1032. if( text != null ) {
  1033. window.title = text;
  1034. return;
  1035. }
  1036. var title = config.current.get("hide.windowTitle");
  1037. window.title = title != null ? title : ((isCDB ? "CastleDB" : "HIDE") + " - " + projectDir);
  1038. }
  1039. public function runCommand(cmd, ?callb:String->Void) {
  1040. var c = cmd.split("%PROJECTDIR%").join(projectDir);
  1041. var slash = isWindows ? "\\" : "/";
  1042. c = c.split("/").join(slash);
  1043. js.node.ChildProcess.exec(c, function(e:js.node.ChildProcess.ChildProcessExecError,_,_) callb(e == null ? null : e.message));
  1044. }
  1045. public function addCustomMenu(item: nw.MenuItem) {
  1046. customMenus.push(item);
  1047. }
  1048. public function initMenu() {
  1049. if( subView != null ) return;
  1050. var menuHTML = "<content>"+new Element("#mainmenu").html() + config.project.get("menu.extra")+"</content>";
  1051. var menu = new Element(menuHTML);
  1052. // project
  1053. if( ideConfig.recentProjects.length > 0 )
  1054. menu.find(".project .recents").html("");
  1055. for( v in ideConfig.recentProjects.copy() ) {
  1056. if( !sys.FileSystem.exists(v) ) {
  1057. ideConfig.recentProjects.remove(v);
  1058. config.global.save();
  1059. continue;
  1060. }
  1061. new Element("<menu>").attr("label",v).appendTo(menu.find(".project .recents")).click(function(_){
  1062. var dir = v;
  1063. setProject(dir);
  1064. reload(); // Reload stylesheets
  1065. });
  1066. }
  1067. menu.find(".project .open").click(function(_) {
  1068. chooseDirectory(function(dir) {
  1069. if( dir == null ) return;
  1070. if( StringTools.endsWith(dir,"/res") || StringTools.endsWith(dir,"\\res") )
  1071. dir = dir.substr(0,-4);
  1072. setProject(dir);
  1073. reload();
  1074. }, true);
  1075. });
  1076. menu.find(".project .clear").click(function(_) {
  1077. ideConfig.recentProjects = [];
  1078. config.global.save();
  1079. initMenu();
  1080. });
  1081. menu.find(".project .exit").click(function(_) {
  1082. Sys.exit(0);
  1083. });
  1084. menu.find(".project .clear-local").click(function(_) {
  1085. js.Browser.window.localStorage.clear();
  1086. nw.App.clearCache();
  1087. try sys.FileSystem.deleteFile(Ide.inst.appPath + "/props.json") catch( e : Dynamic ) {};
  1088. untyped chrome.runtime.reload();
  1089. });
  1090. menu.find(".build-files").click(function(_) {
  1091. hrt.impl.BuildTools.buildAllFiles(resourceDir + "/", function(percent, currentFile) {
  1092. setProgress('($percent%) $currentFile');
  1093. }, function(msg) {
  1094. error(msg);
  1095. }, function(count, errCount) {
  1096. setProgress();
  1097. });
  1098. });
  1099. for( r in renderers ) {
  1100. var name = r.displayName != null ? r.displayName : r.name;
  1101. new Element("<menu type='checkbox'>").attr("label", name).prop("checked",r == h3d.mat.MaterialSetup.current).appendTo(menu.find(".project .renderers")).click(function(_) {
  1102. if( r != h3d.mat.MaterialSetup.current ) {
  1103. projectConfig.renderer = name;
  1104. config.user.save();
  1105. reload();
  1106. }
  1107. });
  1108. }
  1109. // view
  1110. if( !sys.FileSystem.exists(resourceDir) )
  1111. menu.find(".view").remove();
  1112. menu.find(".debug").click(function(_) window.showDevTools());
  1113. var comps = menu.find("[component]");
  1114. for( c in comps.elements() ) {
  1115. var cname = c.attr("component");
  1116. var cl = Type.resolveClass(cname);
  1117. if( cl == null ) error("Missing component class "+cname);
  1118. var state = c.attr("state");
  1119. if( state != null ) try haxe.Json.parse(state) catch( e : Dynamic ) error("Invalid state "+state+" ("+e+")");
  1120. c.click(function(_) {
  1121. open(cname, state == null ? null : haxe.Json.parse(state));
  1122. });
  1123. }
  1124. // database
  1125. var db = menu.find(".database");
  1126. db.find(".dbView").click(function(_) {
  1127. open("hide.view.CdbTable",{});
  1128. });
  1129. db.find(".dbCompress").prop("checked",database.compress).click(function(_) {
  1130. database.compress = !database.compress;
  1131. saveDatabase();
  1132. });
  1133. db.find(".dbExport").click(function(_) {
  1134. hide.comp.cdb.DataFiles.load();
  1135. var lang = new cdb.Lang(@:privateAccess database.data);
  1136. var xml = lang.buildXML();
  1137. xml = String.fromCharCode(0xFEFF) + xml; // prefix with BOM
  1138. chooseFileSave("export.xml", function(f) {
  1139. if( f != null ) sys.io.File.saveContent(getPath(f), xml);
  1140. });
  1141. });
  1142. db.find(".dbImport").click(function(_) {
  1143. chooseFile(["xml"], function(file) {
  1144. hide.comp.cdb.DataFiles.load();
  1145. var lang = new cdb.Lang(@:privateAccess database.data);
  1146. var xml = sys.io.File.getContent(getPath(file));
  1147. lang.apply(xml);
  1148. saveDatabase(true);
  1149. for( file in @:privateAccess hide.comp.cdb.DataFiles.watching.keys() ) {
  1150. if( sys.FileSystem.isDirectory(getPath(file)) )
  1151. continue;
  1152. var p = loadPrefab(file);
  1153. lang.applyPrefab(p);
  1154. savePrefab(file, p);
  1155. }
  1156. hide.comp.cdb.Editor.refreshAll();
  1157. message("Import completed");
  1158. });
  1159. });
  1160. var proofing = projectConfig.dbProofread == true;
  1161. db.find(".dbProofread").prop("checked", proofing).click(function(_) {
  1162. projectConfig.dbProofread = !proofing;
  1163. config.global.save();
  1164. for( v in getViews(hide.view.CdbTable) )
  1165. v.applyProofing();
  1166. initMenu();
  1167. });
  1168. function setDiff(f) {
  1169. databaseDiff = f;
  1170. config.user.set("cdb.databaseDiff", f);
  1171. config.user.save();
  1172. loadDatabase();
  1173. hide.comp.cdb.Editor.refreshAll();
  1174. initMenu();
  1175. for( v in getViews(hide.view.CdbTable) )
  1176. v.rebuild();
  1177. }
  1178. db.find(".dbCreateDiff").click(function(_) {
  1179. chooseFileSave("cdb.diff", function(name) {
  1180. if( name == null ) return;
  1181. if( name.indexOf(".") < 0 ) name += ".diff";
  1182. sys.io.File.saveContent(getPath(name),"{}");
  1183. setDiff(name);
  1184. });
  1185. });
  1186. db.find(".dbLoadDiff").click(function(_) {
  1187. chooseFile(["diff"], function(f) {
  1188. if( f == null ) return;
  1189. setDiff(f);
  1190. });
  1191. });
  1192. db.find(".dbCloseDiff").click(function(_) {
  1193. setDiff(null);
  1194. }).attr("disabled", databaseDiff == null ? "disabled" : null);
  1195. db.find(".dbCustom").click(function(_) {
  1196. open("hide.view.CdbCustomTypes",{});
  1197. });
  1198. db.find(".dbFormulas").click(function(_) {
  1199. open("hide.comp.cdb.FormulasView",{ path : config.current.get("cdb.formulasFile") });
  1200. });
  1201. // Categories
  1202. {
  1203. function applyCategories() {
  1204. for( v in getViews(hide.view.CdbTable) )
  1205. v.applyCategories(projectConfig.dbCategories);
  1206. initMenu();
  1207. }
  1208. var allCats = hide.comp.cdb.Editor.getCategories(database);
  1209. var showAll = db.find(".dbCatShowAll");
  1210. for(cat in allCats) {
  1211. var isShown = projectConfig.dbCategories == null || projectConfig.dbCategories.indexOf(cat) >= 0;
  1212. new Element("<menu type='checkbox'>").attr("label",cat).prop("checked", isShown).insertBefore(showAll).click(function(_){
  1213. if(projectConfig.dbCategories == null)
  1214. projectConfig.dbCategories = allCats; // Init with all cats
  1215. if(isShown)
  1216. projectConfig.dbCategories.remove(cat);
  1217. else
  1218. projectConfig.dbCategories.push(cat);
  1219. config.user.save();
  1220. applyCategories();
  1221. });
  1222. }
  1223. new Element("<separator>").insertBefore(showAll);
  1224. db.find(".dbCatShowAll").click(function(_) {
  1225. projectConfig.dbCategories = null;
  1226. config.user.save();
  1227. applyCategories();
  1228. });
  1229. db.find(".dbCatHideAll").click(function(_) {
  1230. projectConfig.dbCategories = [];
  1231. config.user.save();
  1232. applyCategories();
  1233. });
  1234. }
  1235. // layout
  1236. var layouts = menu.find(".layout .content");
  1237. layouts.html("");
  1238. if(projectConfig.layouts == null)
  1239. projectConfig.layouts = [];
  1240. for( l in projectConfig.layouts ) {
  1241. if( l.name == "Default" ) continue;
  1242. new Element("<menu>").attr("label",l.name).addClass(l.name).appendTo(layouts).click(function(_) {
  1243. initLayout(l);
  1244. });
  1245. }
  1246. menu.find(".layout .autosave").click(function(_) {
  1247. ideConfig.autoSaveLayout = !ideConfig.autoSaveLayout;
  1248. config.global.save();
  1249. }).prop("checked",ideConfig.autoSaveLayout);
  1250. menu.find(".layout .saveas").click(function(_) {
  1251. var name = ask("Please enter a layout name:");
  1252. if( name == null || name == "" ) return;
  1253. projectConfig.layouts.push({ name : name, state : saveLayout() });
  1254. config.user.save();
  1255. initMenu();
  1256. });
  1257. menu.find(".layout .save").click(function(_) {
  1258. currentLayout.state = saveLayout();
  1259. config.global.save();
  1260. });
  1261. // analysis
  1262. var analysis = menu.find(".analysis");
  1263. analysis.find(".memprof").click(function(_) {
  1264. #if (hashlink >= "1.15.0")
  1265. open("hide.view.MemProfiler",{});
  1266. #else
  1267. quickMessage("Memory Profiler not available. Please update hashlink to version 1.15.0 or later.");
  1268. #end
  1269. });
  1270. analysis.find(".remoteconsole").click(function(_) {
  1271. open("hide.view.RemoteConsoleView",{});
  1272. });
  1273. analysis.find(".devtools").click(function(_) {
  1274. open("hide.view.DevTools",{});
  1275. });
  1276. analysis.find(".gpudump").click(function(_) {
  1277. var path = hide.tools.MemDump.gpudump();
  1278. quickMessage('Gpu mem dumped at ${path}.');
  1279. });
  1280. var settings = menu.find(".settings");
  1281. settings.find('.user-settings').click(function(_) {
  1282. open("hide.view.settings.UserSettings", {});
  1283. });
  1284. settings.find('.project-settings').click(function(_) {
  1285. open("hide.view.settings.ProjectSettings", {});
  1286. });
  1287. var finalMenu = new hide.ui.Menu(menu).root;
  1288. for (custom in customMenus) {
  1289. finalMenu.append(custom);
  1290. }
  1291. window.menu = finalMenu;
  1292. }
  1293. public function showFileInResources(path: String) {
  1294. var filetree = getViews(hide.view.FileTree)[0];
  1295. if( filetree != null) {
  1296. if (@:privateAccess filetree.tree == null)
  1297. filetree.onDisplay();
  1298. filetree.activate();
  1299. filetree.revealNode(path);
  1300. }
  1301. }
  1302. public static function showFileInExplorer(path : String) {
  1303. if(!haxe.io.Path.isAbsolute(path)) {
  1304. path = Ide.inst.getPath(path);
  1305. }
  1306. switch(Sys.systemName()) {
  1307. case "Windows": {
  1308. var cmd = "explorer.exe /select," + '"' + StringTools.replace(path, "/", "\\") + '"';
  1309. trace("OpenInExplorer: " + cmd);
  1310. Sys.command(cmd);
  1311. };
  1312. case "Mac": Sys.command("open " + haxe.io.Path.directory(path));
  1313. default: throw "Exploration not implemented on this platform";
  1314. }
  1315. }
  1316. public function openFile( file : String, ?onCreate, ?onOpen) {
  1317. var ext = @:privateAccess hide.view.FileTree.getExtension(file);
  1318. if( ext == null ) return;
  1319. // look if already open
  1320. var path = makeRelative(file);
  1321. for( v in views )
  1322. if( Type.getClassName(Type.getClass(v)) == ext.component && v.state.path == path ) {
  1323. if( v.container.tab != null ) {
  1324. v.container.parent.parent.setActiveContentItem(v.container.parent);
  1325. if (onOpen != null ) onOpen(v);
  1326. }
  1327. return;
  1328. }
  1329. open(ext.component, { path : path }, onCreate, onOpen);
  1330. }
  1331. public function openSubView<T>( component : Class<hide.ui.View<T>>, state : T, events : {} ) {
  1332. var sharedRefs : Map<Int,Dynamic> = untyped global.sharedRefs;
  1333. if( sharedRefs == null ) {
  1334. sharedRefs = new Map();
  1335. untyped global.sharedRefs = sharedRefs;
  1336. }
  1337. var id = 0;
  1338. while( sharedRefs.exists(id) ) id++;
  1339. sharedRefs.set(id,{ state : state, events : events });
  1340. var compName = Type.getClassName(component);
  1341. nw.Window.open("app.html?subView="+compName+"&sid="+id,{ id : compName });
  1342. }
  1343. public function callParentView( name : String, param : Dynamic ) {
  1344. if( subView != null ) Reflect.callMethod(subView.events,Reflect.field(subView.events,name),[param]);
  1345. }
  1346. public function open( component : String, state : Dynamic, ?onCreate : hide.ui.View<Dynamic> -> Void, ?onOpen : hide.ui.View<Dynamic> -> Void ) {
  1347. if( state == null ) state = {};
  1348. var c = hide.ui.View.viewClasses.get(component);
  1349. if( c == null )
  1350. throw "Unknown component " + component;
  1351. state.componentName = component;
  1352. for( v in views ) {
  1353. if( v.viewClass == component && haxe.Json.stringify(v.state) == haxe.Json.stringify(state) ) {
  1354. v.activate();
  1355. if( onCreate != null ) onCreate(v);
  1356. if ( onOpen != null ) onOpen(v);
  1357. return;
  1358. }
  1359. }
  1360. var options = c.options;
  1361. var bestTarget : golden.Container = null;
  1362. for( v in views )
  1363. if( v.defaultOptions.position == options.position ) {
  1364. if( bestTarget == null || bestTarget.width * bestTarget.height < v.container.width * v.container.height )
  1365. bestTarget = v.container;
  1366. }
  1367. var index : Null<Int> = null;
  1368. var target;
  1369. if( bestTarget != null )
  1370. target = bestTarget.parent.parent;
  1371. else {
  1372. target = layout.root.contentItems[0];
  1373. if( target == null ) {
  1374. layout.root.addChild({ type : Row, isClosable: false });
  1375. target = layout.root.contentItems[0];
  1376. }
  1377. target.config.isClosable = false;
  1378. }
  1379. var needResize = options.width != null;
  1380. target.on("componentCreated", function(c) {
  1381. target.off("componentCreated");
  1382. var view : hide.ui.View<Dynamic> = untyped c.origin.__view;
  1383. if( onCreate != null ) onCreate(view);
  1384. if ( onOpen != null ) onOpen(view);
  1385. if( needResize ) {
  1386. // when opening restricted size after free size
  1387. haxe.Timer.delay(function() {
  1388. view.container.setSize(options.width, view.container.height);
  1389. },0);
  1390. } else {
  1391. // when opening free size after restricted size
  1392. var v0 = views[0];
  1393. if( views.length == 2 && views[1] == view && v0.defaultOptions.width != null )
  1394. haxe.Timer.delay(function() {
  1395. v0.container.setSize(v0.defaultOptions.width, v0.container.height);
  1396. },0);
  1397. }
  1398. });
  1399. var config : golden.Config.ItemConfig = {
  1400. type : Component,
  1401. componentName : component,
  1402. componentState : state
  1403. };
  1404. if( options.position == Left ) index = 0;
  1405. if( index == null )
  1406. target.addChild(config);
  1407. else
  1408. target.addChild(config, index);
  1409. }
  1410. public function reopenLastClosedTab() {
  1411. var state = lastClosedTabStates.pop();
  1412. if( state != null && state.componentName != null ) {
  1413. open(state.componentName, state);
  1414. }
  1415. }
  1416. public function globalMessage(element: Element, timeoutSeconds : Float = 5.0) {
  1417. var body = new Element('body');
  1418. var messages = body.find("#message-container");
  1419. if (messages.length == 0) {
  1420. messages = new Element('<div id="message-container"></div>');
  1421. body.append(messages);
  1422. }
  1423. messages.append(element);
  1424. // envie de prendre le raccourci vers le rez de chaussée la
  1425. haxe.Timer.delay(() -> {
  1426. element.addClass("show");
  1427. }, 10);
  1428. if (timeoutSeconds > 0.0) {
  1429. haxe.Timer.delay(() -> {
  1430. element.get(0).ontransitionend = function(_){
  1431. element.remove();
  1432. };
  1433. element.removeClass("show");
  1434. }, Std.int(timeoutSeconds * 1000.0));
  1435. }
  1436. }
  1437. public function quickMessage( text : String, timeoutSeconds : Float = 5.0 ) {
  1438. var str = StringTools.htmlEscape(text);
  1439. str = StringTools.replace(str, "\n", "<br/>");
  1440. var e = new Element('
  1441. <div class="message">
  1442. <div class="icon ico ico-info-circle"></div>
  1443. <div class="text">${str}</div>
  1444. </div>');
  1445. js.Browser.console.log(text);
  1446. globalMessage(e, timeoutSeconds);
  1447. }
  1448. public function message( text : String ) {
  1449. js.Browser.window.alert(text);
  1450. }
  1451. public function confirm( text : String ) {
  1452. return js.Browser.window.confirm(text);
  1453. }
  1454. public function ask( text : String, ?defaultValue = "" ) {
  1455. return js.Browser.window.prompt(text, defaultValue);
  1456. }
  1457. public static dynamic function onIdeError(e: Dynamic) {}
  1458. public static var inst : Ide;
  1459. static function main() {
  1460. h3d.impl.RenderContext.STRICT = false; // prevent errors with bad renderer
  1461. new Ide();
  1462. }
  1463. public static function getGitCommitHashAndDate():String {
  1464. // Check if there is changes in git. If thats the case, we are certainly on a dev machine
  1465. var out = "";
  1466. try {
  1467. out = js.node.ChildProcess.execSync('git status --porcelain=v1');
  1468. } catch (_) {
  1469. return "";
  1470. }
  1471. if (out.length > 0) {
  1472. return "dev";
  1473. }
  1474. try {
  1475. out = js.node.ChildProcess.execSync('git log --pretty=format:"%h(%cs)" -n 1');
  1476. } catch (_) {
  1477. return "";
  1478. }
  1479. return out;
  1480. }
  1481. }
  1482. class CustomLoader extends hxd.res.Loader {
  1483. var pathKeys = new Map<String,{}>();
  1484. function getKey( path : String ) {
  1485. var k = pathKeys.get(path);
  1486. if( k == null ) {
  1487. k = {};
  1488. pathKeys.set(path, k);
  1489. }
  1490. return k;
  1491. }
  1492. override function loadCache<T:hxd.res.Resource>( path : String, c : Class<T> ) : T {
  1493. var engine = h3d.Engine.getCurrent();
  1494. var i = Std.downcast(@:privateAccess engine.resCache.get(getKey(path)), c);
  1495. if( i == null ) {
  1496. i = Type.createInstance(c, [fs.get(path)]);
  1497. // i = new hxd.res.Image(fs.get(path));
  1498. @:privateAccess engine.resCache.set(getKey(path), i);
  1499. }
  1500. return i;
  1501. }
  1502. }
  1503. @:allow(hide.Ide)
  1504. class FilterPathContext {
  1505. public var valueCurrent: String;
  1506. var valueChanged: String;
  1507. public var filterFn: (FilterPathContext) -> Void;
  1508. var changed = false;
  1509. public function new(filterFn: (FilterPathContext) -> Void) {
  1510. this.filterFn = filterFn;
  1511. };
  1512. public function change(newValue) : Void {
  1513. changed = true;
  1514. valueChanged = newValue;
  1515. }
  1516. public function filter(valueCurrent: String) {
  1517. this.valueCurrent = valueCurrent;
  1518. valueChanged = null;
  1519. filterFn(this);
  1520. return changed ? valueChanged : valueCurrent;
  1521. }
  1522. public var getRef : () -> {str: String, ?goto: () -> Void};
  1523. }