level_editor.vala 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693
  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. public enum ToolType
  11. {
  12. PLACE,
  13. MOVE,
  14. ROTATE,
  15. SCALE
  16. }
  17. public enum SnapMode
  18. {
  19. RELATIVE,
  20. ABSOLUTE
  21. }
  22. public enum ReferenceSystem
  23. {
  24. LOCAL,
  25. WORLD
  26. }
  27. public enum StartGame
  28. {
  29. NORMAL,
  30. TEST
  31. }
  32. public class LevelEditor : Gtk.Window
  33. {
  34. // Constants
  35. const Gtk.ActionEntry[] action_entries =
  36. {
  37. { "menu-file", null, "_File", null, null, null },
  38. { "new-level", null, "New Level", "<ctrl>N", null, on_new_level },
  39. { "open-level", null, "Open Level...", "<ctrl>O", null, on_open_level },
  40. { "open-project", null, "Open Project...", null, null, on_open_project },
  41. { "save", null, "Save", "<ctrl>S", null, on_save },
  42. { "save-as", null, "Save As...", "<shift><ctrl>S", null, on_save_as },
  43. { "import", null, "Import", null, null, null },
  44. { "import-sprites", null, "Sprites...", null, null, on_import_sprites },
  45. { "import-meshes", null, "Meshes...", null, null, on_import_meshes },
  46. { "import-sounds", null, "Sounds...", null, null, on_import_sounds },
  47. { "import-textures", null, "Textures...", null, null, on_import_textures },
  48. { "preferences", null, "Preferences", null, null, on_preferences },
  49. { "deploy", null, "Deploy...", null, null, on_deploy },
  50. { "quit", null, "Quit", "<ctrl>Q", null, on_quit },
  51. { "menu-edit", null, "_Edit", null, null, null },
  52. { "undo", null, "Undo", "<ctrl>Z", null, on_undo },
  53. { "redo", null, "Redo", "<shift><ctrl>Z", null, on_redo },
  54. { "duplicate", null, "Duplicate", "<ctrl>D", null, on_duplicate },
  55. { "delete", null, "Delete", "<ctrl>K", null, on_delete },
  56. { "menu-grid", null, "Grid", null, null, null },
  57. { "grid-custom", null, "Custom", "<ctrl>G", null, on_custom_grid },
  58. { "menu-rotation-snap", null, "Rotation Snap", null, null, null },
  59. { "rotation-snap-custom", null, "Custom", "<ctrl>H", null, on_rotation_snap },
  60. { "menu-create", null, "Create", null, null, null },
  61. { "menu-primitives", null, "Primitives", null, null, null },
  62. { "primitive-cube", null, "Cube", null, null, on_create_primitive },
  63. { "primitive-sphere", null, "Sphere", null, null, on_create_primitive },
  64. { "primitive-cone", null, "Cone", null, null, on_create_primitive },
  65. { "primitive-cylinder", null, "Cylinder", null, null, on_create_primitive },
  66. { "primitive-plane", null, "Plane", null, null, on_create_primitive },
  67. { "camera", null, "Camera", null, null, on_create_primitive },
  68. { "light", null, "Light", null, null, on_create_primitive },
  69. { "sound-source", null, "Sound Source", null, null, on_create_primitive },
  70. { "menu-camera", null, "Camera", null, null, null },
  71. { "camera-view-perspective", null, "Perspective", "KP_5", null, on_camera_view },
  72. { "camera-view-front", null, "View Front", "KP_1", null, on_camera_view },
  73. { "camera-view-back", null, "View Back", "<ctrl>KP_1", null, on_camera_view },
  74. { "camera-view-right", null, "View Right", "KP_3", null, on_camera_view },
  75. { "camera-view-left", null, "View Left", "<ctrl>KP_3", null, on_camera_view },
  76. { "camera-view-top", null, "View Top", "KP_7", null, on_camera_view },
  77. { "camera-view-bottom", null, "View Bottom", "<ctrl>KP_7", null, on_camera_view },
  78. { "menu-engine", null, "En_gine", null, null, null },
  79. { "menu-view", null, "View", null, null, null },
  80. { "resource-browser", null, "Resource Browser", "<ctrl>P", null, on_resource_browser },
  81. { "console", null, "Show/Hide Console", "<ctrl>quoteleft",null, on_console },
  82. { "fullscreen", null, "Fullscreen", "F11", null, on_fullscreen },
  83. { "restart", null, "_Restart", null, null, on_engine_restart },
  84. { "reload-lua", null, "Reload Level Editor", "F7", null, on_reload_lua },
  85. { "menu-run", null, "_Run", null, null, null },
  86. { "test-level", "game-run", "Test Level", "F5", "Test Level", on_run_game },
  87. { "run-game", null, "Run Game", null, null, on_run_game },
  88. { "menu-help", null, "Help", null, null, null },
  89. { "manual", null, "Manual", "F1", null, on_manual },
  90. { "report-issue", null, "Report an Issue...", null, null, on_report_issue },
  91. { "open-last-log", null, "Open last.log", null, null, on_open_last_log },
  92. { "changelog", null, "Changelog...", null, null, on_changelog },
  93. { "about", null, "About", null, null, on_about }
  94. };
  95. const Gtk.RadioActionEntry[] grid_entries =
  96. {
  97. { "grid-0.1", null, "0.1m", null, null, 10 },
  98. { "grid-0.2", null, "0.2m", null, null, 20 },
  99. { "grid-0.5", null, "0.5m", null, null, 50 },
  100. { "grid-1", null, "1m", null, null, 100 },
  101. { "grid-2", null, "2m", null, null, 200 },
  102. { "grid-5", null, "5m", null, null, 500 }
  103. };
  104. const Gtk.RadioActionEntry[] rotation_snap_entries =
  105. {
  106. { "rotation-snap-1", null, "1°", null, null, 1 },
  107. { "rotation-snap-15", null, "15°", null, null, 15 },
  108. { "rotation-snap-30", null, "30°", null, null, 30 },
  109. { "rotation-snap-45", null, "45°", null, null, 45 },
  110. { "rotation-snap-90", null, "90°", null, null, 90 },
  111. { "rotation-snap-180", null, "180°", null, null, 180 }
  112. };
  113. const Gtk.RadioActionEntry[] tool_entries =
  114. {
  115. { "place", "tool-place", "Place", null, "Place", (int)ToolType.PLACE },
  116. { "move", "tool-move", "Move", null, "Move", (int)ToolType.MOVE },
  117. { "rotate", "tool-rotate", "Rotate", null, "Rotate", (int)ToolType.ROTATE },
  118. { "scale", "tool-scale", "Scale", null, "Scale", (int)ToolType.SCALE }
  119. };
  120. const Gtk.RadioActionEntry[] snap_mode_entries =
  121. {
  122. { "snap-relative", "reference-local", "Relative Snap", null, "Relative Snap", (int)SnapMode.RELATIVE },
  123. { "snap-absolute", "reference-world", "Absolute Snap", null, "Absolute Snap", (int)SnapMode.ABSOLUTE }
  124. };
  125. const Gtk.RadioActionEntry[] reference_system_entries =
  126. {
  127. { "reference-system-local", "axis-local", "Local Axis", null, "Local Axis", (int)ReferenceSystem.LOCAL },
  128. { "reference-system-world", "axis-world", "World Axis", null, "World Axis", (int)ReferenceSystem.WORLD }
  129. };
  130. const Gtk.ToggleActionEntry[] snap_to_entries =
  131. {
  132. { "snap-to-grid", "snap-to-grid", "Snap To Grid", "<ctrl>U", "Snap To Grid", on_snap_to_grid, true },
  133. { "grid-show", null, "Show Grid", null, "Show Grid", on_show_grid, true }
  134. };
  135. const Gtk.ToggleActionEntry[] view_entries =
  136. {
  137. { "debug-render-world", null, "Debug Render World", null, null, on_debug_render_world, false },
  138. { "debug-physics-world", null, "Debug Physics World", null, null, on_debug_physics_world, false }
  139. };
  140. // Editor state
  141. private double _grid_size;
  142. private double _rotation_snap;
  143. private bool _show_grid;
  144. private bool _snap_to_grid;
  145. private bool _debug_render_world;
  146. private bool _debug_physics_world;
  147. private ToolType _tool_type;
  148. private SnapMode _snap_mode;
  149. private ReferenceSystem _reference_system;
  150. // Engine connections
  151. private GLib.Subprocess _compiler_process;
  152. private GLib.Subprocess _engine_process;
  153. private GLib.Subprocess _game_process;
  154. private ConsoleClient _compiler;
  155. private ConsoleClient _engine;
  156. private ConsoleClient _game;
  157. // Level data
  158. private Database _database;
  159. private Project _project;
  160. private ProjectStore _project_store;
  161. private Level _level;
  162. private DataCompiler _data_compiler;
  163. // Widgets
  164. private ConsoleView _console_view;
  165. private EngineView _engine_view;
  166. private LevelTreeView _level_treeview;
  167. private LevelLayersTreeView _level_layers_treeview;
  168. private PropertiesView _properties_view;
  169. private PreferencesDialog _preferences_dialog;
  170. private ResourceBrowser _resource_browser;
  171. private ResourceBrowser _resource_selection;
  172. private Gtk.Popover _resource_popover;
  173. private Gtk.Overlay _engine_view_overlay;
  174. private Slide _engine_slide;
  175. private Slide _inspector_slide;
  176. private Gtk.ActionGroup _action_group;
  177. private Gtk.UIManager _ui_manager;
  178. private Gtk.MenuBar _menubar;
  179. private Gtk.Toolbar _toolbar;
  180. private Gtk.Paned _engine_pane;
  181. private Gtk.Notebook _level_tree_view_notebook;
  182. private Gtk.Paned _inspector_pane;
  183. private Gtk.Paned _main_pane;
  184. private Gtk.Box _main_vbox;
  185. private Gtk.FileFilter _file_filter;
  186. private bool _fullscreen;
  187. public LevelEditor(Database database, Project project, Level level, ConsoleClient compiler, ConsoleClient engine, ConsoleClient game)
  188. {
  189. this.title = "Level Editor";
  190. // Editor state
  191. _grid_size = 1.0;
  192. _rotation_snap = 15.0;
  193. _show_grid = true;
  194. _snap_to_grid = true;
  195. _debug_render_world = false;
  196. _debug_physics_world = false;
  197. _tool_type = ToolType.MOVE;
  198. _snap_mode = SnapMode.RELATIVE;
  199. _reference_system = ReferenceSystem.LOCAL;
  200. // Engine connections
  201. _compiler_process = null;
  202. _engine_process = null;
  203. _game_process = null;
  204. _compiler = compiler;
  205. _compiler.connected.connect(on_compiler_connected);
  206. _compiler.disconnected.connect(on_compiler_disconnected);
  207. _compiler.message_received.connect(on_message_received);
  208. _engine = engine;
  209. _engine.connected.connect(on_engine_connected);
  210. _engine.disconnected.connect(on_engine_disconnected);
  211. _engine.message_received.connect(on_message_received);
  212. _game = game;
  213. _game.connected.connect(on_game_connected);
  214. _game.disconnected.connect(on_game_disconnected);
  215. _game.message_received.connect(on_message_received);
  216. _data_compiler = new DataCompiler(_compiler);
  217. _database = database;
  218. _project = project;
  219. _project_store = new ProjectStore(_project);
  220. _level = level;
  221. // Widgets
  222. _resource_browser = new ResourceBrowser(_project, _project_store, true);
  223. _resource_browser.resource_selected.connect(on_resource_browser_resource_selected);
  224. _resource_selection = new ResourceBrowser(_project, _project_store, false);
  225. _console_view = new ConsoleView(_engine, _project);
  226. _level_treeview = new LevelTreeView(_database, _level);
  227. _level_layers_treeview = new LevelLayersTreeView(_database, _level);
  228. _properties_view = new PropertiesView(_level, _project_store);
  229. _engine_slide = new Slide();
  230. _engine_slide.show_widget(new StartingCompiler());
  231. _inspector_slide = new Slide();
  232. _inspector_slide.show_widget(new StartingCompiler());
  233. _action_group = new Gtk.ActionGroup("group");
  234. _action_group.add_actions(action_entries, this);
  235. _action_group.add_radio_actions(grid_entries, (int)(_grid_size*100.0), this.on_grid_changed);
  236. _action_group.add_radio_actions(rotation_snap_entries, (int)_rotation_snap, this.on_rotation_snap_changed);
  237. _action_group.add_radio_actions(tool_entries, (int)_tool_type, on_tool_changed);
  238. _action_group.add_radio_actions(snap_mode_entries, (int)_snap_mode, on_snap_mode_changed);
  239. _action_group.add_radio_actions(reference_system_entries, (int)_reference_system, on_reference_system_changed);
  240. _action_group.add_toggle_actions(snap_to_entries, this);
  241. _action_group.add_toggle_actions(view_entries, this);
  242. _ui_manager = new UIManager();
  243. try
  244. {
  245. _ui_manager.add_ui_from_resource("/org/crown/level_editor/level_editor.xml");
  246. _ui_manager.insert_action_group(_action_group, 0);
  247. add_accel_group(_ui_manager.get_accel_group());
  248. }
  249. catch (Error e)
  250. {
  251. error(e.message);
  252. }
  253. _menubar = _ui_manager.get_widget("/menubar") as Gtk.MenuBar;
  254. _toolbar = _ui_manager.get_widget("/toolbar") as Gtk.Toolbar;
  255. _toolbar.set_icon_size(Gtk.IconSize.SMALL_TOOLBAR);
  256. _toolbar.set_style(Gtk.ToolbarStyle.ICONS);
  257. _toolbar.halign = Gtk.Align.START;
  258. _toolbar.valign = Gtk.Align.START;
  259. _toolbar.orientation = Gtk.Orientation.VERTICAL;
  260. _toolbar.margin_start = 6;
  261. _toolbar.margin_top = 6;
  262. _resource_popover = new Gtk.Popover(_toolbar);
  263. _resource_popover.delete_event.connect(() => { _resource_popover.hide(); return true; });
  264. _resource_popover.modal = true;
  265. _resource_browser.resource_selected.connect(() => { _resource_popover.hide(); });
  266. _resource_popover.add(_resource_browser);
  267. _level_tree_view_notebook = new Notebook();
  268. _level_tree_view_notebook.show_border = false;
  269. _level_tree_view_notebook.append_page(_level_treeview, new Gtk.Image.from_icon_name("level-tree", IconSize.SMALL_TOOLBAR));
  270. _level_tree_view_notebook.append_page(_level_layers_treeview, new Gtk.Image.from_icon_name("level-layers", IconSize.SMALL_TOOLBAR));
  271. _engine_view_overlay = new Gtk.Overlay();
  272. _engine_pane = new Gtk.Paned(Gtk.Orientation.VERTICAL);
  273. _engine_pane.pack1(_engine_slide, true, true);
  274. _engine_pane.pack2(_console_view, true, true);
  275. _inspector_pane = new Gtk.Paned(Gtk.Orientation.VERTICAL);
  276. _inspector_pane.pack1(_level_tree_view_notebook, true, true);
  277. _inspector_pane.pack2(_properties_view, true, true);
  278. _main_pane = new Gtk.Paned(Gtk.Orientation.HORIZONTAL);
  279. _main_pane.pack1(_engine_pane, true, false);
  280. _main_pane.pack2(_inspector_slide, true, false);
  281. _main_vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
  282. _main_vbox.pack_start(_menubar, false, false, 0);
  283. _main_vbox.pack_start(_main_pane, true, true, 0);
  284. _file_filter = new FileFilter();
  285. _file_filter.set_filter_name("Level (*.level)");
  286. _file_filter.add_pattern("*.level");
  287. // Save level once every 5 minutes.
  288. GLib.Timeout.add_seconds(5*3600, save_timeout);
  289. _fullscreen = false;
  290. this.destroy.connect(this.on_destroy);
  291. this.delete_event.connect(this.on_delete_event);
  292. this.key_press_event.connect(this.on_key_press);
  293. this.key_release_event.connect(this.on_key_release);
  294. this.window_state_event.connect(this.on_window_state_event);
  295. this.add(_main_vbox);
  296. this.maximize();
  297. this.show_all();
  298. start_compiler();
  299. // This is supposed to work in pixel units but it doesn't for
  300. // a reason I couldn't find. The values passed, though, seem
  301. // to move pane handles to the correct-ish position.
  302. _main_pane.set_position(400);
  303. _engine_pane.set_position(72);
  304. _inspector_pane.set_position(300);
  305. }
  306. private bool on_key_press(Gdk.EventKey ev)
  307. {
  308. if (ev.keyval == Gdk.Key.Control_L)
  309. _engine.send_script(LevelEditorApi.key_down("ctrl_left"));
  310. else if (ev.keyval == Gdk.Key.Shift_L)
  311. _engine.send_script(LevelEditorApi.key_down("shift_left"));
  312. else if (ev.keyval == Gdk.Key.Alt_L)
  313. _engine.send_script(LevelEditorApi.key_down("alt_left"));
  314. return false;
  315. }
  316. private bool on_key_release(Gdk.EventKey ev)
  317. {
  318. if (ev.keyval == Gdk.Key.Control_L)
  319. _engine.send_script(LevelEditorApi.key_up("ctrl_left"));
  320. else if (ev.keyval == Gdk.Key.Shift_L)
  321. _engine.send_script(LevelEditorApi.key_up("shift_left"));
  322. else if (ev.keyval == Gdk.Key.Alt_L)
  323. _engine.send_script(LevelEditorApi.key_up("alt_left"));
  324. return false;
  325. }
  326. private bool on_window_state_event(EventWindowState ev)
  327. {
  328. _fullscreen = (ev.new_window_state & WindowState.FULLSCREEN) != 0;
  329. return true;
  330. }
  331. private void on_resource_browser_resource_selected(string type, string name)
  332. {
  333. _engine.send_script(LevelEditorApi.set_placeable(type, name));
  334. _action_group.get_action("place").activate();
  335. }
  336. private void on_compiler_connected(string address, int port)
  337. {
  338. _console_view.logi("editor", "Connected to data_compiler@%s:%d".printf(address, port));
  339. _compiler.receive_async();
  340. }
  341. private void on_compiler_disconnected()
  342. {
  343. _console_view.logi("editor", "Disconnected from data_compiler");
  344. }
  345. private void on_engine_connected(string address, int port)
  346. {
  347. _console_view.logi("editor", "Connected to level_editor@%s:%d".printf(address, port));
  348. _engine.receive_async();
  349. }
  350. private void on_engine_disconnected()
  351. {
  352. _console_view.logi("editor", "Disconnected from level_editor");
  353. }
  354. private void on_game_connected(string address, int port)
  355. {
  356. _console_view.logi("editor", "Connected to game@%s:%d".printf(address, port));
  357. _game.receive_async();
  358. }
  359. private void on_game_disconnected()
  360. {
  361. _console_view.logi("editor", "Disconnected from game");
  362. _action_group.get_action("test-level").icon_name = "game-run";
  363. _project.delete_level_editor_test_level();
  364. }
  365. private void on_message_received(ConsoleClient client, uint8[] json)
  366. {
  367. Hashtable msg = JSON.decode(json) as Hashtable;
  368. string msg_type = msg["type"] as string;
  369. if (msg_type == "message")
  370. {
  371. _console_view.log((string)msg["system"], (string)msg["message"], (string)msg["severity"]);
  372. }
  373. else if (msg_type == "add_file")
  374. {
  375. string path = (string)msg["path"];
  376. _project.add_file(path);
  377. }
  378. else if (msg_type == "remove_file")
  379. {
  380. string path = (string)msg["path"];
  381. _project.remove_file(path);
  382. }
  383. else if (msg_type == "compile")
  384. {
  385. // Guid id = Guid.parse((string)msg["id"]);
  386. if (msg.has_key("start"))
  387. {
  388. // FIXME
  389. }
  390. else if (msg.has_key("success"))
  391. {
  392. _data_compiler.finished((bool)msg["success"]);
  393. }
  394. }
  395. else if (msg_type == "unit_spawned")
  396. {
  397. string id = (string) msg["id"];
  398. string name = (string) msg["name"];
  399. ArrayList<Value?> pos = (ArrayList<Value?>)msg["position"];
  400. ArrayList<Value?> rot = (ArrayList<Value?>)msg["rotation"];
  401. ArrayList<Value?> scl = (ArrayList<Value?>)msg["scale"];
  402. _level.on_unit_spawned(Guid.parse(id)
  403. , name
  404. , Vector3.from_array(pos)
  405. , Quaternion.from_array(rot)
  406. , Vector3.from_array(scl)
  407. );
  408. }
  409. else if (msg_type == "sound_spawned")
  410. {
  411. string id = (string) msg["id"];
  412. string name = (string) msg["name"];
  413. ArrayList<Value?> pos = (ArrayList<Value?>)msg["position"];
  414. ArrayList<Value?> rot = (ArrayList<Value?>)msg["rotation"];
  415. ArrayList<Value?> scl = (ArrayList<Value?>)msg["scale"];
  416. double range = (double) msg["range"];
  417. double volume = (double) msg["volume"];
  418. bool loop = (bool) msg["loop"];
  419. _level.on_sound_spawned(Guid.parse(id)
  420. , name
  421. , Vector3.from_array(pos)
  422. , Quaternion.from_array(rot)
  423. , Vector3.from_array(scl)
  424. , range
  425. , volume
  426. , loop
  427. );
  428. }
  429. else if (msg_type == "move_objects")
  430. {
  431. Hashtable ids = (Hashtable)msg["ids"];
  432. Hashtable new_positions = (Hashtable)msg["new_positions"];
  433. Hashtable new_rotations = (Hashtable)msg["new_rotations"];
  434. Hashtable new_scales = (Hashtable)msg["new_scales"];
  435. ArrayList<string> keys = new ArrayList<string>.wrap(ids.keys.to_array());
  436. keys.sort(Gee.Functions.get_compare_func_for(typeof(string)));
  437. Guid[] n_ids = new Guid[keys.size];
  438. Vector3[] n_positions = new Vector3[keys.size];
  439. Quaternion[] n_rotations = new Quaternion[keys.size];
  440. Vector3[] n_scales = new Vector3[keys.size];
  441. for (int i = 0; i < keys.size; ++i)
  442. {
  443. string k = keys[i];
  444. n_ids[i] = Guid.parse((string)ids[k]);
  445. n_positions[i] = Vector3.from_array((ArrayList<Value?>)(new_positions[k]));
  446. n_rotations[i] = Quaternion.from_array((ArrayList<Value?>)new_rotations[k]);
  447. n_scales[i] = Vector3.from_array((ArrayList<Value?>)new_scales[k]);
  448. }
  449. _level.on_move_objects(n_ids, n_positions, n_rotations, n_scales);
  450. }
  451. else if (msg_type == "selection")
  452. {
  453. Hashtable objects = (Hashtable)msg["objects"];
  454. ArrayList<string> keys = new ArrayList<string>.wrap(objects.keys.to_array());
  455. keys.sort(Gee.Functions.get_compare_func_for(typeof(string)));
  456. Guid[] ids = new Guid[keys.size];
  457. for (int i = 0; i < keys.size; ++i)
  458. {
  459. string k = keys[i];
  460. ids[i] = Guid.parse((string)objects[k]);
  461. }
  462. _level.on_selection(ids);
  463. }
  464. else if (msg_type == "error")
  465. {
  466. _console_view.loge("editor", "Error: " + (string)msg["message"]);
  467. }
  468. else
  469. {
  470. _console_view.loge("editor", "Unknown message type: " + msg_type);
  471. }
  472. // Receive next message
  473. client.receive_async();
  474. }
  475. private void send_state()
  476. {
  477. StringBuilder sb = new StringBuilder();
  478. sb.append(LevelEditorApi.set_grid_size(_grid_size));
  479. sb.append(LevelEditorApi.set_rotation_snap(_rotation_snap));
  480. sb.append(LevelEditorApi.enable_show_grid(_show_grid));
  481. sb.append(LevelEditorApi.enable_snap_to_grid(_snap_to_grid));
  482. sb.append(LevelEditorApi.enable_debug_render_world(_debug_render_world));
  483. sb.append(LevelEditorApi.enable_debug_physics_world(_debug_physics_world));
  484. sb.append(LevelEditorApi.set_tool_type(_tool_type));
  485. sb.append(LevelEditorApi.set_snap_mode(_snap_mode));
  486. sb.append(LevelEditorApi.set_reference_system(_reference_system));
  487. _engine.send_script(sb.str);
  488. }
  489. private bool on_button_press(EventButton ev)
  490. {
  491. // Prevent accelerators to step on camera's toes
  492. remove_accel_group(_ui_manager.get_accel_group());
  493. return true;
  494. }
  495. private bool on_button_release(EventButton ev)
  496. {
  497. add_accel_group(_ui_manager.get_accel_group());
  498. return true;
  499. }
  500. private void start_compiler()
  501. {
  502. string args[] =
  503. {
  504. ENGINE_EXE,
  505. "--source-dir", _project.source_dir(),
  506. "--data-dir", _project.data_dir(),
  507. "--map-source-dir", "core", _project.toolchain_dir(),
  508. "--server",
  509. "--wait-console",
  510. null
  511. };
  512. GLib.SubprocessLauncher sl = new GLib.SubprocessLauncher(subprocess_flags());
  513. sl.set_cwd(ENGINE_DIR);
  514. try
  515. {
  516. _compiler_process = sl.spawnv(args);
  517. }
  518. catch (Error e)
  519. {
  520. _console_view.loge("editor", e.message);
  521. }
  522. for (int tries = 0; !_compiler.is_connected() && tries < 5; ++tries)
  523. {
  524. _compiler.connect("127.0.0.1", CROWN_DEFAULT_SERVER_PORT);
  525. GLib.Thread.usleep(250*1000);
  526. }
  527. _data_compiler.compile.begin(_project.data_dir(), _project.platform(), (obj, res) => {
  528. if (_data_compiler.compile.end(res))
  529. {
  530. if (_engine_view != null)
  531. return;
  532. _engine_view = new EngineView(_engine);
  533. _engine_view.realized.connect(on_engine_view_realized);
  534. _engine_view.button_press_event.connect(on_button_press);
  535. _engine_view.button_release_event.connect(on_button_release);
  536. _engine_view_overlay.add(_engine_view);
  537. _engine_view_overlay.add_overlay(_toolbar);
  538. _engine_slide.show_widget(_engine_view_overlay);
  539. _inspector_slide.show_widget(_inspector_pane);
  540. }
  541. });
  542. }
  543. private void stop_compiler()
  544. {
  545. _compiler.send(DataCompilerApi.quit());
  546. _compiler.close();
  547. if (_compiler_process != null)
  548. {
  549. try
  550. {
  551. _compiler_process.wait();
  552. }
  553. catch (Error e)
  554. {
  555. _console_view.loge("editor", e.message);
  556. }
  557. }
  558. }
  559. private void start_engine(uint window_xid)
  560. {
  561. if (window_xid == 0)
  562. return;
  563. string args[] =
  564. {
  565. ENGINE_EXE,
  566. "--data-dir", _project.data_dir(),
  567. "--boot-dir", LEVEL_EDITOR_BOOT_DIR,
  568. "--parent-window", window_xid.to_string(),
  569. "--wait-console",
  570. null
  571. };
  572. GLib.SubprocessLauncher sl = new GLib.SubprocessLauncher(subprocess_flags());
  573. sl.set_cwd(ENGINE_DIR);
  574. try
  575. {
  576. _engine_process = sl.spawnv(args);
  577. }
  578. catch (Error e)
  579. {
  580. _console_view.loge("editor", e.message);
  581. }
  582. for (int tries = 0; !_engine.is_connected() && tries < 10; ++tries)
  583. {
  584. _engine.connect("127.0.0.1", 10001);
  585. GLib.Thread.usleep(500*1000);
  586. }
  587. _level.send_level();
  588. send_state();
  589. }
  590. private void stop_engine()
  591. {
  592. _engine.send_script("Device.quit()");
  593. _engine.close();
  594. if (_engine_process != null)
  595. {
  596. try
  597. {
  598. _engine_process.wait();
  599. }
  600. catch (Error e)
  601. {
  602. _console_view.loge("editor", e.message);
  603. }
  604. }
  605. }
  606. private void restart_engine()
  607. {
  608. stop_engine();
  609. start_engine(_engine_view.window_id);
  610. }
  611. private void start_game(StartGame sg)
  612. {
  613. _database.dump(_project.level_editor_test_level());
  614. _data_compiler.compile.begin(_project.data_dir(), _project.platform(), (obj, res) => {
  615. if (_data_compiler.compile.end(res))
  616. {
  617. string args[] =
  618. {
  619. ENGINE_EXE,
  620. "--data-dir", _project.data_dir(),
  621. "--console-port", "12345",
  622. "--wait-console",
  623. "--lua-string", sg == StartGame.TEST ? "TEST=true" : "",
  624. null
  625. };
  626. GLib.SubprocessLauncher sl = new GLib.SubprocessLauncher(subprocess_flags());
  627. sl.set_cwd(ENGINE_DIR);
  628. try
  629. {
  630. _game_process = sl.spawnv(args);
  631. }
  632. catch (Error e)
  633. {
  634. _console_view.loge("editor", e.message);
  635. }
  636. for (int tries = 0; !_game.is_connected() && tries < 10; ++tries)
  637. {
  638. _game.connect("127.0.0.1", 12345);
  639. GLib.Thread.usleep(500*1000);
  640. }
  641. }
  642. });
  643. }
  644. private void stop_game()
  645. {
  646. _game.send_script("Device.quit()");
  647. _game.close();
  648. if (_game_process != null)
  649. {
  650. try
  651. {
  652. _game_process.wait();
  653. }
  654. catch (Error e)
  655. {
  656. _console_view.loge("editor", e.message);
  657. }
  658. }
  659. }
  660. private void deploy_game()
  661. {
  662. FileChooserDialog fcd = new FileChooserDialog("Select destination directory..."
  663. , null
  664. , FileChooserAction.SELECT_FOLDER
  665. , "Cancel"
  666. , ResponseType.CANCEL
  667. , "Open"
  668. , ResponseType.ACCEPT
  669. );
  670. if (fcd.run() == (int)ResponseType.ACCEPT)
  671. {
  672. GLib.File data_dir = File.new_for_path(fcd.get_filename());
  673. string args[] =
  674. {
  675. ENGINE_EXE,
  676. "--source-dir", _project.source_dir(),
  677. "--map-source-dir", "core", _project.toolchain_dir(),
  678. "--data-dir", data_dir.get_path(),
  679. "--compile",
  680. null
  681. };
  682. GLib.SubprocessLauncher sl = new GLib.SubprocessLauncher(subprocess_flags());
  683. sl.set_cwd(ENGINE_DIR);
  684. try
  685. {
  686. GLib.Subprocess compiler = sl.spawnv(args);
  687. compiler.wait();
  688. if (compiler.get_exit_status() == 0)
  689. {
  690. GLib.File engine_exe = File.new_for_path(ENGINE_EXE);
  691. GLib.File engine_exe_dest = File.new_for_path(data_dir.get_path() + "/" + ENGINE_EXE);
  692. engine_exe.copy(engine_exe_dest, FileCopyFlags.OVERWRITE);
  693. _console_view.logi("editor", "Project deployed to `%s`".printf(data_dir.get_path()));
  694. }
  695. }
  696. catch (Error e)
  697. {
  698. _console_view.logi("editor", "%s".printf(e.message));
  699. _console_view.logi("editor", "Failed to deploy project");
  700. }
  701. }
  702. fcd.destroy();
  703. }
  704. private void on_engine_view_realized()
  705. {
  706. start_engine(_engine_view.window_id);
  707. }
  708. private void on_tool_changed(Gtk.Action action)
  709. {
  710. RadioAction ra = (RadioAction)action;
  711. _tool_type = (ToolType)ra.current_value;
  712. send_state();
  713. }
  714. private void on_snap_mode_changed(Gtk.Action action)
  715. {
  716. RadioAction ra = (RadioAction)action;
  717. _snap_mode = (SnapMode)ra.current_value;
  718. send_state();
  719. }
  720. private void on_reference_system_changed(Gtk.Action action)
  721. {
  722. RadioAction ra = (RadioAction)action;
  723. _reference_system = (ReferenceSystem)ra.current_value;
  724. send_state();
  725. }
  726. private void on_grid_changed(Gtk.Action action)
  727. {
  728. RadioAction ra = (RadioAction)action;
  729. _grid_size = (float)ra.current_value/100.0;
  730. send_state();
  731. }
  732. private void on_rotation_snap_changed(Gtk.Action action)
  733. {
  734. RadioAction ra = (RadioAction)action;
  735. _rotation_snap = (float)ra.current_value;
  736. send_state();
  737. }
  738. private void new_level()
  739. {
  740. _level.load_empty_level();
  741. _level.send_level();
  742. }
  743. private void load_level()
  744. {
  745. FileChooserDialog fcd = new FileChooserDialog("Open Level..."
  746. , this
  747. , FileChooserAction.OPEN
  748. , "Cancel"
  749. , ResponseType.CANCEL
  750. , "Open"
  751. , ResponseType.ACCEPT
  752. );
  753. fcd.add_filter(_file_filter);
  754. fcd.set_current_folder(_project.source_dir());
  755. if (fcd.run() == (int)ResponseType.ACCEPT)
  756. {
  757. string filename = fcd.get_filename();
  758. if (filename.has_prefix(_project.source_dir()))
  759. {
  760. if (filename.has_suffix(".level") && filename != _level._filename)
  761. {
  762. _level.load(filename);
  763. _level.send_level();
  764. send_state();
  765. }
  766. }
  767. else
  768. {
  769. _console_view.loge("editor", "Level not loaded: file must be within `%s`".printf(_project.source_dir()));
  770. }
  771. }
  772. fcd.destroy();
  773. }
  774. private void load_project()
  775. {
  776. FileChooserDialog fcd = new FileChooserDialog("Open Project..."
  777. , this
  778. , FileChooserAction.SELECT_FOLDER
  779. , "Cancel"
  780. , ResponseType.CANCEL
  781. , "Open"
  782. , ResponseType.ACCEPT
  783. );
  784. if (fcd.run() == (int)ResponseType.ACCEPT)
  785. {
  786. string filename = fcd.get_filename();
  787. _console_view.logi("editor", "Loading project `%s`...".printf(filename));
  788. _project.load(filename, _project.toolchain_dir());
  789. _level.load_empty_level();
  790. stop_compiler();
  791. start_compiler();
  792. restart_engine();
  793. _resource_browser.restart_engine();
  794. }
  795. fcd.destroy();
  796. }
  797. private bool save_as()
  798. {
  799. bool saved = false;
  800. FileChooserDialog fcd = new FileChooserDialog("Save As..."
  801. , this
  802. , FileChooserAction.SAVE
  803. , "Cancel"
  804. , ResponseType.CANCEL
  805. , "Save"
  806. , ResponseType.ACCEPT
  807. );
  808. fcd.add_filter(_file_filter);
  809. fcd.set_current_folder(_project.source_dir());
  810. if (fcd.run() == (int)ResponseType.ACCEPT)
  811. {
  812. string filename = fcd.get_filename();
  813. if (filename.has_prefix(_project.source_dir()))
  814. {
  815. _level.save(filename.has_suffix(".level") ? filename : filename + ".level");
  816. saved = true;
  817. }
  818. else
  819. {
  820. _console_view.loge("editor", "Level not saved: file must be within `%s`".printf(_project.source_dir()));
  821. }
  822. }
  823. fcd.destroy();
  824. return saved;
  825. }
  826. private bool save()
  827. {
  828. bool saved = false;
  829. if (_level._filename == null)
  830. {
  831. saved = save_as();
  832. }
  833. else
  834. {
  835. _level.save(_level._filename);
  836. saved = true;
  837. }
  838. return saved;
  839. }
  840. private bool save_timeout()
  841. {
  842. if (_level._filename != null)
  843. save();
  844. return true;
  845. }
  846. private void shutdown()
  847. {
  848. if (_resource_browser != null)
  849. _resource_browser.destroy();
  850. if (_preferences_dialog != null)
  851. _preferences_dialog.destroy();
  852. stop_game();
  853. stop_engine();
  854. stop_compiler();
  855. Gtk.main_quit();
  856. }
  857. private void quit()
  858. {
  859. if (!_database.changed())
  860. {
  861. shutdown();
  862. return;
  863. }
  864. Gtk.MessageDialog md = new Gtk.MessageDialog(this
  865. , Gtk.DialogFlags.MODAL
  866. , Gtk.MessageType.WARNING
  867. , Gtk.ButtonsType.NONE
  868. , "File changed, save?"
  869. );
  870. md.add_button("Quit _without Saving", ResponseType.NO);
  871. md.add_button("_Cancel", ResponseType.CANCEL);
  872. md.add_button("_Save", ResponseType.YES);
  873. md.set_default_response(ResponseType.YES);
  874. int rt = md.run();
  875. md.destroy();
  876. if (rt == (int)ResponseType.YES && save() || rt == (int)ResponseType.NO)
  877. shutdown();
  878. }
  879. private void on_new_level()
  880. {
  881. if (!_database.changed())
  882. {
  883. new_level();
  884. send_state();
  885. return;
  886. }
  887. Gtk.MessageDialog md = new Gtk.MessageDialog(this
  888. , Gtk.DialogFlags.MODAL
  889. , Gtk.MessageType.WARNING
  890. , Gtk.ButtonsType.NONE
  891. , "File changed, save?"
  892. );
  893. md.add_button("New _without Saving", ResponseType.NO);
  894. md.add_button("_Cancel", ResponseType.CANCEL);
  895. md.add_button("_Save", ResponseType.YES);
  896. md.set_default_response(ResponseType.YES);
  897. int rt = md.run();
  898. md.destroy();
  899. if (rt == (int)ResponseType.YES && save() || rt == (int)ResponseType.NO)
  900. {
  901. new_level();
  902. send_state();
  903. }
  904. }
  905. private void on_open_level(Gtk.Action action)
  906. {
  907. if (!_database.changed())
  908. {
  909. load_level();
  910. return;
  911. }
  912. Gtk.MessageDialog md = new Gtk.MessageDialog(this
  913. , Gtk.DialogFlags.MODAL
  914. , Gtk.MessageType.WARNING
  915. , Gtk.ButtonsType.NONE
  916. , "File changed, save?"
  917. );
  918. md.add_button("Open _without Saving", ResponseType.NO);
  919. md.add_button("_Cancel", ResponseType.CANCEL);
  920. md.add_button("_Save", ResponseType.YES);
  921. md.set_default_response(ResponseType.YES);
  922. int rt = md.run();
  923. md.destroy();
  924. if (rt == (int)ResponseType.YES && save() || rt == (int)ResponseType.NO)
  925. load_level();
  926. }
  927. private void on_open_project(Gtk.Action action)
  928. {
  929. if (!_database.changed())
  930. {
  931. load_project();
  932. return;
  933. }
  934. Gtk.MessageDialog md = new Gtk.MessageDialog(this
  935. , Gtk.DialogFlags.MODAL
  936. , Gtk.MessageType.WARNING
  937. , Gtk.ButtonsType.NONE
  938. , "File changed, save?"
  939. );
  940. md.add_button("Open _without Saving", ResponseType.NO);
  941. md.add_button("_Cancel", ResponseType.CANCEL);
  942. md.add_button("_Save", ResponseType.YES);
  943. md.set_default_response(ResponseType.YES);
  944. int rt = md.run();
  945. md.destroy();
  946. if (rt == (int)ResponseType.YES && save() || rt == (int)ResponseType.NO)
  947. load_project();
  948. }
  949. private void on_save(Gtk.Action action)
  950. {
  951. save();
  952. }
  953. private void on_save_as(Gtk.Action action)
  954. {
  955. save_as();
  956. }
  957. // If it returns true then filenames and out_dir are valid.
  958. private bool on_import_begin(Gtk.FileFilter ff, ref SList<string> filenames, ref string out_dir)
  959. {
  960. FileChooserDialog fcd = new FileChooserDialog("Import..."
  961. , this
  962. , FileChooserAction.OPEN
  963. , "Cancel"
  964. , ResponseType.CANCEL
  965. , "Open"
  966. , ResponseType.ACCEPT
  967. );
  968. fcd.select_multiple = true;
  969. fcd.add_filter(ff);
  970. if (fcd.run() != (int)ResponseType.ACCEPT)
  971. {
  972. fcd.destroy();
  973. return false;
  974. }
  975. filenames = fcd.get_filenames();
  976. fcd.destroy();
  977. FileChooserDialog dst = new FileChooserDialog("Select destination folder..."
  978. , this
  979. , FileChooserAction.SELECT_FOLDER
  980. , "Cancel"
  981. , ResponseType.CANCEL
  982. , "Select"
  983. , ResponseType.ACCEPT
  984. );
  985. dst.set_current_folder(_project.source_dir());
  986. if (dst.run() != (int)ResponseType.ACCEPT)
  987. {
  988. dst.destroy();
  989. return false;
  990. }
  991. out_dir = dst.get_filename();
  992. dst.destroy();
  993. return true;
  994. }
  995. private void on_import_end()
  996. {
  997. _data_compiler.compile.begin(_project.data_dir(), _project.platform(), (obj, res) => {
  998. _data_compiler.compile.end(res);
  999. });
  1000. }
  1001. private void on_import_sprites(Gtk.Action action)
  1002. {
  1003. Gtk.FileFilter ff = new FileFilter();
  1004. ff.set_filter_name("Sprite (*.png)");
  1005. ff.add_pattern("*.png");
  1006. SList<string> filenames = new SList<string>();
  1007. string out_dir = "";
  1008. if (on_import_begin(ff, ref filenames, ref out_dir))
  1009. {
  1010. _project.import_sprites(filenames, out_dir);
  1011. on_import_end();
  1012. }
  1013. }
  1014. private void on_import_meshes(Gtk.Action action)
  1015. {
  1016. Gtk.FileFilter ff = new FileFilter();
  1017. ff.set_filter_name("Mesh (*.mesh)");
  1018. ff.add_pattern("*.mesh");
  1019. SList<string> filenames = new SList<string>();
  1020. string out_dir = "";
  1021. if (on_import_begin(ff, ref filenames, ref out_dir))
  1022. {
  1023. _project.import_meshes(filenames, out_dir);
  1024. on_import_end();
  1025. }
  1026. }
  1027. private void on_import_sounds(Gtk.Action action)
  1028. {
  1029. Gtk.FileFilter ff = new FileFilter();
  1030. ff.set_filter_name("Sound (*.wav)");
  1031. ff.add_pattern("*.wav");
  1032. SList<string> filenames = new SList<string>();
  1033. string out_dir = "";
  1034. if (on_import_begin(ff, ref filenames, ref out_dir))
  1035. {
  1036. _project.import_sounds(filenames, out_dir);
  1037. on_import_end();
  1038. }
  1039. }
  1040. private void on_import_textures(Gtk.Action action)
  1041. {
  1042. Gtk.FileFilter ff = new FileFilter();
  1043. ff.set_filter_name("Texture (*.png, *.tga, *.dds, *.ktx, *.pvr)");
  1044. ff.add_pattern("*.png");
  1045. ff.add_pattern("*.tga");
  1046. ff.add_pattern("*.dds");
  1047. ff.add_pattern("*.ktx");
  1048. ff.add_pattern("*.pvr");
  1049. SList<string> filenames = new SList<string>();
  1050. string out_dir = "";
  1051. if (on_import_begin(ff, ref filenames, ref out_dir))
  1052. {
  1053. _project.import_textures(filenames, out_dir);
  1054. on_import_end();
  1055. }
  1056. }
  1057. private void on_preferences(Gtk.Action action)
  1058. {
  1059. if (_preferences_dialog == null)
  1060. {
  1061. _preferences_dialog = new PreferencesDialog(_engine);
  1062. _preferences_dialog.set_transient_for(this);
  1063. _preferences_dialog.delete_event.connect(() => { _preferences_dialog.hide(); return true; });
  1064. }
  1065. _preferences_dialog.show_all();
  1066. }
  1067. private void on_deploy(Gtk.Action action)
  1068. {
  1069. deploy_game();
  1070. }
  1071. private void on_quit(Gtk.Action action)
  1072. {
  1073. quit();
  1074. }
  1075. private void on_show_grid(Gtk.Action action)
  1076. {
  1077. ToggleAction ta = (ToggleAction)action;
  1078. _show_grid = ta.active;
  1079. send_state();
  1080. }
  1081. private void on_custom_grid()
  1082. {
  1083. Gtk.Dialog dg = new Gtk.Dialog.with_buttons("Grid size"
  1084. , this
  1085. , DialogFlags.MODAL
  1086. , "Cancel"
  1087. , ResponseType.CANCEL
  1088. , "Ok"
  1089. , ResponseType.OK
  1090. , null
  1091. );
  1092. EntryDouble sb = new EntryDouble(_grid_size, 0.1, 1000);
  1093. sb.activate.connect(() => { dg.response(ResponseType.OK); });
  1094. dg.get_content_area().add(sb);
  1095. dg.skip_taskbar_hint = true;
  1096. dg.show_all();
  1097. if (dg.run() == (int)ResponseType.OK)
  1098. {
  1099. _grid_size = sb.value;
  1100. send_state();
  1101. }
  1102. dg.destroy();
  1103. }
  1104. private void on_rotation_snap(Gtk.Action action)
  1105. {
  1106. Gtk.Dialog dg = new Gtk.Dialog.with_buttons("Rotation snap"
  1107. , this
  1108. , DialogFlags.MODAL
  1109. , "Cancel"
  1110. , ResponseType.CANCEL
  1111. , "Ok"
  1112. , ResponseType.OK
  1113. , null
  1114. );
  1115. EntryDouble sb = new EntryDouble(_rotation_snap, 1.0, 180.0);
  1116. sb.activate.connect(() => { dg.response(ResponseType.OK); });
  1117. dg.get_content_area().add(sb);
  1118. dg.skip_taskbar_hint = true;
  1119. dg.show_all();
  1120. if (dg.run() == (int)ResponseType.OK)
  1121. {
  1122. _rotation_snap = sb.value;
  1123. send_state();
  1124. }
  1125. dg.destroy();
  1126. }
  1127. private void on_create_primitive(Gtk.Action action)
  1128. {
  1129. if (action.name == "primitive-cube")
  1130. _engine.send_script(LevelEditorApi.set_placeable("unit", "core/units/primitives/cube"));
  1131. else if (action.name == "primitive-sphere")
  1132. _engine.send_script(LevelEditorApi.set_placeable("unit", "core/units/primitives/sphere"));
  1133. else if (action.name == "primitive-cone")
  1134. _engine.send_script(LevelEditorApi.set_placeable("unit", "core/units/primitives/cone"));
  1135. else if (action.name == "primitive-cylinder")
  1136. _engine.send_script(LevelEditorApi.set_placeable("unit", "core/units/primitives/cylinder"));
  1137. else if (action.name == "primitive-plane")
  1138. _engine.send_script(LevelEditorApi.set_placeable("unit", "core/units/primitives/plane"));
  1139. else if (action.name == "camera")
  1140. _engine.send_script(LevelEditorApi.set_placeable("unit", "core/units/camera"));
  1141. else if (action.name == "light")
  1142. _engine.send_script(LevelEditorApi.set_placeable("unit", "core/units/light"));
  1143. else if (action.name == "sound-source")
  1144. _engine.send_script(LevelEditorApi.set_placeable("sound", ""));
  1145. _action_group.get_action("place").activate();
  1146. }
  1147. private void on_camera_view(Gtk.Action action)
  1148. {
  1149. if (action.name == "camera-view-perspective")
  1150. _engine.send_script("LevelEditor:camera_view_perspective()");
  1151. else if (action.name == "camera-view-front")
  1152. _engine.send_script("LevelEditor:camera_view_front()");
  1153. else if (action.name == "camera-view-back")
  1154. _engine.send_script("LevelEditor:camera_view_back()");
  1155. else if (action.name == "camera-view-right")
  1156. _engine.send_script("LevelEditor:camera_view_right()");
  1157. else if (action.name == "camera-view-left")
  1158. _engine.send_script("LevelEditor:camera_view_left()");
  1159. else if (action.name == "camera-view-top")
  1160. _engine.send_script("LevelEditor:camera_view_top()");
  1161. else if (action.name == "camera-view-bottom")
  1162. _engine.send_script("LevelEditor:camera_view_bottom()");
  1163. }
  1164. private void on_resource_browser(Gtk.Action action)
  1165. {
  1166. _resource_popover.show_all();
  1167. }
  1168. private void on_fullscreen(Gtk.Action action)
  1169. {
  1170. if (_fullscreen)
  1171. this.unfullscreen();
  1172. else
  1173. this.fullscreen();
  1174. }
  1175. private void on_console(Gtk.Action action)
  1176. {
  1177. if (_console_view.is_visible())
  1178. {
  1179. if (_console_view._entry.has_focus)
  1180. _console_view.hide();
  1181. else
  1182. _console_view._entry.grab_focus();
  1183. }
  1184. else
  1185. {
  1186. _console_view.show_all();
  1187. }
  1188. }
  1189. private void on_engine_restart(Gtk.Action action)
  1190. {
  1191. restart_engine();
  1192. }
  1193. private void on_reload_lua(Gtk.Action action)
  1194. {
  1195. _data_compiler.compile.begin(_project.data_dir(), _project.platform(), (obj, res) => {
  1196. if (_data_compiler.compile.end(res))
  1197. {
  1198. _engine.send(DeviceApi.pause());
  1199. _engine.send(DeviceApi.reload("lua", "core/editors/level_editor/level_editor"));
  1200. _engine.send(DeviceApi.unpause());
  1201. }
  1202. });
  1203. }
  1204. public void on_snap_to_grid(Gtk.Action action)
  1205. {
  1206. ToggleAction ta = (ToggleAction)action;
  1207. _snap_to_grid = ta.active;
  1208. send_state();
  1209. }
  1210. private void on_debug_render_world(Gtk.Action action)
  1211. {
  1212. ToggleAction ta = (ToggleAction)action;
  1213. _debug_render_world = ta.active;
  1214. send_state();
  1215. }
  1216. private void on_debug_physics_world(Gtk.Action action)
  1217. {
  1218. ToggleAction ta = (ToggleAction)action;
  1219. _debug_physics_world = ta.active;
  1220. send_state();
  1221. }
  1222. private void on_run_game(Gtk.Action action)
  1223. {
  1224. if (_game.is_connected())
  1225. {
  1226. stop_game();
  1227. action.icon_name = "game-run";
  1228. }
  1229. else
  1230. {
  1231. start_game(action.name == "test-level" ? StartGame.TEST : StartGame.NORMAL);
  1232. action.icon_name = "game-stop";
  1233. }
  1234. }
  1235. private void on_undo(Gtk.Action action)
  1236. {
  1237. _database.undo();
  1238. }
  1239. private void on_redo(Gtk.Action action)
  1240. {
  1241. _database.redo();
  1242. }
  1243. private void on_duplicate(Gtk.Action action)
  1244. {
  1245. _level.duplicate_selected_objects();
  1246. }
  1247. private void on_delete(Gtk.Action action)
  1248. {
  1249. _level.destroy_selected_objects();
  1250. }
  1251. private void on_manual(Gtk.Action action)
  1252. {
  1253. try
  1254. {
  1255. AppInfo.launch_default_for_uri("https://dbartolini.github.io/crown/html/v" + CROWN_VERSION, null);
  1256. }
  1257. catch (Error e)
  1258. {
  1259. _console_view.loge("editor", e.message);
  1260. }
  1261. }
  1262. private void on_report_issue(Gtk.Action action)
  1263. {
  1264. try
  1265. {
  1266. AppInfo.launch_default_for_uri("https://github.com/dbartolini/crown/issues", null);
  1267. }
  1268. catch (Error e)
  1269. {
  1270. _console_view.loge("editor", e.message);
  1271. }
  1272. }
  1273. private void on_open_last_log(Gtk.Action action)
  1274. {
  1275. File file = File.new_for_path(_project.data_dir() + "/last.log");
  1276. try
  1277. {
  1278. AppInfo.launch_default_for_uri(file.get_uri(), null);
  1279. }
  1280. catch (Error e)
  1281. {
  1282. _console_view.loge("editor", e.message);
  1283. }
  1284. }
  1285. private void on_changelog(Gtk.Action action)
  1286. {
  1287. try
  1288. {
  1289. AppInfo.launch_default_for_uri("https://dbartolini.github.io/crown/html/v" + CROWN_VERSION + "/changelog.html", null);
  1290. }
  1291. catch (Error e)
  1292. {
  1293. _console_view.loge("editor", e.message);
  1294. }
  1295. }
  1296. private void on_about(Gtk.Action action)
  1297. {
  1298. Gtk.AboutDialog dlg = new Gtk.AboutDialog();
  1299. dlg.set_destroy_with_parent(true);
  1300. dlg.set_transient_for(this);
  1301. dlg.set_modal(true);
  1302. try
  1303. {
  1304. dlg.set_logo(new Pixbuf.from_resource("/org/crown/ui/icons/128x128/pepper.png"));
  1305. }
  1306. catch (Error e)
  1307. {
  1308. _console_view.loge("editor", e.message);
  1309. }
  1310. dlg.program_name = "Crown Game Engine";
  1311. dlg.version = CROWN_VERSION;
  1312. dlg.website = "https://github.com/dbartolini/crown";
  1313. dlg.copyright = "Copyright (c) 2012-2020 Daniele Bartolini and individual contributors.";
  1314. dlg.license = "Crown Game Engine.\n"
  1315. + "Copyright (c) 2012-2020 Daniele Bartolini and individual contributors.\n"
  1316. + "\n"
  1317. + "This program is free software; you can redistribute it and/or\n"
  1318. + "modify it under the terms of the GNU General Public License\n"
  1319. + "as published by the Free Software Foundation; either version 2\n"
  1320. + "of the License, or (at your option) any later version.\n"
  1321. + "\n"
  1322. + "This program is distributed in the hope that it will be useful,\n"
  1323. + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
  1324. + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
  1325. + "GNU General Public License for more details.\n"
  1326. + "\n"
  1327. + "You should have received a copy of the GNU General Public License\n"
  1328. + "along with this program; if not, write to the Free Software\n"
  1329. + "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n"
  1330. ;
  1331. dlg.run();
  1332. dlg.destroy();
  1333. }
  1334. private void on_destroy()
  1335. {
  1336. Gtk.main_quit();
  1337. }
  1338. private bool on_delete_event()
  1339. {
  1340. quit();
  1341. return true;
  1342. }
  1343. }
  1344. public static GLib.SubprocessFlags subprocess_flags()
  1345. {
  1346. GLib.SubprocessFlags flags = SubprocessFlags.NONE;
  1347. #if !CROWN_DEBUG
  1348. flags |= SubprocessFlags.STDOUT_SILENCE | SubprocessFlags.STDERR_SILENCE;
  1349. #endif
  1350. return flags;
  1351. }
  1352. public static int main (string[] args)
  1353. {
  1354. Gtk.init(ref args);
  1355. Intl.setlocale(LocaleCategory.ALL, "C");
  1356. Gtk.Settings.get_default().gtk_theme_name = "Adwaita";
  1357. Gtk.Settings.get_default().gtk_application_prefer_dark_theme = true;
  1358. Gtk.CssProvider provider = new Gtk.CssProvider();
  1359. Gdk.Screen screen = Gdk.Display.get_default().get_default_screen();
  1360. Gtk.StyleContext.add_provider_for_screen(screen, provider, STYLE_PROVIDER_PRIORITY_APPLICATION);
  1361. provider.load_from_resource("/org/crown/ui/theme/style.css");
  1362. Gtk.IconTheme.get_default().add_resource_path("/org/crown/ui/icons/theme");
  1363. bool create_initial_files = false;
  1364. string source_dir = "";
  1365. string level_resource = "";
  1366. string toolchain_dir = "";
  1367. if (args.length > 1)
  1368. {
  1369. if (!GLib.FileUtils.test(args[1], FileTest.EXISTS) || !GLib.FileUtils.test(args[1], FileTest.IS_DIR))
  1370. {
  1371. stdout.printf("Source directory does not exist or it is not a directory\n");
  1372. return -1;
  1373. }
  1374. source_dir = args[1];
  1375. }
  1376. else
  1377. {
  1378. FileChooserDialog fcd = new FileChooserDialog("Select folder where to create new project..."
  1379. , null
  1380. , FileChooserAction.SELECT_FOLDER
  1381. , "Cancel"
  1382. , ResponseType.CANCEL
  1383. , "Select"
  1384. , ResponseType.ACCEPT
  1385. );
  1386. if (fcd.run() != (int)ResponseType.ACCEPT)
  1387. {
  1388. fcd.destroy();
  1389. return -1;
  1390. }
  1391. string dir = fcd.get_filename();
  1392. fcd.destroy();
  1393. if (GLib.FileUtils.test(dir, FileTest.IS_REGULAR))
  1394. {
  1395. stdout.printf("Source directory can't be a regular file\n");
  1396. return -1;
  1397. }
  1398. source_dir = dir;
  1399. create_initial_files = true;
  1400. }
  1401. if (args.length > 2)
  1402. {
  1403. // Validation is done below after the Project object instantiation
  1404. level_resource = args[2];
  1405. }
  1406. if (args.length > 3)
  1407. {
  1408. if (!GLib.FileUtils.test(args[3], FileTest.EXISTS) || !GLib.FileUtils.test(args[3], FileTest.IS_DIR))
  1409. {
  1410. stdout.printf("Toolchain directory does not exist or it is not a directory\n");
  1411. return -1;
  1412. }
  1413. toolchain_dir = args[3];
  1414. }
  1415. else
  1416. {
  1417. bool found = false;
  1418. /// More desirable paths come first
  1419. string toolchain_paths[] =
  1420. {
  1421. "../..",
  1422. "../../../samples"
  1423. };
  1424. for (int i = 0; i < toolchain_paths.length; ++i)
  1425. {
  1426. string path = Path.build_filename(toolchain_paths[i], "core");
  1427. // Try to locate the toolchain directory
  1428. if (GLib.FileUtils.test(path, FileTest.EXISTS) && GLib.FileUtils.test(path, FileTest.IS_DIR))
  1429. {
  1430. toolchain_dir = toolchain_paths[i];
  1431. found = true;
  1432. break;
  1433. }
  1434. }
  1435. if (!found)
  1436. {
  1437. stdout.printf("Unable to find the toolchain directory\n");
  1438. return -1;
  1439. }
  1440. }
  1441. Project project = new Project();
  1442. project.load(source_dir, toolchain_dir);
  1443. if (create_initial_files)
  1444. project.create_initial_files();
  1445. Database database = new Database();
  1446. ConsoleClient compiler = new ConsoleClient();
  1447. ConsoleClient engine = new ConsoleClient();
  1448. ConsoleClient game = new ConsoleClient();
  1449. Level level = new Level(database, engine, project);
  1450. LevelEditor editor = new LevelEditor(database, project, level, compiler, engine, game);
  1451. if (level_resource != "")
  1452. {
  1453. string level_path = project.source_dir() + "/" + level_resource + ".level";
  1454. if (!GLib.FileUtils.test(level_path, FileTest.EXISTS) || !GLib.FileUtils.test(level_path, FileTest.IS_REGULAR))
  1455. {
  1456. stdout.printf("Level resource '%s' does not exist.\n", level_resource);
  1457. return -1;
  1458. }
  1459. level.load(level_path);
  1460. }
  1461. else
  1462. {
  1463. level.load_empty_level();
  1464. }
  1465. editor.show_all();
  1466. Gtk.main();
  1467. return 0;
  1468. }
  1469. }