editor_view.vala 12 KB

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