editor_view.vala 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. /*
  2. * Copyright (c) 2012-2026 Daniele Bartolini et al.
  3. * SPDX-License-Identifier: GPL-3.0-or-later
  4. */
  5. #if CROWN_PLATFORM_LINUX
  6. extern uint gdk_x11_window_get_xid(Gdk.Window window);
  7. #elif CROWN_PLATFORM_WINDOWS
  8. extern uint gdk_win32_window_get_handle(Gdk.Window window);
  9. #endif
  10. namespace Crown
  11. {
  12. public class EditorView : Gtk.EventBox
  13. {
  14. public const Gtk.TargetEntry[] dnd_targets =
  15. {
  16. { "RESOURCE_PATH", Gtk.TargetFlags.SAME_APP, 0 },
  17. };
  18. // Data
  19. public RuntimeInstance _runtime;
  20. public Gtk.Allocation _allocation;
  21. public uint _resize_timer_id;
  22. public uint _enable_accels_id;
  23. public uint _tick_callback_id;
  24. public bool _mouse_left;
  25. public bool _mouse_middle;
  26. public bool _mouse_right;
  27. public uint _window_id;
  28. public uint _last_window_id;
  29. public Gee.HashMap<uint, bool> _keys;
  30. public bool _input_enabled;
  31. public bool _drag_enter;
  32. public uint _drag_last_time;
  33. public int64 _motion_last_time;
  34. public const int MOTION_EVENTS_RATE = 75;
  35. public GLib.StringBuilder _buffer;
  36. public Gtk.EventControllerKey _controller_key;
  37. public Gtk.GestureMultiPress _gesture_click;
  38. public Gtk.EventControllerMotion _controller_motion;
  39. public Gtk.EventControllerScroll _controller_scroll;
  40. // Signals
  41. public signal void native_window_ready(uint window_id, int width, int height);
  42. public string key_to_string(uint k)
  43. {
  44. switch (k) {
  45. case Gdk.Key.w: return "w";
  46. case Gdk.Key.a: return "a";
  47. case Gdk.Key.s: return "s";
  48. case Gdk.Key.d: return "d";
  49. case Gdk.Key.q: return "q";
  50. case Gdk.Key.e: return "e";
  51. case Gdk.Key.Control_L: return "ctrl_left";
  52. case Gdk.Key.Shift_L: return "shift_left";
  53. case Gdk.Key.Alt_L: return "alt_left";
  54. case Gdk.Key.Alt_R: return "alt_right";
  55. default: return "<unknown>";
  56. }
  57. }
  58. public bool camera_modifier_pressed()
  59. {
  60. return _keys[Gdk.Key.Alt_L]
  61. || _keys[Gdk.Key.Alt_R]
  62. ;
  63. }
  64. public void camera_modifier_reset()
  65. {
  66. _keys[Gdk.Key.Alt_L] = false;
  67. _keys[Gdk.Key.Alt_R] = false;
  68. }
  69. public EditorView(RuntimeInstance runtime, bool input_enabled = true)
  70. {
  71. _runtime = runtime;
  72. _allocation = { 0, 0, 0, 0 };
  73. _resize_timer_id = 0;
  74. _enable_accels_id = 0;
  75. _tick_callback_id = 0;
  76. _mouse_left = false;
  77. _mouse_middle = false;
  78. _mouse_right = false;
  79. _window_id = 0;
  80. _last_window_id = 0;
  81. _keys = new Gee.HashMap<uint, bool>();
  82. _keys[Gdk.Key.w] = false;
  83. _keys[Gdk.Key.a] = false;
  84. _keys[Gdk.Key.s] = false;
  85. _keys[Gdk.Key.d] = false;
  86. _keys[Gdk.Key.q] = false;
  87. _keys[Gdk.Key.e] = false;
  88. _keys[Gdk.Key.Control_L] = false;
  89. _keys[Gdk.Key.Shift_L] = false;
  90. _keys[Gdk.Key.Alt_L] = false;
  91. _keys[Gdk.Key.Alt_R] = false;
  92. _input_enabled = input_enabled;
  93. _drag_enter = false;
  94. _drag_last_time = 0;
  95. _motion_last_time = 0;
  96. _buffer = new GLib.StringBuilder();
  97. // Widgets
  98. this.can_focus = true;
  99. this.events |= Gdk.EventMask.POINTER_MOTION_MASK
  100. | Gdk.EventMask.KEY_PRESS_MASK
  101. | Gdk.EventMask.KEY_RELEASE_MASK
  102. | Gdk.EventMask.FOCUS_CHANGE_MASK
  103. | Gdk.EventMask.SCROLL_MASK
  104. ;
  105. this.focus_out_event.connect(on_event_box_focus_out_event);
  106. this.size_allocate.connect(on_size_allocate);
  107. if (input_enabled) {
  108. _controller_key = new Gtk.EventControllerKey(this);
  109. _controller_key.key_pressed.connect(on_key_pressed);
  110. _controller_key.key_released.connect(on_key_released);
  111. _gesture_click = new Gtk.GestureMultiPress(this);
  112. _gesture_click.set_button(0);
  113. _gesture_click.pressed.connect(on_button_pressed);
  114. _gesture_click.released.connect(on_button_released);
  115. _controller_motion = new Gtk.EventControllerMotion(this);
  116. _controller_motion.enter.connect(on_enter);
  117. _controller_motion.motion.connect(on_motion);
  118. _controller_scroll = new Gtk.EventControllerScroll(this, Gtk.EventControllerScrollFlags.BOTH_AXES);
  119. _controller_scroll.scroll.connect(on_scroll);
  120. }
  121. this.realize.connect(on_event_box_realized);
  122. this.set_visual(Gdk.Screen.get_default().get_system_visual());
  123. this.events |= Gdk.EventMask.STRUCTURE_MASK; // map_event
  124. this.map_event.connect(() => {
  125. device_frame_delayed(16, _runtime);
  126. return Gdk.EVENT_PROPAGATE;
  127. });
  128. Gtk.drag_dest_set(this, Gtk.DestDefaults.MOTION, dnd_targets, Gdk.DragAction.COPY);
  129. this.drag_data_received.connect(on_drag_data_received);
  130. this.drag_motion.connect(on_drag_motion);
  131. this.drag_drop.connect(on_drag_drop);
  132. this.drag_leave.connect(on_drag_leave);
  133. }
  134. public void on_drag_data_received(Gdk.DragContext context, int x, int y, Gtk.SelectionData data, uint info, uint time_)
  135. {
  136. // https://valadoc.org/gtk+-3.0/Gtk.Widget.drag_data_received.html
  137. unowned uint8[] raw_data = data.get_data_with_length();
  138. if (raw_data.length == -1)
  139. return;
  140. string resource_path = (string)raw_data;
  141. string type = ResourceId.type(resource_path);
  142. string name = ResourceId.name(resource_path);
  143. if (type == OBJECT_TYPE_UNIT || type == OBJECT_TYPE_SOUND) {
  144. GLib.Application.get_default().activate_action("set-placeable", new GLib.Variant.tuple({ type, name }));
  145. int scale = this.get_scale_factor();
  146. _runtime.send_script(LevelEditorApi.mouse_down(x*scale, y*scale));
  147. }
  148. }
  149. public bool on_drag_motion(Gdk.DragContext context, int x, int y, uint _time)
  150. {
  151. // https://valadoc.org/gtk+-3.0/Gtk.Widget.drag_motion.html
  152. Gdk.Atom target;
  153. target = Gtk.drag_dest_find_target(this, context, null);
  154. if (target == Gdk.Atom.NONE) {
  155. Gdk.drag_status(context, 0, _time);
  156. } else {
  157. if (_drag_enter == false) {
  158. Gtk.drag_get_data(this, context, target, _time);
  159. _drag_enter = true;
  160. }
  161. if (_time - _drag_last_time >= 1000/MOTION_EVENTS_RATE) {
  162. // Drag motion events seem to fire at a very high frequency compared to regular
  163. // motion notify events. Limit them to 60 hz.
  164. _drag_last_time = _time;
  165. int scale = this.get_scale_factor();
  166. _runtime.send_script(LevelEditorApi.set_mouse_state(x*scale
  167. , y*scale
  168. , _mouse_left
  169. , _mouse_middle
  170. , _mouse_right
  171. ));
  172. _runtime.send(DeviceApi.frame());
  173. }
  174. }
  175. return true;
  176. }
  177. public bool on_drag_drop(Gdk.DragContext context, int x, int y, uint time_)
  178. {
  179. // https://valadoc.org/gtk+-3.0/Gtk.Widget.drag_drop.html
  180. int scale = this.get_scale_factor();
  181. _runtime.send_script(LevelEditorApi.mouse_up(x*scale, y*scale));
  182. GLib.Application.get_default().activate_action("cancel-place", null);
  183. _runtime.send(DeviceApi.frame());
  184. Gtk.drag_finish(context, true, false, time_);
  185. return true;
  186. }
  187. public void on_drag_leave(Gdk.DragContext context, uint time_)
  188. {
  189. // https://valadoc.org/gtk+-3.0/Gtk.Widget.drag_leave.html
  190. _drag_enter = false;
  191. }
  192. public void on_button_released(int n_press, double x, double y)
  193. {
  194. uint button = _gesture_click.get_current_button();
  195. int scale = this.get_scale_factor();
  196. _mouse_left = button == Gdk.BUTTON_PRIMARY ? false : _mouse_left;
  197. _mouse_middle = button == Gdk.BUTTON_MIDDLE ? false : _mouse_middle;
  198. _mouse_right = button == Gdk.BUTTON_SECONDARY ? false : _mouse_right;
  199. _buffer.append(LevelEditorApi.set_mouse_state((int)x*scale
  200. , (int)y*scale
  201. , _mouse_left
  202. , _mouse_middle
  203. , _mouse_right
  204. ));
  205. if (button == Gdk.BUTTON_PRIMARY)
  206. _buffer.append(LevelEditorApi.mouse_up((int)x*scale, (int)y*scale));
  207. if (camera_modifier_pressed()) {
  208. if (!_mouse_left || !_mouse_middle || !_mouse_right)
  209. _buffer.append("LevelEditor:camera_drag_start('idle')");
  210. } else if (!_mouse_middle || !_mouse_right) {
  211. _buffer.append("LevelEditor:camera_drag_start('idle')");
  212. bool is_flying = _tick_callback_id > 0;
  213. if (!_mouse_right && is_flying) {
  214. // Wait a little to prevent camera movement keys
  215. // from activating unwanted accelerators.
  216. _enable_accels_id = GLib.Timeout.add_full(GLib.Priority.DEFAULT, 300, on_enable_accels);
  217. if (_tick_callback_id != 0) {
  218. remove_tick_callback(_tick_callback_id);
  219. _tick_callback_id = 0;
  220. }
  221. }
  222. }
  223. if (_buffer.len != 0) {
  224. _runtime.send_script(_buffer.str);
  225. _buffer.erase();
  226. _runtime.send(DeviceApi.frame());
  227. }
  228. }
  229. public void on_button_pressed(int n_press, double x, double y)
  230. {
  231. uint button = _gesture_click.get_current_button();
  232. int scale = this.get_scale_factor();
  233. this.grab_focus();
  234. _mouse_left = button == Gdk.BUTTON_PRIMARY ? true : _mouse_left;
  235. _mouse_middle = button == Gdk.BUTTON_MIDDLE ? true : _mouse_middle;
  236. _mouse_right = button == Gdk.BUTTON_SECONDARY ? true : _mouse_right;
  237. _buffer.append(LevelEditorApi.set_mouse_state((int)x*scale
  238. , (int)y*scale
  239. , _mouse_left
  240. , _mouse_middle
  241. , _mouse_right
  242. ));
  243. if (camera_modifier_pressed()) {
  244. if (_mouse_left)
  245. _buffer.append("LevelEditor:camera_drag_start('tumble')");
  246. if (_mouse_middle)
  247. _buffer.append("LevelEditor:camera_drag_start('track')");
  248. if (_mouse_right)
  249. _buffer.append("LevelEditor:camera_drag_start('dolly')");
  250. } else if (_mouse_middle) {
  251. _buffer.append("LevelEditor:camera_drag_start('tumble')");
  252. } else if (_mouse_right) {
  253. _buffer.append("LevelEditor:camera_drag_start('flythrough')");
  254. if (_tick_callback_id == 0)
  255. _tick_callback_id = add_tick_callback(on_tick);
  256. if (_enable_accels_id > 0)
  257. GLib.Source.remove(_enable_accels_id);
  258. ((LevelEditorApplication)GLib.Application.get_default()).set_conflicting_accels(false);
  259. }
  260. if (button == Gdk.BUTTON_PRIMARY)
  261. _buffer.append(LevelEditorApi.mouse_down((int)x*scale, (int)y*scale));
  262. if (_buffer.len != 0) {
  263. _runtime.send_script(_buffer.str);
  264. _buffer.erase();
  265. _runtime.send(DeviceApi.frame());
  266. }
  267. }
  268. public bool on_key_pressed(uint keyval, uint keycode, Gdk.ModifierType state)
  269. {
  270. if (keyval == Gdk.Key.Escape)
  271. GLib.Application.get_default().activate_action("cancel-place", null);
  272. if (keyval == Gdk.Key.Up)
  273. _buffer.append("LevelEditor:key_down(\"move_up\")");
  274. if (keyval == Gdk.Key.Down)
  275. _buffer.append("LevelEditor:key_down(\"move_down\")");
  276. if (keyval == Gdk.Key.Right)
  277. _buffer.append("LevelEditor:key_down(\"move_right\")");
  278. if (keyval == Gdk.Key.Left)
  279. _buffer.append("LevelEditor:key_down(\"move_left\")");
  280. if (_keys.has_key(keyval)) {
  281. if (!_keys[keyval]) {
  282. _buffer.append(LevelEditorApi.key_down(key_to_string(keyval)));
  283. if (keyval == Gdk.Key.w)
  284. _buffer.append("LevelEditor._camera.actions.forward = true;");
  285. if (keyval == Gdk.Key.s)
  286. _buffer.append("LevelEditor._camera.actions.back = true;");
  287. if (keyval == Gdk.Key.a)
  288. _buffer.append("LevelEditor._camera.actions.left = true;");
  289. if (keyval == Gdk.Key.d)
  290. _buffer.append("LevelEditor._camera.actions.right = true;");
  291. if (keyval == Gdk.Key.q)
  292. _buffer.append("LevelEditor._camera.actions.up = true;");
  293. if (keyval == Gdk.Key.e)
  294. _buffer.append("LevelEditor._camera.actions.down = true;");
  295. }
  296. _keys[keyval] = true;
  297. }
  298. if (_buffer.len != 0) {
  299. _runtime.send_script(_buffer.str);
  300. _buffer.erase();
  301. _runtime.send(DeviceApi.frame());
  302. }
  303. return Gdk.EVENT_PROPAGATE;
  304. }
  305. public void on_key_released(uint keyval, uint keycode, Gdk.ModifierType state)
  306. {
  307. if (_keys.has_key(keyval)) {
  308. if (_keys[keyval]) {
  309. _buffer.append(LevelEditorApi.key_up(key_to_string(keyval)));
  310. if (keyval == Gdk.Key.w)
  311. _buffer.append("LevelEditor._camera.actions.forward = false");
  312. if (keyval == Gdk.Key.s)
  313. _buffer.append("LevelEditor._camera.actions.back = false");
  314. if (keyval == Gdk.Key.a)
  315. _buffer.append("LevelEditor._camera.actions.left = false");
  316. if (keyval == Gdk.Key.d)
  317. _buffer.append("LevelEditor._camera.actions.right = false");
  318. if (keyval == Gdk.Key.q)
  319. _buffer.append("LevelEditor._camera.actions.up = false");
  320. if (keyval == Gdk.Key.e)
  321. _buffer.append("LevelEditor._camera.actions.down = false");
  322. }
  323. _keys[keyval] = false;
  324. }
  325. if (_buffer.len != 0) {
  326. _runtime.send_script(_buffer.str);
  327. _buffer.erase();
  328. _runtime.send(DeviceApi.frame());
  329. }
  330. }
  331. public void on_motion(double x, double y)
  332. {
  333. int64 now = GLib.get_monotonic_time();
  334. if (now - _motion_last_time >= (1000*1000)/MOTION_EVENTS_RATE) {
  335. _motion_last_time = now;
  336. int scale = this.get_scale_factor();
  337. _runtime.send_script(LevelEditorApi.set_mouse_state((int)x*scale
  338. , (int)y*scale
  339. , _mouse_left
  340. , _mouse_middle
  341. , _mouse_right
  342. ));
  343. _runtime.send(DeviceApi.frame());
  344. }
  345. }
  346. public void on_scroll(double dx, double dy)
  347. {
  348. if (_keys[Gdk.Key.Shift_L]) {
  349. _runtime.send_script(LevelEditorApi.mouse_wheel(-dy));
  350. } else {
  351. _runtime.send_script("LevelEditor:camera_drag_start_relative('dolly')");
  352. _runtime.send_script("LevelEditor._camera:update(1,0,%.17f,1,1)".printf(-dy * 32.0));
  353. _runtime.send_script("LevelEditor:camera_drag_start('idle')");
  354. _runtime.send(DeviceApi.frame());
  355. }
  356. }
  357. public bool on_event_box_focus_out_event(Gdk.EventFocus ev)
  358. {
  359. camera_modifier_reset();
  360. _keys[Gdk.Key.Control_L] = false;
  361. _keys[Gdk.Key.Shift_L] = false;
  362. _runtime.send_script(LevelEditorApi.key_up(key_to_string(Gdk.Key.Control_L)));
  363. _runtime.send_script(LevelEditorApi.key_up(key_to_string(Gdk.Key.Shift_L)));
  364. return Gdk.EVENT_PROPAGATE;
  365. }
  366. public void on_size_allocate(Gtk.Allocation ev)
  367. {
  368. int scale = this.get_scale_factor();
  369. if (_allocation.x == ev.x
  370. && _allocation.y == ev.y
  371. && _allocation.width == ev.width
  372. && _allocation.height == ev.height
  373. )
  374. return;
  375. if (_last_window_id != _window_id) {
  376. _last_window_id = _window_id;
  377. native_window_ready(_window_id, ev.width*scale, ev.height*scale);
  378. }
  379. _allocation = ev;
  380. _runtime.send(DeviceApi.resize(_allocation.width*scale, _allocation.height*scale));
  381. // Ensure there is some delay between the last resize() and the last frame().
  382. if (_resize_timer_id == 0) {
  383. _resize_timer_id = GLib.Timeout.add_full(GLib.Priority.DEFAULT, 200, () => {
  384. _runtime.send(DeviceApi.frame());
  385. _resize_timer_id = 0;
  386. return GLib.Source.REMOVE;
  387. });
  388. }
  389. }
  390. public void on_event_box_realized()
  391. {
  392. this.get_window().ensure_native();
  393. #if CROWN_PLATFORM_LINUX
  394. this.get_display().sync();
  395. _window_id = gdk_x11_window_get_xid(this.get_window());
  396. #elif CROWN_PLATFORM_WINDOWS
  397. _window_id = gdk_win32_window_get_handle(this.get_window());
  398. #endif
  399. }
  400. public void on_enter(double x, double y)
  401. {
  402. this.grab_focus();
  403. }
  404. public bool on_tick(Gtk.Widget widget, Gdk.FrameClock frame_clock)
  405. {
  406. _runtime.send(DeviceApi.frame());
  407. return GLib.Source.CONTINUE;
  408. }
  409. public bool on_enable_accels()
  410. {
  411. ((LevelEditorApplication)GLib.Application.get_default()).set_conflicting_accels(true);
  412. _enable_accels_id = 0;
  413. return GLib.Source.REMOVE;
  414. }
  415. }
  416. } /* namespace Crown */