level_editor.vala 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908
  1. /*
  2. * Copyright (c) 2012-2020 Daniele Bartolini and individual contributors.
  3. * License: https://github.com/dbartolini/crown/blob/master/LICENSE
  4. */
  5. using Gdk; // Pixbuf
  6. using Gee;
  7. using Gtk;
  8. namespace Crown
  9. {
  10. const int WINDOW_DEFAULT_WIDTH = 1280;
  11. const int WINDOW_DEFAULT_HEIGHT = 720;
  12. public class LevelEditorWindow : Gtk.ApplicationWindow
  13. {
  14. private const GLib.ActionEntry[] action_entries =
  15. {
  16. { "fullscreen", on_fullscreen, null, null }
  17. };
  18. public bool _fullscreen;
  19. public LevelEditorWindow(Gtk.Application app)
  20. {
  21. Object(application: app);
  22. this.add_action_entries(action_entries, this);
  23. this.title = "Level Editor";
  24. this.key_press_event.connect(this.on_key_press);
  25. this.key_release_event.connect(this.on_key_release);
  26. this.window_state_event.connect(this.on_window_state_event);
  27. this.show.connect(this.on_show);
  28. this.delete_event.connect(this.on_delete_event);
  29. this.set_default_size(WINDOW_DEFAULT_WIDTH, WINDOW_DEFAULT_HEIGHT);
  30. this.focus_out_event.connect(this.on_focus_out);
  31. _fullscreen = false;
  32. }
  33. private void on_fullscreen(GLib.SimpleAction action, GLib.Variant? param)
  34. {
  35. if (_fullscreen)
  36. unfullscreen();
  37. else
  38. fullscreen();
  39. }
  40. private bool on_key_press(Gdk.EventKey ev)
  41. {
  42. LevelEditorApplication app = (LevelEditorApplication)application;
  43. if (ev.keyval == Gdk.Key.Control_L)
  44. app._editor.send_script(LevelEditorApi.key_down("ctrl_left"));
  45. else if (ev.keyval == Gdk.Key.Shift_L)
  46. app._editor.send_script(LevelEditorApi.key_down("shift_left"));
  47. else if (ev.keyval == Gdk.Key.Alt_L)
  48. app._editor.send_script(LevelEditorApi.key_down("alt_left"));
  49. return false;
  50. }
  51. private bool on_key_release(Gdk.EventKey ev)
  52. {
  53. LevelEditorApplication app = (LevelEditorApplication)application;
  54. if (ev.keyval == Gdk.Key.Control_L)
  55. app._editor.send_script(LevelEditorApi.key_up("ctrl_left"));
  56. else if (ev.keyval == Gdk.Key.Shift_L)
  57. app._editor.send_script(LevelEditorApi.key_up("shift_left"));
  58. else if (ev.keyval == Gdk.Key.Alt_L)
  59. app._editor.send_script(LevelEditorApi.key_up("alt_left"));
  60. return false;
  61. }
  62. private bool on_window_state_event(EventWindowState ev)
  63. {
  64. _fullscreen = (ev.new_window_state & WindowState.FULLSCREEN) != 0;
  65. return true;
  66. }
  67. private void on_show()
  68. {
  69. this.maximize();
  70. }
  71. private bool on_delete_event()
  72. {
  73. LevelEditorApplication app = (LevelEditorApplication)application;
  74. if (app.should_quit())
  75. {
  76. app.close_all();
  77. return false; // Quit application
  78. }
  79. return true; // Keep alive
  80. }
  81. private bool on_focus_out(Gdk.EventFocus ev)
  82. {
  83. LevelEditorApplication app = (LevelEditorApplication)application;
  84. app._editor.send_script(LevelEditorApi.key_up("ctrl_left"));
  85. app._editor.send_script(LevelEditorApi.key_up("shift_left"));
  86. app._editor.send_script(LevelEditorApi.key_up("alt_left"));
  87. return true;
  88. }
  89. }
  90. public enum ToolType
  91. {
  92. PLACE,
  93. MOVE,
  94. ROTATE,
  95. SCALE
  96. }
  97. public enum SnapMode
  98. {
  99. RELATIVE,
  100. ABSOLUTE
  101. }
  102. public enum ReferenceSystem
  103. {
  104. LOCAL,
  105. WORLD
  106. }
  107. public enum StartGame
  108. {
  109. NORMAL,
  110. TEST
  111. }
  112. public class LevelEditorApplication : Gtk.Application
  113. {
  114. // Constants
  115. private const GLib.ActionEntry[] action_entries =
  116. {
  117. // parameter type
  118. // name activate() | state
  119. // | | | |
  120. { "menu-file", null, null, null },
  121. { "new-level", on_new_level, null, null },
  122. { "open-level", on_open_level, "s", null },
  123. { "open-project", on_open_project, null, null },
  124. { "save", on_save, null, null },
  125. { "save-as", on_save_as, null, null },
  126. { "import", on_import, null, null },
  127. { "preferences", on_preferences, null, null },
  128. { "deploy", on_deploy, null, null },
  129. { "quit", on_quit, null, null },
  130. { "menu-edit", null, null, null },
  131. { "undo", on_undo, null, null },
  132. { "redo", on_redo, null, null },
  133. { "duplicate", on_duplicate, null, null },
  134. { "delete", on_delete, null, null },
  135. { "tool", on_tool_changed, "s", "'move'" },
  136. { "snap", on_snap_mode_changed, "s", "'relative'" },
  137. { "reference-system", on_reference_system_changed, "s", "'local'" },
  138. { "snap-to-grid", on_snap_to_grid, null, "true" },
  139. { "menu-grid", null, null, null },
  140. { "grid-show", on_show_grid, null, "true" },
  141. { "grid-custom", on_custom_grid, null, null },
  142. { "grid-preset", on_grid_changed, "s", "'1'" },
  143. { "menu-rotation-snap", null, null, null },
  144. { "rotation-snap-custom", on_rotation_snap, null, null },
  145. { "rotation-snap-preset", on_rotation_snap_changed, "s", "'15'" },
  146. { "menu-create", null, null, null },
  147. { "menu-primitives", null, null, null },
  148. { "primitive-cube", on_create_primitive, null, null },
  149. { "primitive-sphere", on_create_primitive, null, null },
  150. { "primitive-cone", on_create_primitive, null, null },
  151. { "primitive-cylinder", on_create_primitive, null, null },
  152. { "primitive-plane", on_create_primitive, null, null },
  153. { "camera", on_create_primitive, null, null },
  154. { "light", on_create_primitive, null, null },
  155. { "sound-source", on_create_primitive, null, null },
  156. { "menu-camera", null, null, null },
  157. { "camera-view", on_camera_view, "s", "'perspective'" },
  158. { "menu-engine", null, null, null },
  159. { "restart", on_editor_restart, null, null },
  160. { "reload-lua", on_refresh_lua, null, null },
  161. { "menu-view", null, null, null },
  162. { "resource-chooser", on_resource_chooser, null, null },
  163. { "project-browser", on_project_browser, null, null },
  164. { "console", on_console, null, null },
  165. { "statusbar", on_statusbar, null, null },
  166. { "inspector", on_inspector, null, null },
  167. { "menu-run", null, null, null },
  168. { "test-level", on_run_game, null, null },
  169. { "run-game", on_run_game, null, null },
  170. { "menu-help", null, null, null },
  171. { "manual", on_manual, null, null },
  172. { "report-issue", on_report_issue, null, null },
  173. { "browse-logs", on_browse_logs, null, null },
  174. { "changelog", on_changelog, null, null },
  175. { "about", on_about, null, null },
  176. { "debug-render-world", on_debug_render_world, null, "false" },
  177. { "debug-physics-world", on_debug_physics_world, null, "false" }
  178. };
  179. // Command line options
  180. private string _source_dir = "";
  181. private string _toolchain_dir = "";
  182. private string _level_resource = "";
  183. private bool _create_initial_files = false;
  184. // Editor state
  185. private double _grid_size;
  186. private double _rotation_snap;
  187. private bool _show_grid;
  188. private bool _snap_to_grid;
  189. private bool _debug_render_world;
  190. private bool _debug_physics_world;
  191. private ToolType _tool_type;
  192. private SnapMode _snap_mode;
  193. private ReferenceSystem _reference_system;
  194. // Engine connections
  195. private GLib.Subprocess _compiler_process;
  196. private GLib.Subprocess _editor_process;
  197. private GLib.Subprocess _game_process;
  198. private ConsoleClient _compiler;
  199. public ConsoleClient _editor;
  200. private ConsoleClient _game;
  201. // Level data
  202. private Database _database;
  203. private Project _project;
  204. private ProjectStore _project_store;
  205. private Level _level;
  206. private DataCompiler _data_compiler;
  207. // Widgets
  208. private ProjectBrowser _project_browser;
  209. private EditorView _editor_view;
  210. private LevelTreeView _level_treeview;
  211. private LevelLayersTreeView _level_layers_treeview;
  212. private PropertiesView _properties_view;
  213. private PreferencesDialog _preferences_dialog;
  214. private ResourceChooser _resource_chooser;
  215. private Gtk.Popover _resource_popover;
  216. private Gtk.Overlay _editor_view_overlay;
  217. private Slide _project_slide;
  218. private Slide _editor_slide;
  219. private Slide _inspector_slide;
  220. private Gtk.Toolbar _toolbar;
  221. private Gtk.ToolButton _toolbar_run;
  222. private Gtk.Notebook _level_tree_view_notebook;
  223. private Gtk.Paned _editor_pane;
  224. private Gtk.Paned _content_pane;
  225. private Gtk.Paned _inspector_pane;
  226. private Gtk.Paned _main_pane;
  227. private Statusbar _statusbar;
  228. private Gtk.Box _main_vbox;
  229. private Gtk.FileFilter _file_filter;
  230. private Gtk.ComboBoxText _combo;
  231. private uint _save_timer_id;
  232. public LevelEditorApplication()
  233. {
  234. Object(application_id: "org.crown.level_editor"
  235. , flags: GLib.ApplicationFlags.FLAGS_NONE
  236. );
  237. }
  238. protected override void startup()
  239. {
  240. base.startup();
  241. Gtk.Settings.get_default().gtk_theme_name = "Adwaita";
  242. Gtk.Settings.get_default().gtk_application_prefer_dark_theme = true;
  243. Gtk.CssProvider provider = new Gtk.CssProvider();
  244. Gdk.Screen screen = Gdk.Display.get_default().get_default_screen();
  245. Gtk.StyleContext.add_provider_for_screen(screen, provider, STYLE_PROVIDER_PRIORITY_APPLICATION);
  246. provider.load_from_resource("/org/crown/level_editor/css/style.css");
  247. this.add_action_entries(action_entries, this);
  248. if (_source_dir == "")
  249. {
  250. Gtk.FileChooserDialog fcd = new Gtk.FileChooserDialog("Select folder where to create new project..."
  251. , null
  252. , FileChooserAction.SELECT_FOLDER
  253. , "Cancel"
  254. , ResponseType.CANCEL
  255. , "Select"
  256. , ResponseType.ACCEPT
  257. );
  258. if (fcd.run() != (int)ResponseType.ACCEPT)
  259. {
  260. fcd.destroy();
  261. return;
  262. }
  263. string dir = fcd.get_filename();
  264. fcd.destroy();
  265. if (GLib.FileUtils.test(dir, FileTest.IS_REGULAR))
  266. {
  267. stdout.printf("Source directory can't be a regular file\n");
  268. return;
  269. }
  270. _source_dir = dir;
  271. _create_initial_files = true;
  272. }
  273. _compiler = new ConsoleClient();
  274. _compiler.connected.connect(on_compiler_connected);
  275. _compiler.disconnected.connect(on_compiler_disconnected_unexpected);
  276. _compiler.message_received.connect(on_message_received);
  277. _data_compiler = new DataCompiler(_compiler);
  278. _project = new Project(_data_compiler);
  279. _project.load(_source_dir, _toolchain_dir);
  280. if (_create_initial_files)
  281. _project.create_initial_files();
  282. _database = new Database();
  283. _editor = new ConsoleClient();
  284. _editor.connected.connect(on_editor_connected);
  285. _editor.disconnected.connect(on_editor_disconnected_unexpected);
  286. _editor.message_received.connect(on_message_received);
  287. _game = new ConsoleClient();
  288. _game.connected.connect(on_game_connected);
  289. _game.disconnected.connect(on_game_disconnected);
  290. _game.message_received.connect(on_message_received);
  291. _level = new Level(_database, _editor, _project);
  292. // Editor state
  293. _grid_size = 1.0;
  294. _rotation_snap = 15.0;
  295. _show_grid = true;
  296. _snap_to_grid = true;
  297. _debug_render_world = false;
  298. _debug_physics_world = false;
  299. _tool_type = ToolType.MOVE;
  300. _snap_mode = SnapMode.RELATIVE;
  301. _reference_system = ReferenceSystem.LOCAL;
  302. // Engine connections
  303. _compiler_process = null;
  304. _editor_process = null;
  305. _game_process = null;
  306. _project_store = new ProjectStore(_project);
  307. // Widgets
  308. _combo = new Gtk.ComboBoxText();
  309. _combo.append("editor", "Editor");
  310. _combo.append("game", "Game");
  311. _combo.set_active_id("editor");
  312. _console_view = new ConsoleView(_project, _combo);
  313. _project_browser = new ProjectBrowser(_project, _project_store);
  314. _level_treeview = new LevelTreeView(_database, _level);
  315. _level_layers_treeview = new LevelLayersTreeView(_database, _level);
  316. _properties_view = new PropertiesView(_level, _project_store);
  317. _project_slide = new Slide();
  318. _editor_slide = new Slide();
  319. _inspector_slide = new Slide();
  320. Gtk.Builder builder = new Gtk.Builder.from_resource("/org/crown/level_editor/ui/toolbar.ui");
  321. _toolbar = builder.get_object("toolbar") as Gtk.Toolbar;
  322. _toolbar_run = builder.get_object("run") as Gtk.ToolButton;
  323. _editor_view_overlay = new Gtk.Overlay();
  324. _editor_view_overlay.add_overlay(_toolbar);
  325. _resource_popover = new Gtk.Popover(_toolbar);
  326. _resource_popover.delete_event.connect(() => { _resource_popover.hide(); return true; });
  327. _resource_popover.modal = true;
  328. _preferences_dialog = new PreferencesDialog(this);
  329. _preferences_dialog.set_transient_for(this.active_window);
  330. _preferences_dialog.delete_event.connect(() => { _preferences_dialog.hide(); return true; });
  331. _resource_chooser = new ResourceChooser(_project, _project_store, true);
  332. _resource_chooser.resource_selected.connect(on_resource_browser_resource_selected);
  333. _resource_chooser.resource_selected.connect(() => { _resource_popover.hide(); });
  334. _resource_popover.add(_resource_chooser);
  335. _level_tree_view_notebook = new Notebook();
  336. _level_tree_view_notebook.show_border = false;
  337. _level_tree_view_notebook.append_page(_level_treeview, new Gtk.Image.from_icon_name("level-tree", IconSize.SMALL_TOOLBAR));
  338. _level_tree_view_notebook.append_page(_level_layers_treeview, new Gtk.Image.from_icon_name("level-layers", IconSize.SMALL_TOOLBAR));
  339. _editor_pane = new Gtk.Paned(Gtk.Orientation.HORIZONTAL);
  340. _editor_pane.pack1(_project_slide, false, false);
  341. _editor_pane.pack2(_editor_slide, true, false);
  342. _editor_pane.set_position(210);
  343. _content_pane = new Gtk.Paned(Gtk.Orientation.VERTICAL);
  344. _content_pane.pack1(_editor_pane, true, false);
  345. _content_pane.pack2(_console_view, false, false);
  346. _content_pane.set_position(500);
  347. _inspector_pane = new Gtk.Paned(Gtk.Orientation.VERTICAL);
  348. _inspector_pane.pack1(_level_tree_view_notebook, true, false);
  349. _inspector_pane.pack2(_properties_view, false, false);
  350. _inspector_pane.set_position(250);
  351. _main_pane = new Gtk.Paned(Gtk.Orientation.HORIZONTAL);
  352. _main_pane.pack1(_content_pane, true, false);
  353. _main_pane.pack2(_inspector_slide, false, false);
  354. _main_pane.set_position(WINDOW_DEFAULT_WIDTH - 375);
  355. _statusbar = new Statusbar();
  356. _main_vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
  357. _main_vbox.pack_start(_main_pane, true, true, 0);
  358. _main_vbox.pack_start(_statusbar, false, false, 0);
  359. _file_filter = new Gtk.FileFilter();
  360. _file_filter.set_filter_name("Level (*.level)");
  361. _file_filter.add_pattern("*.level");
  362. if (_level_resource != "")
  363. {
  364. string level_path = Path.build_filename(_project.source_dir(), _level_resource + ".level");
  365. if (!GLib.FileUtils.test(level_path, FileTest.EXISTS) || !GLib.FileUtils.test(level_path, FileTest.IS_REGULAR))
  366. {
  367. loge("Level resource '%s' does not exist.".printf(_level_resource));
  368. return;
  369. }
  370. _level.load(level_path);
  371. }
  372. else
  373. {
  374. _level.load_empty_level();
  375. }
  376. load_settings();
  377. restart_compiler();
  378. }
  379. public void load_settings()
  380. {
  381. Hashtable settings = SJSON.load(_settings_file.get_path());
  382. _preferences_dialog.load(settings.has_key("preferences") ? (Hashtable)settings["preferences"] : new Hashtable());
  383. }
  384. public void save_settings()
  385. {
  386. Hashtable preferences = new Hashtable();
  387. _preferences_dialog.save(preferences);
  388. Hashtable settings = new Hashtable();
  389. settings["preferences"] = preferences;
  390. SJSON.save(settings, _settings_file.get_path());
  391. }
  392. protected override void activate()
  393. {
  394. if (_source_dir == "")
  395. return;
  396. if (this.active_window == null)
  397. {
  398. LevelEditorWindow win = new LevelEditorWindow(this);
  399. win.add(_main_vbox);
  400. try
  401. {
  402. win.icon = IconTheme.get_default().load_icon("pepper", 48, 0);
  403. }
  404. catch (Error e)
  405. {
  406. loge(e.message);
  407. }
  408. }
  409. this.active_window.show_all();
  410. }
  411. protected override bool local_command_line(ref unowned string[] args, out int exit_status)
  412. {
  413. if (args.length > 1)
  414. {
  415. if (!GLib.FileUtils.test(args[1], FileTest.EXISTS) || !GLib.FileUtils.test(args[1], FileTest.IS_DIR))
  416. {
  417. loge("Source directory does not exist or it is not a directory\n");
  418. exit_status = 1;
  419. return true;
  420. }
  421. _source_dir = args[1];
  422. }
  423. if (args.length > 2)
  424. {
  425. // Validation is done below after the Project object instantiation
  426. _level_resource = args[2];
  427. }
  428. if (args.length > 3)
  429. {
  430. if (!GLib.FileUtils.test(args[3], FileTest.EXISTS) || !GLib.FileUtils.test(args[3], FileTest.IS_DIR))
  431. {
  432. loge("Toolchain directory does not exist or it is not a directory\n");
  433. exit_status = 1;
  434. return true;
  435. }
  436. _toolchain_dir = args[3];
  437. }
  438. else
  439. {
  440. bool found = false;
  441. /// More desirable paths come first
  442. string toolchain_paths[] =
  443. {
  444. "../..",
  445. "../../../samples"
  446. };
  447. for (int i = 0; i < toolchain_paths.length; ++i)
  448. {
  449. string path = Path.build_filename(toolchain_paths[i], "core");
  450. // Try to locate the toolchain directory
  451. if (GLib.FileUtils.test(path, FileTest.EXISTS) && GLib.FileUtils.test(path, FileTest.IS_DIR))
  452. {
  453. _toolchain_dir = toolchain_paths[i];
  454. found = true;
  455. break;
  456. }
  457. }
  458. if (!found)
  459. {
  460. loge("Unable to find the toolchain directory\n");
  461. exit_status = 1;
  462. return true;
  463. }
  464. }
  465. exit_status = 0;
  466. return false;
  467. }
  468. protected override int command_line(ApplicationCommandLine command_line)
  469. {
  470. this.activate();
  471. return 0;
  472. }
  473. public ConsoleClient? current_selected_client()
  474. {
  475. if (_combo.get_active_id() == "editor")
  476. return _editor;
  477. else if (_combo.get_active_id() == "game")
  478. return _game;
  479. else
  480. return null;
  481. }
  482. private void on_resource_browser_resource_selected(string type, string name)
  483. {
  484. _editor.send_script(LevelEditorApi.set_placeable(type, name));
  485. activate_action("tool", new GLib.Variant.string("place"));
  486. }
  487. private void on_compiler_connected(string address, int port)
  488. {
  489. logi("Connected to data_compiler@%s:%d".printf(address, port));
  490. _compiler.receive_async();
  491. }
  492. private void on_compiler_disconnected()
  493. {
  494. logi("Disconnected from data_compiler");
  495. }
  496. private void on_compiler_disconnected_unexpected()
  497. {
  498. on_compiler_disconnected();
  499. stop_game();
  500. stop_editor();
  501. // Reset the callback
  502. _data_compiler.finished(false);
  503. _project_slide.show_widget(compiler_crashed_label());
  504. _editor_slide.show_widget(compiler_crashed_label());
  505. _inspector_slide.show_widget(compiler_crashed_label());
  506. }
  507. private void on_editor_connected(string address, int port)
  508. {
  509. logi("Connected to level_editor@%s:%d".printf(address, port));
  510. _editor.receive_async();
  511. }
  512. private void on_editor_disconnected()
  513. {
  514. logi("Disconnected from editor");
  515. }
  516. private void on_editor_disconnected_unexpected()
  517. {
  518. on_editor_disconnected();
  519. Gtk.Label label = new Gtk.Label(null);
  520. label.set_markup("Something went wrong.\rTry to <a href=\"restart\">restart</a> this view.");
  521. label.activate_link.connect(() => {
  522. activate_action("restart", null);
  523. return true;
  524. });
  525. _editor_slide.show_widget(label);
  526. }
  527. private void on_game_connected(string address, int port)
  528. {
  529. logi("Connected to game@%s:%d".printf(address, port));
  530. _game.receive_async();
  531. _combo.set_active_id("game");
  532. }
  533. private void on_game_disconnected()
  534. {
  535. logi("Disconnected from game");
  536. _project.delete_garbage();
  537. _combo.set_active_id("editor");
  538. _toolbar_run.icon_name = "game-run";
  539. }
  540. private void on_message_received(ConsoleClient client, uint8[] json)
  541. {
  542. Hashtable msg = JSON.decode(json) as Hashtable;
  543. string msg_type = msg["type"] as string;
  544. if (msg_type == "message")
  545. {
  546. log((string)msg["system"], (string)msg["severity"], (string)msg["message"]);
  547. }
  548. else if (msg_type == "add_file")
  549. {
  550. string path = (string)msg["path"];
  551. _project.add_file(path);
  552. }
  553. else if (msg_type == "remove_file")
  554. {
  555. string path = (string)msg["path"];
  556. _project.remove_file(path);
  557. }
  558. else if (msg_type == "add_tree")
  559. {
  560. string path = (string)msg["path"];
  561. _project.add_tree(path);
  562. }
  563. else if (msg_type == "remove_tree")
  564. {
  565. string path = (string)msg["path"];
  566. _project.remove_tree(path);
  567. }
  568. else if (msg_type == "compile")
  569. {
  570. // Guid id = Guid.parse((string)msg["id"]);
  571. if (msg.has_key("start"))
  572. {
  573. // FIXME
  574. }
  575. else if (msg.has_key("success"))
  576. {
  577. _data_compiler.finished((bool)msg["success"]);
  578. }
  579. }
  580. else if (msg_type == "unit_spawned")
  581. {
  582. string id = (string) msg["id"];
  583. string name = (string) msg["name"];
  584. ArrayList<Value?> pos = (ArrayList<Value?>)msg["position"];
  585. ArrayList<Value?> rot = (ArrayList<Value?>)msg["rotation"];
  586. ArrayList<Value?> scl = (ArrayList<Value?>)msg["scale"];
  587. _level.on_unit_spawned(Guid.parse(id)
  588. , name
  589. , Vector3.from_array(pos)
  590. , Quaternion.from_array(rot)
  591. , Vector3.from_array(scl)
  592. );
  593. }
  594. else if (msg_type == "sound_spawned")
  595. {
  596. string id = (string) msg["id"];
  597. string name = (string) msg["name"];
  598. ArrayList<Value?> pos = (ArrayList<Value?>)msg["position"];
  599. ArrayList<Value?> rot = (ArrayList<Value?>)msg["rotation"];
  600. ArrayList<Value?> scl = (ArrayList<Value?>)msg["scale"];
  601. double range = (double) msg["range"];
  602. double volume = (double) msg["volume"];
  603. bool loop = (bool) msg["loop"];
  604. _level.on_sound_spawned(Guid.parse(id)
  605. , name
  606. , Vector3.from_array(pos)
  607. , Quaternion.from_array(rot)
  608. , Vector3.from_array(scl)
  609. , range
  610. , volume
  611. , loop
  612. );
  613. }
  614. else if (msg_type == "move_objects")
  615. {
  616. Hashtable ids = (Hashtable)msg["ids"];
  617. Hashtable new_positions = (Hashtable)msg["new_positions"];
  618. Hashtable new_rotations = (Hashtable)msg["new_rotations"];
  619. Hashtable new_scales = (Hashtable)msg["new_scales"];
  620. ArrayList<string> keys = new ArrayList<string>.wrap(ids.keys.to_array());
  621. keys.sort(Gee.Functions.get_compare_func_for(typeof(string)));
  622. Guid[] n_ids = new Guid[keys.size];
  623. Vector3[] n_positions = new Vector3[keys.size];
  624. Quaternion[] n_rotations = new Quaternion[keys.size];
  625. Vector3[] n_scales = new Vector3[keys.size];
  626. for (int i = 0; i < keys.size; ++i)
  627. {
  628. string k = keys[i];
  629. n_ids[i] = Guid.parse((string)ids[k]);
  630. n_positions[i] = Vector3.from_array((ArrayList<Value?>)(new_positions[k]));
  631. n_rotations[i] = Quaternion.from_array((ArrayList<Value?>)new_rotations[k]);
  632. n_scales[i] = Vector3.from_array((ArrayList<Value?>)new_scales[k]);
  633. }
  634. _level.on_move_objects(n_ids, n_positions, n_rotations, n_scales);
  635. }
  636. else if (msg_type == "selection")
  637. {
  638. Hashtable objects = (Hashtable)msg["objects"];
  639. ArrayList<string> keys = new ArrayList<string>.wrap(objects.keys.to_array());
  640. keys.sort(Gee.Functions.get_compare_func_for(typeof(string)));
  641. Guid[] ids = new Guid[keys.size];
  642. for (int i = 0; i < keys.size; ++i)
  643. {
  644. string k = keys[i];
  645. ids[i] = Guid.parse((string)objects[k]);
  646. }
  647. _level.on_selection(ids);
  648. }
  649. else if (msg_type == "error")
  650. {
  651. loge((string)msg["message"]);
  652. }
  653. else
  654. {
  655. loge("Unknown message type: " + msg_type);
  656. }
  657. // Receive next message
  658. client.receive_async();
  659. }
  660. private void send_state()
  661. {
  662. StringBuilder sb = new StringBuilder();
  663. sb.append(LevelEditorApi.set_grid_size(_grid_size));
  664. sb.append(LevelEditorApi.set_rotation_snap(_rotation_snap));
  665. sb.append(LevelEditorApi.enable_show_grid(_show_grid));
  666. sb.append(LevelEditorApi.enable_snap_to_grid(_snap_to_grid));
  667. sb.append(LevelEditorApi.enable_debug_render_world(_debug_render_world));
  668. sb.append(LevelEditorApi.enable_debug_physics_world(_debug_physics_world));
  669. sb.append(LevelEditorApi.set_tool_type(_tool_type));
  670. sb.append(LevelEditorApi.set_snap_mode(_snap_mode));
  671. sb.append(LevelEditorApi.set_reference_system(_reference_system));
  672. _editor.send_script(sb.str);
  673. }
  674. private bool on_button_press(EventButton ev)
  675. {
  676. return true;
  677. }
  678. private bool on_button_release(EventButton ev)
  679. {
  680. return true;
  681. }
  682. Gtk.Widget starting_compiler_label()
  683. {
  684. return new Gtk.Label("Compiling resources, please wait...");
  685. }
  686. Gtk.Widget compiler_crashed_label()
  687. {
  688. Gtk.Label label = new Gtk.Label(null);
  689. label.set_markup("Data Compiler disconnected.\rTry to <a href=\"restart\">restart</a> compiler to continue.");
  690. label.activate_link.connect(() => {
  691. restart_compiler();
  692. return true;
  693. });
  694. return label;
  695. }
  696. Gtk.Widget compiler_failed_compilation_label()
  697. {
  698. Gtk.Label label = new Gtk.Label(null);
  699. label.set_markup("Data compilation failed.\rFix errors and <a href=\"restart\">restart</a> compiler to continue.");
  700. label.activate_link.connect(() => {
  701. restart_compiler();
  702. return true;
  703. });
  704. return label;
  705. }
  706. private void restart_compiler()
  707. {
  708. stop_compiler();
  709. _project.reset();
  710. _project_slide.show_widget(starting_compiler_label());
  711. _editor_slide.show_widget(starting_compiler_label());
  712. _inspector_slide.show_widget(starting_compiler_label());
  713. string args[] =
  714. {
  715. ENGINE_EXE,
  716. "--source-dir", _project.source_dir(),
  717. "--data-dir", _project.data_dir(),
  718. "--map-source-dir", "core", _project.toolchain_dir(),
  719. "--server",
  720. "--wait-console",
  721. null
  722. };
  723. GLib.SubprocessLauncher sl = new GLib.SubprocessLauncher(subprocess_flags());
  724. sl.set_cwd(ENGINE_DIR);
  725. try
  726. {
  727. _compiler_process = sl.spawnv(args);
  728. }
  729. catch (Error e)
  730. {
  731. loge(e.message);
  732. }
  733. // It is an error if compiler disconnects after here.
  734. _compiler.disconnected.disconnect(on_compiler_disconnected);
  735. _compiler.disconnected.connect(on_compiler_disconnected_unexpected);
  736. for (int tries = 0; !_compiler.is_connected() && tries < 5; ++tries)
  737. {
  738. _compiler.connect("127.0.0.1", CROWN_DEFAULT_SERVER_PORT);
  739. GLib.Thread.usleep(250*1000);
  740. }
  741. _data_compiler.compile.begin(_project.data_dir(), _project.platform(), (obj, res) => {
  742. if (_data_compiler.compile.end(res))
  743. {
  744. restart_editor();
  745. _project_slide.show_widget(_project_browser);
  746. _inspector_slide.show_widget(_inspector_pane);
  747. }
  748. else
  749. {
  750. _project_slide.show_widget(compiler_failed_compilation_label());
  751. _editor_slide.show_widget(compiler_failed_compilation_label());
  752. _inspector_slide.show_widget(compiler_failed_compilation_label());
  753. }
  754. });
  755. }
  756. private void stop_compiler()
  757. {
  758. stop_game();
  759. stop_editor();
  760. if (_compiler != null)
  761. {
  762. // Explicit call to this function should not produce error messages.
  763. _compiler.disconnected.disconnect(on_compiler_disconnected_unexpected);
  764. _compiler.disconnected.connect(on_compiler_disconnected);
  765. _compiler.send(DataCompilerApi.quit());
  766. _compiler.close();
  767. }
  768. if (_compiler_process != null)
  769. {
  770. try
  771. {
  772. _compiler_process.wait();
  773. }
  774. catch (Error e)
  775. {
  776. loge(e.message);
  777. }
  778. }
  779. }
  780. private void start_editor(uint window_xid)
  781. {
  782. if (window_xid == 0)
  783. return;
  784. string args[] =
  785. {
  786. ENGINE_EXE,
  787. "--data-dir", _project.data_dir(),
  788. "--boot-dir", LEVEL_EDITOR_BOOT_DIR,
  789. "--parent-window", window_xid.to_string(),
  790. "--wait-console",
  791. null
  792. };
  793. GLib.SubprocessLauncher sl = new GLib.SubprocessLauncher(subprocess_flags());
  794. sl.set_cwd(ENGINE_DIR);
  795. try
  796. {
  797. _editor_process = sl.spawnv(args);
  798. }
  799. catch (Error e)
  800. {
  801. loge(e.message);
  802. }
  803. // It is an error if editor disconnects after here.
  804. _editor.disconnected.disconnect(on_editor_disconnected);
  805. _editor.disconnected.connect(on_editor_disconnected_unexpected);
  806. for (int tries = 0; !_editor.is_connected() && tries < 10; ++tries)
  807. {
  808. _editor.connect("127.0.0.1", 10001);
  809. GLib.Thread.usleep(500*1000);
  810. }
  811. _level.send_level();
  812. send_state();
  813. _preferences_dialog.apply();
  814. }
  815. private void stop_editor()
  816. {
  817. _resource_chooser.stop_editor();
  818. if (_editor != null)
  819. {
  820. // Explicit call to this function should not produce error messages.
  821. _editor.disconnected.disconnect(on_editor_disconnected_unexpected);
  822. _editor.disconnected.connect(on_editor_disconnected);
  823. _editor.send_script("Device.quit()");
  824. _editor.close();
  825. }
  826. if (_editor_process != null)
  827. {
  828. try
  829. {
  830. _editor_process.wait();
  831. }
  832. catch (Error e)
  833. {
  834. loge(e.message);
  835. }
  836. }
  837. _editor_slide.show_widget(new Gtk.Label("Disconnected."));
  838. }
  839. private void restart_editor()
  840. {
  841. stop_editor();
  842. if (_editor_view != null)
  843. {
  844. _editor_view_overlay.remove(_editor_view);
  845. _editor_view = null;
  846. }
  847. _editor_view = new EditorView(_editor);
  848. _editor_view.realized.connect(on_editor_view_realized);
  849. _editor_view.button_press_event.connect(on_button_press);
  850. _editor_view.button_release_event.connect(on_button_release);
  851. _editor_view_overlay.add(_editor_view);
  852. _editor_slide.show_widget(_editor_view_overlay);
  853. _resource_chooser.restart_editor();
  854. }
  855. private void start_game(StartGame sg)
  856. {
  857. _project.dump_test_level(_database);
  858. _data_compiler.compile.begin(_project.data_dir(), _project.platform(), (obj, res) => {
  859. if (_data_compiler.compile.end(res))
  860. {
  861. string args[] =
  862. {
  863. ENGINE_EXE,
  864. "--data-dir", _project.data_dir(),
  865. "--console-port", "12345",
  866. "--wait-console",
  867. "--lua-string", sg == StartGame.TEST ? "TEST=true" : "",
  868. null
  869. };
  870. GLib.SubprocessLauncher sl = new GLib.SubprocessLauncher(subprocess_flags());
  871. sl.set_cwd(ENGINE_DIR);
  872. try
  873. {
  874. _game_process = sl.spawnv(args);
  875. }
  876. catch (Error e)
  877. {
  878. loge(e.message);
  879. }
  880. for (int tries = 0; !_game.is_connected() && tries < 10; ++tries)
  881. {
  882. _game.connect("127.0.0.1", 12345);
  883. GLib.Thread.usleep(500*1000);
  884. }
  885. }
  886. else
  887. {
  888. _toolbar_run.icon_name = "game-run";
  889. }
  890. });
  891. }
  892. private void stop_game()
  893. {
  894. if (_game != null)
  895. {
  896. _game.send_script("Device.quit()");
  897. _game.close();
  898. }
  899. if (_game_process != null)
  900. {
  901. try
  902. {
  903. _game_process.wait();
  904. }
  905. catch (Error e)
  906. {
  907. loge(e.message);
  908. }
  909. }
  910. }
  911. private void deploy_game()
  912. {
  913. Gtk.FileChooserDialog fcd = new Gtk.FileChooserDialog("Select destination directory..."
  914. , this.active_window
  915. , FileChooserAction.SELECT_FOLDER
  916. , "Cancel"
  917. , ResponseType.CANCEL
  918. , "Open"
  919. , ResponseType.ACCEPT
  920. );
  921. if (fcd.run() == (int)ResponseType.ACCEPT)
  922. {
  923. GLib.File data_dir = File.new_for_path(fcd.get_filename());
  924. string args[] =
  925. {
  926. ENGINE_EXE,
  927. "--source-dir", _project.source_dir(),
  928. "--map-source-dir", "core", _project.toolchain_dir(),
  929. "--data-dir", data_dir.get_path(),
  930. "--compile",
  931. null
  932. };
  933. GLib.SubprocessLauncher sl = new GLib.SubprocessLauncher(subprocess_flags());
  934. sl.set_cwd(ENGINE_DIR);
  935. try
  936. {
  937. GLib.Subprocess compiler = sl.spawnv(args);
  938. compiler.wait();
  939. if (compiler.get_exit_status() == 0)
  940. {
  941. string game_name = DEPLOY_DEFAULT_NAME;
  942. GLib.File engine_exe_src = File.new_for_path(DEPLOY_EXE);
  943. GLib.File engine_exe_dst = File.new_for_path(Path.build_filename(data_dir.get_path(), game_name + EXE_SUFFIX));
  944. engine_exe_src.copy(engine_exe_dst, FileCopyFlags.OVERWRITE);
  945. #if CROWN_PLATFORM_WINDOWS
  946. string lua51_name = "lua51.dll";
  947. GLib.File lua51_dll_src = File.new_for_path(lua51_name);
  948. GLib.File lua51_dll_dst = File.new_for_path(Path.build_filename(data_dir.get_path(), lua51_name));
  949. lua51_dll_src.copy(lua51_dll_dst, FileCopyFlags.OVERWRITE);
  950. string openal_name = "openal-release.dll";
  951. GLib.File openal_dll_src = File.new_for_path(openal_name);
  952. GLib.File openal_dll_dst = File.new_for_path(Path.build_filename(data_dir.get_path(), openal_name));
  953. openal_dll_src.copy(openal_dll_dst, FileCopyFlags.OVERWRITE);
  954. #endif // CROWN_PLATFORM_WINDOWS
  955. logi("Project deployed to `%s`".printf(data_dir.get_path()));
  956. }
  957. }
  958. catch (Error e)
  959. {
  960. logi("%s".printf(e.message));
  961. logi("Failed to deploy project");
  962. }
  963. }
  964. fcd.destroy();
  965. }
  966. private void on_editor_view_realized()
  967. {
  968. start_editor(_editor_view.window_id);
  969. }
  970. private void on_tool_changed(GLib.SimpleAction action, GLib.Variant? param)
  971. {
  972. string name = param.get_string();
  973. if (name == "place")
  974. _tool_type = ToolType.PLACE;
  975. else if (name == "move")
  976. _tool_type = ToolType.MOVE;
  977. else if (name == "rotate")
  978. _tool_type = ToolType.ROTATE;
  979. else if (name == "scale")
  980. _tool_type = ToolType.SCALE;
  981. send_state();
  982. action.set_state(param);
  983. }
  984. private void on_snap_mode_changed(GLib.SimpleAction action, GLib.Variant? param)
  985. {
  986. string name = param.get_string();
  987. if (name == "relative")
  988. _snap_mode = SnapMode.RELATIVE;
  989. else if (name == "absolute")
  990. _snap_mode = SnapMode.ABSOLUTE;
  991. send_state();
  992. action.set_state(param);
  993. }
  994. private void on_reference_system_changed(GLib.SimpleAction action, GLib.Variant? param)
  995. {
  996. string name = param.get_string();
  997. if (name == "local")
  998. _reference_system = ReferenceSystem.LOCAL;
  999. else if (name == "world")
  1000. _reference_system = ReferenceSystem.WORLD;
  1001. send_state();
  1002. action.set_state(param);
  1003. }
  1004. private void on_grid_changed(GLib.SimpleAction action, GLib.Variant? param)
  1005. {
  1006. _grid_size = float.parse(param.get_string());
  1007. send_state();
  1008. action.set_state(param);
  1009. }
  1010. private void on_rotation_snap_changed(GLib.SimpleAction action, GLib.Variant? param)
  1011. {
  1012. _rotation_snap = float.parse(param.get_string());
  1013. send_state();
  1014. action.set_state(param);
  1015. }
  1016. private void new_level()
  1017. {
  1018. _level.load_empty_level();
  1019. _level.send_level();
  1020. }
  1021. private void load_level(string level)
  1022. {
  1023. string filename = level;
  1024. if (filename == "")
  1025. {
  1026. Gtk.FileChooserDialog fcd = new Gtk.FileChooserDialog("Open Level..."
  1027. , this.active_window
  1028. , FileChooserAction.OPEN
  1029. , "Cancel"
  1030. , ResponseType.CANCEL
  1031. , "Open"
  1032. , ResponseType.ACCEPT
  1033. );
  1034. fcd.add_filter(_file_filter);
  1035. fcd.set_current_folder(_project.source_dir());
  1036. if (fcd.run() == (int)ResponseType.ACCEPT)
  1037. filename = fcd.get_filename();
  1038. fcd.destroy();
  1039. }
  1040. if (filename == "")
  1041. return;
  1042. if (!_project.path_is_within_dir(filename, _project.source_dir()))
  1043. {
  1044. loge("File must be within `%s`".printf(_project.source_dir()));
  1045. return;
  1046. }
  1047. if (filename.has_suffix(".level") && filename != _level._filename)
  1048. {
  1049. _level.load(filename);
  1050. _level.send_level();
  1051. send_state();
  1052. }
  1053. }
  1054. private void load_project()
  1055. {
  1056. Gtk.FileChooserDialog fcd = new Gtk.FileChooserDialog("Open Project..."
  1057. , this.active_window
  1058. , FileChooserAction.SELECT_FOLDER
  1059. , "Cancel"
  1060. , ResponseType.CANCEL
  1061. , "Open"
  1062. , ResponseType.ACCEPT
  1063. );
  1064. int rt = fcd.run();
  1065. if (rt != (int)ResponseType.ACCEPT)
  1066. {
  1067. fcd.destroy();
  1068. return;
  1069. }
  1070. string filename = fcd.get_filename();
  1071. fcd.destroy();
  1072. if (filename == _project.source_dir())
  1073. return;
  1074. logi("Loading `%s`...".printf(filename));
  1075. _project.load(filename, _project.toolchain_dir());
  1076. _level.load_empty_level();
  1077. restart_compiler();
  1078. }
  1079. private bool save_as(string? filename)
  1080. {
  1081. string path;
  1082. if (filename != null)
  1083. {
  1084. path = filename;
  1085. }
  1086. else
  1087. {
  1088. Gtk.FileChooserDialog fcd = new Gtk.FileChooserDialog("Save As..."
  1089. , this.active_window
  1090. , FileChooserAction.SAVE
  1091. , "Cancel"
  1092. , ResponseType.CANCEL
  1093. , "Save"
  1094. , ResponseType.ACCEPT
  1095. );
  1096. fcd.add_filter(_file_filter);
  1097. fcd.set_current_folder(_project.source_dir());
  1098. int rt = fcd.run();
  1099. if (rt != (int)ResponseType.ACCEPT)
  1100. {
  1101. fcd.destroy();
  1102. return false;
  1103. }
  1104. path = fcd.get_filename();
  1105. fcd.destroy();
  1106. }
  1107. if (!_project.path_is_within_dir(path, _project.source_dir()))
  1108. {
  1109. loge("File must be within `%s`".printf(_project.source_dir()));
  1110. return false;
  1111. }
  1112. _level.save(path.has_suffix(".level") ? path : path + ".level");
  1113. _statusbar.set_temporary_message("Saved %s".printf(_level._filename));
  1114. return true;
  1115. }
  1116. private bool save()
  1117. {
  1118. return save_as(_level._filename);
  1119. }
  1120. private bool save_timeout()
  1121. {
  1122. if (_level._filename != null)
  1123. save();
  1124. return true;
  1125. }
  1126. public void close_all()
  1127. {
  1128. save_settings();
  1129. if (_save_timer_id > 0)
  1130. GLib.Source.remove(_save_timer_id);
  1131. if (_resource_chooser != null)
  1132. _resource_chooser.destroy();
  1133. if (_preferences_dialog != null)
  1134. _preferences_dialog.destroy();
  1135. stop_compiler();
  1136. }
  1137. protected override void shutdown()
  1138. {
  1139. base.shutdown();
  1140. }
  1141. // Returns true if the level has been saved or the user decided it
  1142. // should be discarded.
  1143. public bool should_quit()
  1144. {
  1145. if (!_database.changed())
  1146. return true;
  1147. Gtk.MessageDialog md = new Gtk.MessageDialog(this.active_window
  1148. , Gtk.DialogFlags.MODAL
  1149. , Gtk.MessageType.WARNING
  1150. , Gtk.ButtonsType.NONE
  1151. , "File changed, save?"
  1152. );
  1153. md.add_button("Quit _without Saving", ResponseType.NO);
  1154. md.add_button("_Cancel", ResponseType.CANCEL);
  1155. md.add_button("_Save", ResponseType.YES);
  1156. md.set_default_response(ResponseType.YES);
  1157. int rt = md.run();
  1158. md.destroy();
  1159. if (rt == (int)ResponseType.YES && save() || rt == (int)ResponseType.NO)
  1160. return true;
  1161. return false;
  1162. }
  1163. private void on_new_level(GLib.SimpleAction action, GLib.Variant? param)
  1164. {
  1165. if (!_database.changed())
  1166. {
  1167. new_level();
  1168. send_state();
  1169. return;
  1170. }
  1171. Gtk.MessageDialog md = new Gtk.MessageDialog(this.active_window
  1172. , Gtk.DialogFlags.MODAL
  1173. , Gtk.MessageType.WARNING
  1174. , Gtk.ButtonsType.NONE
  1175. , "File changed, save?"
  1176. );
  1177. md.add_button("New _without Saving", ResponseType.NO);
  1178. md.add_button("_Cancel", ResponseType.CANCEL);
  1179. md.add_button("_Save", ResponseType.YES);
  1180. md.set_default_response(ResponseType.YES);
  1181. int rt = md.run();
  1182. md.destroy();
  1183. if (rt == (int)ResponseType.YES && save() || rt == (int)ResponseType.NO)
  1184. {
  1185. new_level();
  1186. send_state();
  1187. }
  1188. }
  1189. private void on_open_level(GLib.SimpleAction action, GLib.Variant? param)
  1190. {
  1191. if (!_database.changed())
  1192. {
  1193. load_level(param.get_string());
  1194. return;
  1195. }
  1196. Gtk.MessageDialog md = new Gtk.MessageDialog(this.active_window
  1197. , Gtk.DialogFlags.MODAL
  1198. , Gtk.MessageType.WARNING
  1199. , Gtk.ButtonsType.NONE
  1200. , "File changed, save?"
  1201. );
  1202. md.add_button("Open _without Saving", ResponseType.NO);
  1203. md.add_button("_Cancel", ResponseType.CANCEL);
  1204. md.add_button("_Save", ResponseType.YES);
  1205. md.set_default_response(ResponseType.YES);
  1206. int rt = md.run();
  1207. md.destroy();
  1208. if (rt == (int)ResponseType.YES && save() || rt == (int)ResponseType.NO)
  1209. load_level(param.get_string());
  1210. }
  1211. private void on_open_project(GLib.SimpleAction action, GLib.Variant? param)
  1212. {
  1213. if (!_database.changed())
  1214. {
  1215. load_project();
  1216. return;
  1217. }
  1218. Gtk.MessageDialog md = new Gtk.MessageDialog(this.active_window
  1219. , Gtk.DialogFlags.MODAL
  1220. , Gtk.MessageType.WARNING
  1221. , Gtk.ButtonsType.NONE
  1222. , "File changed, save?"
  1223. );
  1224. md.add_button("Open _without Saving", ResponseType.NO);
  1225. md.add_button("_Cancel", ResponseType.CANCEL);
  1226. md.add_button("_Save", ResponseType.YES);
  1227. md.set_default_response(ResponseType.YES);
  1228. int rt = md.run();
  1229. md.destroy();
  1230. if (rt == (int)ResponseType.YES && save() || rt == (int)ResponseType.NO)
  1231. load_project();
  1232. }
  1233. private void on_save(GLib.SimpleAction action, GLib.Variant? param)
  1234. {
  1235. save();
  1236. }
  1237. private void on_save_as(GLib.SimpleAction action, GLib.Variant? param)
  1238. {
  1239. save_as(null);
  1240. }
  1241. private void on_import(GLib.SimpleAction action, GLib.Variant? param)
  1242. {
  1243. _project.import(null, this.active_window);
  1244. }
  1245. private void on_preferences(GLib.SimpleAction action, GLib.Variant? param)
  1246. {
  1247. _preferences_dialog.show_all();
  1248. }
  1249. private void on_deploy(GLib.SimpleAction action, GLib.Variant? param)
  1250. {
  1251. deploy_game();
  1252. }
  1253. private void on_quit(GLib.SimpleAction action, GLib.Variant? param)
  1254. {
  1255. this.active_window.close();
  1256. }
  1257. private void on_show_grid(GLib.SimpleAction action, GLib.Variant? param)
  1258. {
  1259. _show_grid = !action.get_state().get_boolean();
  1260. send_state();
  1261. action.set_state(new GLib.Variant.boolean(_show_grid));
  1262. }
  1263. private void on_custom_grid()
  1264. {
  1265. Gtk.Dialog dg = new Gtk.Dialog.with_buttons("Grid size"
  1266. , this.active_window
  1267. , DialogFlags.MODAL
  1268. , "Cancel"
  1269. , ResponseType.CANCEL
  1270. , "Ok"
  1271. , ResponseType.OK
  1272. , null
  1273. );
  1274. EntryDouble sb = new EntryDouble(_grid_size, 0.1, 1000);
  1275. sb.activate.connect(() => { dg.response(ResponseType.OK); });
  1276. dg.get_content_area().add(sb);
  1277. dg.skip_taskbar_hint = true;
  1278. dg.show_all();
  1279. if (dg.run() == (int)ResponseType.OK)
  1280. {
  1281. _grid_size = sb.value;
  1282. send_state();
  1283. }
  1284. dg.destroy();
  1285. }
  1286. private void on_rotation_snap(GLib.SimpleAction action, GLib.Variant? param)
  1287. {
  1288. Gtk.Dialog dg = new Gtk.Dialog.with_buttons("Rotation snap"
  1289. , this.active_window
  1290. , DialogFlags.MODAL
  1291. , "Cancel"
  1292. , ResponseType.CANCEL
  1293. , "Ok"
  1294. , ResponseType.OK
  1295. , null
  1296. );
  1297. EntryDouble sb = new EntryDouble(_rotation_snap, 1.0, 180.0);
  1298. sb.activate.connect(() => { dg.response(ResponseType.OK); });
  1299. dg.get_content_area().add(sb);
  1300. dg.skip_taskbar_hint = true;
  1301. dg.show_all();
  1302. if (dg.run() == (int)ResponseType.OK)
  1303. {
  1304. _rotation_snap = sb.value;
  1305. send_state();
  1306. }
  1307. dg.destroy();
  1308. }
  1309. private void on_create_primitive(GLib.SimpleAction action, GLib.Variant? param)
  1310. {
  1311. if (action.name == "primitive-cube")
  1312. _editor.send_script(LevelEditorApi.set_placeable("unit", "core/units/primitives/cube"));
  1313. else if (action.name == "primitive-sphere")
  1314. _editor.send_script(LevelEditorApi.set_placeable("unit", "core/units/primitives/sphere"));
  1315. else if (action.name == "primitive-cone")
  1316. _editor.send_script(LevelEditorApi.set_placeable("unit", "core/units/primitives/cone"));
  1317. else if (action.name == "primitive-cylinder")
  1318. _editor.send_script(LevelEditorApi.set_placeable("unit", "core/units/primitives/cylinder"));
  1319. else if (action.name == "primitive-plane")
  1320. _editor.send_script(LevelEditorApi.set_placeable("unit", "core/units/primitives/plane"));
  1321. else if (action.name == "camera")
  1322. _editor.send_script(LevelEditorApi.set_placeable("unit", "core/units/camera"));
  1323. else if (action.name == "light")
  1324. _editor.send_script(LevelEditorApi.set_placeable("unit", "core/units/light"));
  1325. else if (action.name == "sound-source")
  1326. _editor.send_script(LevelEditorApi.set_placeable("sound", ""));
  1327. activate_action("tool", new GLib.Variant.string("place"));
  1328. }
  1329. private void on_camera_view(GLib.SimpleAction action, GLib.Variant? param)
  1330. {
  1331. string name = param.get_string();
  1332. if (name == "perspective")
  1333. _editor.send_script("LevelEditor:camera_view_perspective()");
  1334. else if (name == "front")
  1335. _editor.send_script("LevelEditor:camera_view_front()");
  1336. else if (name == "back")
  1337. _editor.send_script("LevelEditor:camera_view_back()");
  1338. else if (name == "right")
  1339. _editor.send_script("LevelEditor:camera_view_right()");
  1340. else if (name == "left")
  1341. _editor.send_script("LevelEditor:camera_view_left()");
  1342. else if (name == "top")
  1343. _editor.send_script("LevelEditor:camera_view_top()");
  1344. else if (name == "bottom")
  1345. _editor.send_script("LevelEditor:camera_view_bottom()");
  1346. action.set_state(param);
  1347. }
  1348. private void on_resource_chooser(GLib.SimpleAction action, GLib.Variant? param)
  1349. {
  1350. _resource_popover.show_all();
  1351. }
  1352. private void on_project_browser(GLib.SimpleAction action, GLib.Variant? param)
  1353. {
  1354. if (_project_slide.is_visible())
  1355. {
  1356. _project_slide.hide();
  1357. }
  1358. else
  1359. {
  1360. _project_slide.show_all();
  1361. }
  1362. }
  1363. private void on_console(GLib.SimpleAction action, GLib.Variant? param)
  1364. {
  1365. if (_console_view.is_visible())
  1366. {
  1367. if (_console_view._entry.has_focus)
  1368. _console_view.hide();
  1369. else
  1370. _console_view._entry.grab_focus();
  1371. }
  1372. else
  1373. {
  1374. _console_view.show_all();
  1375. }
  1376. }
  1377. private void on_statusbar(GLib.SimpleAction action, GLib.Variant? param)
  1378. {
  1379. if (_statusbar.is_visible())
  1380. {
  1381. _statusbar.hide();
  1382. }
  1383. else
  1384. {
  1385. _statusbar.show_all();
  1386. }
  1387. }
  1388. private void on_inspector(GLib.SimpleAction action, GLib.Variant? param)
  1389. {
  1390. if (_inspector_slide.is_visible())
  1391. {
  1392. _inspector_slide.hide();
  1393. }
  1394. else
  1395. {
  1396. _inspector_slide.show_all();
  1397. }
  1398. }
  1399. private void on_editor_restart(GLib.SimpleAction action, GLib.Variant? param)
  1400. {
  1401. restart_editor();
  1402. }
  1403. private void on_refresh_lua(GLib.SimpleAction action, GLib.Variant? param)
  1404. {
  1405. _data_compiler.compile.begin(_project.data_dir(), _project.platform(), (obj, res) => {
  1406. if (_data_compiler.compile.end(res))
  1407. {
  1408. _editor.send(DeviceApi.refresh());
  1409. _game.send(DeviceApi.refresh());
  1410. }
  1411. });
  1412. }
  1413. private void on_snap_to_grid(GLib.SimpleAction action, GLib.Variant? param)
  1414. {
  1415. _snap_to_grid = !action.get_state().get_boolean();
  1416. send_state();
  1417. action.set_state(new GLib.Variant.boolean(_snap_to_grid));
  1418. }
  1419. private void on_debug_render_world(GLib.SimpleAction action, GLib.Variant? param)
  1420. {
  1421. _debug_render_world = !action.get_state().get_boolean();
  1422. send_state();
  1423. action.set_state(new GLib.Variant.boolean(_debug_render_world));
  1424. }
  1425. private void on_debug_physics_world(GLib.SimpleAction action, GLib.Variant? param)
  1426. {
  1427. _debug_physics_world = !action.get_state().get_boolean();
  1428. send_state();
  1429. action.set_state(new GLib.Variant.boolean(_debug_physics_world));
  1430. }
  1431. private void on_run_game(GLib.SimpleAction action, GLib.Variant? param)
  1432. {
  1433. if (_game.is_connected())
  1434. {
  1435. stop_game();
  1436. }
  1437. else
  1438. {
  1439. // Always change icon state regardless of failures
  1440. _toolbar_run.icon_name = "game-stop";
  1441. start_game(action.name == "test-level" ? StartGame.TEST : StartGame.NORMAL);
  1442. }
  1443. }
  1444. private void on_undo(GLib.SimpleAction action, GLib.Variant? param)
  1445. {
  1446. int id = _database.undo();
  1447. if (id != -1)
  1448. _statusbar.set_temporary_message("Undo: " + ActionNames[id]);
  1449. }
  1450. private void on_redo(GLib.SimpleAction action, GLib.Variant? param)
  1451. {
  1452. int id = _database.redo();
  1453. if (id != -1)
  1454. _statusbar.set_temporary_message("Redo: " + ActionNames[id]);
  1455. }
  1456. private void on_duplicate(GLib.SimpleAction action, GLib.Variant? param)
  1457. {
  1458. _level.duplicate_selected_objects();
  1459. }
  1460. private void on_delete(GLib.SimpleAction action, GLib.Variant? param)
  1461. {
  1462. _level.destroy_selected_objects();
  1463. }
  1464. private void on_manual(GLib.SimpleAction action, GLib.Variant? param)
  1465. {
  1466. try
  1467. {
  1468. AppInfo.launch_default_for_uri("https://dbartolini.github.io/crown/html/v" + CROWN_VERSION, null);
  1469. }
  1470. catch (Error e)
  1471. {
  1472. loge(e.message);
  1473. }
  1474. }
  1475. private void on_report_issue(GLib.SimpleAction action, GLib.Variant? param)
  1476. {
  1477. try
  1478. {
  1479. AppInfo.launch_default_for_uri("https://github.com/dbartolini/crown/issues", null);
  1480. }
  1481. catch (Error e)
  1482. {
  1483. loge(e.message);
  1484. }
  1485. }
  1486. private void on_browse_logs(GLib.SimpleAction action, GLib.Variant? param)
  1487. {
  1488. open_directory(_logs_dir.get_path());
  1489. }
  1490. private void on_changelog(GLib.SimpleAction action, GLib.Variant? param)
  1491. {
  1492. try
  1493. {
  1494. AppInfo.launch_default_for_uri("https://dbartolini.github.io/crown/html/v" + CROWN_VERSION + "/changelog.html", null);
  1495. }
  1496. catch (Error e)
  1497. {
  1498. loge(e.message);
  1499. }
  1500. }
  1501. private void on_about(GLib.SimpleAction action, GLib.Variant? param)
  1502. {
  1503. Gtk.AboutDialog dlg = new Gtk.AboutDialog();
  1504. dlg.set_destroy_with_parent(true);
  1505. dlg.set_transient_for(this.active_window);
  1506. dlg.set_modal(true);
  1507. dlg.set_logo_icon_name("pepper");
  1508. dlg.program_name = "Crown Game Engine";
  1509. dlg.version = CROWN_VERSION;
  1510. dlg.website = "https://github.com/dbartolini/crown";
  1511. dlg.copyright = "Copyright (c) 2012-2020 Daniele Bartolini and individual contributors.";
  1512. dlg.license = "Crown Game Engine.\n"
  1513. + "Copyright (c) 2012-2020 Daniele Bartolini and individual contributors.\n"
  1514. + "\n"
  1515. + "This program is free software; you can redistribute it and/or\n"
  1516. + "modify it under the terms of the GNU General Public License\n"
  1517. + "as published by the Free Software Foundation; either version 2\n"
  1518. + "of the License, or (at your option) any later version.\n"
  1519. + "\n"
  1520. + "This program is distributed in the hope that it will be useful,\n"
  1521. + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
  1522. + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
  1523. + "GNU General Public License for more details.\n"
  1524. + "\n"
  1525. + "You should have received a copy of the GNU General Public License\n"
  1526. + "along with this program; if not, write to the Free Software\n"
  1527. + "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n"
  1528. ;
  1529. dlg.run();
  1530. dlg.destroy();
  1531. }
  1532. public void set_autosave_timer(uint minutes)
  1533. {
  1534. if (_save_timer_id > 0)
  1535. GLib.Source.remove(_save_timer_id);
  1536. _save_timer_id = GLib.Timeout.add_seconds(minutes*60, save_timeout);
  1537. }
  1538. }
  1539. // Global paths
  1540. public static GLib.File _config_dir;
  1541. public static GLib.File _logs_dir;
  1542. public static GLib.File _log_file;
  1543. public static GLib.File _settings_file;
  1544. public static GLib.FileStream _log_stream;
  1545. public static ConsoleView _console_view;
  1546. public static void log(string system, string severity, string message)
  1547. {
  1548. GLib.DateTime now = new GLib.DateTime.now_utc();
  1549. string line = "%s.%06d %.4s %s: %s\n".printf(now.format("%H:%M:%S")
  1550. , now.get_microsecond()
  1551. , severity.ascii_up()
  1552. , system
  1553. , message
  1554. );
  1555. if (_log_stream != null)
  1556. {
  1557. _log_stream.puts(line);
  1558. _log_stream.flush();
  1559. }
  1560. if (_console_view != null)
  1561. _console_view.log(severity, line);
  1562. }
  1563. public static void logi(string message)
  1564. {
  1565. log("editor", "info", message);
  1566. }
  1567. public static void logw(string message)
  1568. {
  1569. log("editor", "warn", message);
  1570. }
  1571. public static void loge(string message)
  1572. {
  1573. log("editor", "erro", message);
  1574. }
  1575. public void open_directory(string directory)
  1576. {
  1577. #if CROWN_PLATFORM_LINUX
  1578. try
  1579. {
  1580. GLib.AppInfo.launch_default_for_uri("file://" + directory, null);
  1581. }
  1582. catch (Error e)
  1583. {
  1584. loge(e.message);
  1585. }
  1586. #else
  1587. GLib.SubprocessLauncher sl = new GLib.SubprocessLauncher(subprocess_flags());
  1588. try
  1589. {
  1590. sl.spawnv({ "explorer.exe", directory, null });
  1591. }
  1592. catch (Error e)
  1593. {
  1594. loge(e.message);
  1595. }
  1596. #endif
  1597. }
  1598. public static GLib.SubprocessFlags subprocess_flags()
  1599. {
  1600. GLib.SubprocessFlags flags = SubprocessFlags.NONE;
  1601. #if !CROWN_DEBUG
  1602. flags |= SubprocessFlags.STDOUT_SILENCE | SubprocessFlags.STDERR_SILENCE;
  1603. #endif
  1604. return flags;
  1605. }
  1606. public static int main(string[] args)
  1607. {
  1608. Intl.setlocale(LocaleCategory.ALL, "C");
  1609. // Global paths
  1610. _config_dir = GLib.File.new_for_path(GLib.Path.build_filename(GLib.Environment.get_user_config_dir(), "crown"));
  1611. try { _config_dir.make_directory(); } catch (Error e) { /* Nobody cares */ }
  1612. _logs_dir = GLib.File.new_for_path(GLib.Path.build_filename(_config_dir.get_path(), "logs"));
  1613. try { _logs_dir.make_directory(); } catch (Error e) { /* Nobody cares */ }
  1614. _log_file = GLib.File.new_for_path(GLib.Path.build_filename(_logs_dir.get_path(), new GLib.DateTime.now_utc().format("%Y-%m-%d") + ".log"));
  1615. _settings_file = GLib.File.new_for_path(GLib.Path.build_filename(_config_dir.get_path(), "settings.sjson"));
  1616. _log_stream = GLib.FileStream.open(_log_file.get_path(), "a");
  1617. LevelEditorApplication app = new LevelEditorApplication();
  1618. return app.run(args);
  1619. }
  1620. }