2
0

editor_view.vala 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. /*
  2. * Copyright (c) 2012-2025 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. private const Gtk.TargetEntry[] dnd_targets =
  15. {
  16. { "RESOURCE_PATH", Gtk.TargetFlags.SAME_APP, 0 },
  17. };
  18. // Data
  19. private RuntimeInstance _runtime;
  20. private Gtk.Allocation _allocation;
  21. private uint _resize_timer_id;
  22. private bool _mouse_left;
  23. private bool _mouse_middle;
  24. private bool _mouse_right;
  25. private uint _window_id;
  26. private uint _last_window_id;
  27. private Gee.HashMap<uint, bool> _keys;
  28. private bool _input_enabled;
  29. private bool _drag_enter;
  30. private uint _last_time;
  31. // Signals
  32. public signal void native_window_ready(uint window_id, int width, int height);
  33. private string key_to_string(uint k)
  34. {
  35. switch (k) {
  36. case Gdk.Key.w: return "w";
  37. case Gdk.Key.a: return "a";
  38. case Gdk.Key.s: return "s";
  39. case Gdk.Key.d: return "d";
  40. case Gdk.Key.Alt_L: return "alt_left";
  41. case Gdk.Key.Alt_R: return "alt_right";
  42. default: return "<unknown>";
  43. }
  44. }
  45. private bool camera_modifier_pressed()
  46. {
  47. return _keys[Gdk.Key.Alt_L]
  48. || _keys[Gdk.Key.Alt_R]
  49. ;
  50. }
  51. private void camera_modifier_reset()
  52. {
  53. _keys[Gdk.Key.Alt_L] = false;
  54. _keys[Gdk.Key.Alt_R] = false;
  55. }
  56. public EditorView(RuntimeInstance runtime, bool input_enabled = true)
  57. {
  58. _runtime = runtime;
  59. _allocation = { 0, 0, 0, 0 };
  60. _resize_timer_id = 0;
  61. _mouse_left = false;
  62. _mouse_middle = false;
  63. _mouse_right = false;
  64. _window_id = 0;
  65. _last_window_id = 0;
  66. _keys = new Gee.HashMap<uint, bool>();
  67. _keys[Gdk.Key.w] = false;
  68. _keys[Gdk.Key.a] = false;
  69. _keys[Gdk.Key.s] = false;
  70. _keys[Gdk.Key.d] = false;
  71. _keys[Gdk.Key.Alt_L] = false;
  72. _keys[Gdk.Key.Alt_R] = false;
  73. _input_enabled = input_enabled;
  74. _drag_enter = false;
  75. _last_time = 0;
  76. // Widgets
  77. this.can_focus = true;
  78. this.events |= Gdk.EventMask.POINTER_MOTION_MASK
  79. | Gdk.EventMask.KEY_PRESS_MASK
  80. | Gdk.EventMask.KEY_RELEASE_MASK
  81. | Gdk.EventMask.FOCUS_CHANGE_MASK
  82. | Gdk.EventMask.SCROLL_MASK
  83. ;
  84. this.focus_out_event.connect(on_event_box_focus_out_event);
  85. this.size_allocate.connect(on_size_allocate);
  86. if (input_enabled) {
  87. this.button_release_event.connect(on_button_release);
  88. this.button_press_event.connect(on_button_press);
  89. this.key_press_event.connect(on_key_press);
  90. this.key_release_event.connect(on_key_release);
  91. this.motion_notify_event.connect(on_motion_notify);
  92. this.scroll_event.connect(on_scroll);
  93. }
  94. this.realize.connect(on_event_box_realized);
  95. this.set_visual(Gdk.Screen.get_default().get_system_visual());
  96. this.events |= Gdk.EventMask.STRUCTURE_MASK; // map_event
  97. this.map_event.connect(() => {
  98. device_frame_delayed(16, _runtime);
  99. return Gdk.EVENT_PROPAGATE;
  100. });
  101. this.enter_notify_event.connect(on_enter_notify_event);
  102. Gtk.drag_dest_set(this, Gtk.DestDefaults.MOTION, dnd_targets, Gdk.DragAction.COPY);
  103. this.drag_data_received.connect(on_drag_data_received);
  104. this.drag_motion.connect(on_drag_motion);
  105. this.drag_drop.connect(on_drag_drop);
  106. this.drag_leave.connect(on_drag_leave);
  107. }
  108. private void on_drag_data_received(Gdk.DragContext context, int x, int y, Gtk.SelectionData data, uint info, uint time_)
  109. {
  110. // https://valadoc.org/gtk+-3.0/Gtk.Widget.drag_data_received.html
  111. unowned uint8[] raw_data = data.get_data_with_length();
  112. if (raw_data.length == -1)
  113. return;
  114. string resource_path = (string)raw_data;
  115. string type = ResourceId.type(resource_path);
  116. string name = ResourceId.name(resource_path);
  117. if (type == OBJECT_TYPE_UNIT || type == OBJECT_TYPE_SOUND_SOURCE) {
  118. GLib.Application.get_default().activate_action("set-placeable", new GLib.Variant.tuple({ type, name }));
  119. int scale = this.get_scale_factor();
  120. _runtime.send_script(LevelEditorApi.mouse_down(x*scale, y*scale));
  121. }
  122. }
  123. private bool on_drag_motion(Gdk.DragContext context, int x, int y, uint _time)
  124. {
  125. // https://valadoc.org/gtk+-3.0/Gtk.Widget.drag_motion.html
  126. Gdk.Atom target;
  127. target = Gtk.drag_dest_find_target(this, context, null);
  128. if (target == Gdk.Atom.NONE) {
  129. Gdk.drag_status(context, 0, _time);
  130. } else {
  131. if (_drag_enter == false) {
  132. Gtk.drag_get_data(this, context, target, _time);
  133. _drag_enter = true;
  134. }
  135. if (_time - _last_time >= 16) {
  136. // Drag motion events seem to fire at a very high frequency compared to regular
  137. // motion notify events. Limit them to 60 hz.
  138. _last_time = _time;
  139. int scale = this.get_scale_factor();
  140. _runtime.send_script(LevelEditorApi.set_mouse_state(x*scale
  141. , y*scale
  142. , _mouse_left
  143. , _mouse_middle
  144. , _mouse_right
  145. ));
  146. _runtime.send(DeviceApi.frame());
  147. }
  148. }
  149. return true;
  150. }
  151. private bool on_drag_drop(Gdk.DragContext context, int x, int y, uint time_)
  152. {
  153. // https://valadoc.org/gtk+-3.0/Gtk.Widget.drag_drop.html
  154. int scale = this.get_scale_factor();
  155. _runtime.send_script(LevelEditorApi.mouse_up(x*scale, y*scale));
  156. GLib.Application.get_default().activate_action("cancel-place", null);
  157. _runtime.send(DeviceApi.frame());
  158. Gtk.drag_finish(context, true, false, time_);
  159. return true;
  160. }
  161. private void on_drag_leave(Gdk.DragContext context, uint time_)
  162. {
  163. // https://valadoc.org/gtk+-3.0/Gtk.Widget.drag_leave.html
  164. _drag_enter = false;
  165. }
  166. private bool on_button_release(Gdk.EventButton ev)
  167. {
  168. int scale = this.get_scale_factor();
  169. _mouse_left = ev.button == Gdk.BUTTON_PRIMARY ? false : _mouse_left;
  170. _mouse_middle = ev.button == Gdk.BUTTON_MIDDLE ? false : _mouse_middle;
  171. _mouse_right = ev.button == Gdk.BUTTON_SECONDARY ? false : _mouse_right;
  172. string str = LevelEditorApi.set_mouse_state((int)ev.x*scale
  173. , (int)ev.y*scale
  174. , _mouse_left
  175. , _mouse_middle
  176. , _mouse_right
  177. );
  178. if (ev.button == Gdk.BUTTON_PRIMARY)
  179. str += LevelEditorApi.mouse_up((int)ev.x*scale, (int)ev.y*scale);
  180. if (camera_modifier_pressed()) {
  181. if (!_mouse_left || !_mouse_middle || !_mouse_right)
  182. str += "LevelEditor:camera_drag_start('idle')";
  183. }
  184. if (str.length != 0) {
  185. _runtime.send_script(str);
  186. _runtime.send(DeviceApi.frame());
  187. }
  188. return Gdk.EVENT_PROPAGATE;
  189. }
  190. private bool on_button_press(Gdk.EventButton ev)
  191. {
  192. int scale = this.get_scale_factor();
  193. this.grab_focus();
  194. _mouse_left = ev.button == Gdk.BUTTON_PRIMARY ? true : _mouse_left;
  195. _mouse_middle = ev.button == Gdk.BUTTON_MIDDLE ? true : _mouse_middle;
  196. _mouse_right = ev.button == Gdk.BUTTON_SECONDARY ? true : _mouse_right;
  197. string str = LevelEditorApi.set_mouse_state((int)ev.x*scale
  198. , (int)ev.y*scale
  199. , _mouse_left
  200. , _mouse_middle
  201. , _mouse_right
  202. );
  203. if (camera_modifier_pressed()) {
  204. if (_mouse_left)
  205. str += "LevelEditor:camera_drag_start('tumble')";
  206. if (_mouse_middle)
  207. str += "LevelEditor:camera_drag_start('track')";
  208. if (_mouse_right)
  209. str += "LevelEditor:camera_drag_start('dolly')";
  210. }
  211. if (ev.button == Gdk.BUTTON_PRIMARY)
  212. str += LevelEditorApi.mouse_down((int)ev.x*scale, (int)ev.y*scale);
  213. if (str.length != 0) {
  214. _runtime.send_script(str);
  215. _runtime.send(DeviceApi.frame());
  216. }
  217. return Gdk.EVENT_PROPAGATE;
  218. }
  219. private bool on_key_press(Gdk.EventKey ev)
  220. {
  221. string str = "";
  222. if (ev.keyval == Gdk.Key.Escape)
  223. GLib.Application.get_default().activate_action("cancel-place", null);
  224. if (ev.keyval == Gdk.Key.Up)
  225. str += "LevelEditor:key_down(\"move_up\")";
  226. if (ev.keyval == Gdk.Key.Down)
  227. str += "LevelEditor:key_down(\"move_down\")";
  228. if (ev.keyval == Gdk.Key.Right)
  229. str += "LevelEditor:key_down(\"move_right\")";
  230. if (ev.keyval == Gdk.Key.Left)
  231. str += "LevelEditor:key_down(\"move_left\")";
  232. if (_keys.has_key(ev.keyval)) {
  233. if (!_keys[ev.keyval])
  234. str += LevelEditorApi.key_down(key_to_string(ev.keyval));
  235. _keys[ev.keyval] = true;
  236. }
  237. if (str.length != 0) {
  238. _runtime.send_script(str);
  239. _runtime.send(DeviceApi.frame());
  240. }
  241. return Gdk.EVENT_PROPAGATE;
  242. }
  243. private bool on_key_release(Gdk.EventKey ev)
  244. {
  245. string str = "";
  246. if ((ev.keyval == Gdk.Key.Alt_L || ev.keyval == Gdk.Key.Alt_R))
  247. str += "LevelEditor:camera_drag_start('idle')";
  248. if (_keys.has_key(ev.keyval)) {
  249. if (_keys[ev.keyval])
  250. str += LevelEditorApi.key_up(key_to_string(ev.keyval));
  251. _keys[ev.keyval] = false;
  252. }
  253. if (str.length != 0) {
  254. _runtime.send_script(str);
  255. _runtime.send(DeviceApi.frame());
  256. }
  257. return Gdk.EVENT_PROPAGATE;
  258. }
  259. private bool on_motion_notify(Gdk.EventMotion ev)
  260. {
  261. int scale = this.get_scale_factor();
  262. _runtime.send_script(LevelEditorApi.set_mouse_state((int)ev.x*scale
  263. , (int)ev.y*scale
  264. , _mouse_left
  265. , _mouse_middle
  266. , _mouse_right
  267. ));
  268. _runtime.send(DeviceApi.frame());
  269. return Gdk.EVENT_PROPAGATE;
  270. }
  271. private bool on_scroll(Gdk.EventScroll ev)
  272. {
  273. _runtime.send_script(LevelEditorApi.mouse_wheel(ev.direction == Gdk.ScrollDirection.UP ? 1.0 : -1.0));
  274. return Gdk.EVENT_PROPAGATE;
  275. }
  276. private bool on_event_box_focus_out_event(Gdk.EventFocus ev)
  277. {
  278. camera_modifier_reset();
  279. return Gdk.EVENT_PROPAGATE;
  280. }
  281. private void on_size_allocate(Gtk.Allocation ev)
  282. {
  283. int scale = this.get_scale_factor();
  284. if (_allocation.x == ev.x
  285. && _allocation.y == ev.y
  286. && _allocation.width == ev.width
  287. && _allocation.height == ev.height
  288. )
  289. return;
  290. if (_last_window_id != _window_id) {
  291. _last_window_id = _window_id;
  292. native_window_ready(_window_id, ev.width*scale, ev.height*scale);
  293. }
  294. _allocation = ev;
  295. _runtime.send(DeviceApi.resize(_allocation.width*scale, _allocation.height*scale));
  296. // Ensure there is some delay between the last resize() and the last frame().
  297. if (_resize_timer_id == 0) {
  298. _resize_timer_id = GLib.Timeout.add_full(GLib.Priority.DEFAULT, 200, () => {
  299. _runtime.send(DeviceApi.frame());
  300. _resize_timer_id = 0;
  301. return GLib.Source.REMOVE;
  302. });
  303. }
  304. }
  305. private void on_event_box_realized()
  306. {
  307. this.get_window().ensure_native();
  308. #if CROWN_PLATFORM_LINUX
  309. this.get_display().sync();
  310. _window_id = gdk_x11_window_get_xid(this.get_window());
  311. #elif CROWN_PLATFORM_WINDOWS
  312. _window_id = gdk_win32_window_get_handle(this.get_window());
  313. #endif
  314. }
  315. bool on_enter_notify_event(Gdk.EventCrossing event)
  316. {
  317. if (_input_enabled)
  318. this.grab_focus();
  319. return Gdk.EVENT_PROPAGATE;
  320. }
  321. }
  322. } /* namespace Crown */