editor_view.vala 12 KB

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