| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499 |
- /*
- * Copyright (c) 2012-2026 Daniele Bartolini et al.
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
- #if CROWN_PLATFORM_LINUX
- extern uint gdk_x11_window_get_xid(Gdk.Window window);
- #elif CROWN_PLATFORM_WINDOWS
- extern uint gdk_win32_window_get_handle(Gdk.Window window);
- #endif
- namespace Crown
- {
- public class EditorView : Gtk.EventBox
- {
- public const Gtk.TargetEntry[] dnd_targets =
- {
- { "RESOURCE_PATH", Gtk.TargetFlags.SAME_APP, 0 },
- };
- // Data
- public RuntimeInstance _runtime;
- public Gtk.Allocation _allocation;
- public uint _resize_timer_id;
- public uint _enable_accels_id;
- public uint _tick_callback_id;
- public bool _mouse_left;
- public bool _mouse_middle;
- public bool _mouse_right;
- public uint _window_id;
- public uint _last_window_id;
- public Gee.HashMap<uint, bool> _keys;
- public bool _input_enabled;
- public bool _drag_enter;
- public uint _drag_last_time;
- public int64 _motion_last_time;
- public const int MOTION_EVENTS_RATE = 75;
- public GLib.StringBuilder _buffer;
- public Gtk.EventControllerKey _controller_key;
- public Gtk.GestureMultiPress _gesture_click;
- public Gtk.EventControllerMotion _controller_motion;
- public Gtk.EventControllerScroll _controller_scroll;
- // Signals
- public signal void native_window_ready(uint window_id, int width, int height);
- public string key_to_string(uint k)
- {
- switch (k) {
- case Gdk.Key.w: return "w";
- case Gdk.Key.a: return "a";
- case Gdk.Key.s: return "s";
- case Gdk.Key.d: return "d";
- case Gdk.Key.q: return "q";
- case Gdk.Key.e: return "e";
- case Gdk.Key.Control_L: return "ctrl_left";
- case Gdk.Key.Shift_L: return "shift_left";
- case Gdk.Key.Alt_L: return "alt_left";
- case Gdk.Key.Alt_R: return "alt_right";
- default: return "<unknown>";
- }
- }
- public bool camera_modifier_pressed()
- {
- return _keys[Gdk.Key.Alt_L]
- || _keys[Gdk.Key.Alt_R]
- ;
- }
- public void camera_modifier_reset()
- {
- _keys[Gdk.Key.Alt_L] = false;
- _keys[Gdk.Key.Alt_R] = false;
- }
- public EditorView(RuntimeInstance runtime, bool input_enabled = true)
- {
- _runtime = runtime;
- _allocation = { 0, 0, 0, 0 };
- _resize_timer_id = 0;
- _enable_accels_id = 0;
- _tick_callback_id = 0;
- _mouse_left = false;
- _mouse_middle = false;
- _mouse_right = false;
- _window_id = 0;
- _last_window_id = 0;
- _keys = new Gee.HashMap<uint, bool>();
- _keys[Gdk.Key.w] = false;
- _keys[Gdk.Key.a] = false;
- _keys[Gdk.Key.s] = false;
- _keys[Gdk.Key.d] = false;
- _keys[Gdk.Key.q] = false;
- _keys[Gdk.Key.e] = false;
- _keys[Gdk.Key.Control_L] = false;
- _keys[Gdk.Key.Shift_L] = false;
- _keys[Gdk.Key.Alt_L] = false;
- _keys[Gdk.Key.Alt_R] = false;
- _input_enabled = input_enabled;
- _drag_enter = false;
- _drag_last_time = 0;
- _motion_last_time = 0;
- _buffer = new GLib.StringBuilder();
- // Widgets
- this.can_focus = true;
- this.events |= Gdk.EventMask.POINTER_MOTION_MASK
- | Gdk.EventMask.KEY_PRESS_MASK
- | Gdk.EventMask.KEY_RELEASE_MASK
- | Gdk.EventMask.FOCUS_CHANGE_MASK
- | Gdk.EventMask.SCROLL_MASK
- ;
- this.focus_out_event.connect(on_event_box_focus_out_event);
- this.size_allocate.connect(on_size_allocate);
- if (input_enabled) {
- _controller_key = new Gtk.EventControllerKey(this);
- _controller_key.key_pressed.connect(on_key_pressed);
- _controller_key.key_released.connect(on_key_released);
- _gesture_click = new Gtk.GestureMultiPress(this);
- _gesture_click.set_button(0);
- _gesture_click.pressed.connect(on_button_pressed);
- _gesture_click.released.connect(on_button_released);
- _controller_motion = new Gtk.EventControllerMotion(this);
- _controller_motion.enter.connect(on_enter);
- _controller_motion.motion.connect(on_motion);
- _controller_scroll = new Gtk.EventControllerScroll(this, Gtk.EventControllerScrollFlags.BOTH_AXES);
- _controller_scroll.scroll.connect(on_scroll);
- }
- this.realize.connect(on_event_box_realized);
- this.set_visual(Gdk.Screen.get_default().get_system_visual());
- this.events |= Gdk.EventMask.STRUCTURE_MASK; // map_event
- this.map_event.connect(() => {
- device_frame_delayed(16, _runtime);
- return Gdk.EVENT_PROPAGATE;
- });
- Gtk.drag_dest_set(this, Gtk.DestDefaults.MOTION, dnd_targets, Gdk.DragAction.COPY);
- this.drag_data_received.connect(on_drag_data_received);
- this.drag_motion.connect(on_drag_motion);
- this.drag_drop.connect(on_drag_drop);
- this.drag_leave.connect(on_drag_leave);
- }
- public void on_drag_data_received(Gdk.DragContext context, int x, int y, Gtk.SelectionData data, uint info, uint time_)
- {
- // https://valadoc.org/gtk+-3.0/Gtk.Widget.drag_data_received.html
- unowned uint8[] raw_data = data.get_data_with_length();
- if (raw_data.length == -1)
- return;
- string resource_path = (string)raw_data;
- string type = ResourceId.type(resource_path);
- string name = ResourceId.name(resource_path);
- if (type == OBJECT_TYPE_UNIT || type == OBJECT_TYPE_SOUND) {
- GLib.Application.get_default().activate_action("set-placeable", new GLib.Variant.tuple({ type, name }));
- int scale = this.get_scale_factor();
- _runtime.send_script(LevelEditorApi.mouse_down(x*scale, y*scale));
- }
- }
- public bool on_drag_motion(Gdk.DragContext context, int x, int y, uint _time)
- {
- // https://valadoc.org/gtk+-3.0/Gtk.Widget.drag_motion.html
- Gdk.Atom target;
- target = Gtk.drag_dest_find_target(this, context, null);
- if (target == Gdk.Atom.NONE) {
- Gdk.drag_status(context, 0, _time);
- } else {
- if (_drag_enter == false) {
- Gtk.drag_get_data(this, context, target, _time);
- _drag_enter = true;
- }
- if (_time - _drag_last_time >= 1000/MOTION_EVENTS_RATE) {
- // Drag motion events seem to fire at a very high frequency compared to regular
- // motion notify events. Limit them to 60 hz.
- _drag_last_time = _time;
- int scale = this.get_scale_factor();
- _runtime.send_script(LevelEditorApi.set_mouse_state(x*scale
- , y*scale
- , _mouse_left
- , _mouse_middle
- , _mouse_right
- ));
- _runtime.send(DeviceApi.frame());
- }
- }
- return true;
- }
- public bool on_drag_drop(Gdk.DragContext context, int x, int y, uint time_)
- {
- // https://valadoc.org/gtk+-3.0/Gtk.Widget.drag_drop.html
- int scale = this.get_scale_factor();
- _runtime.send_script(LevelEditorApi.mouse_up(x*scale, y*scale));
- GLib.Application.get_default().activate_action("cancel-place", null);
- _runtime.send(DeviceApi.frame());
- Gtk.drag_finish(context, true, false, time_);
- return true;
- }
- public void on_drag_leave(Gdk.DragContext context, uint time_)
- {
- // https://valadoc.org/gtk+-3.0/Gtk.Widget.drag_leave.html
- _drag_enter = false;
- }
- public void on_button_released(int n_press, double x, double y)
- {
- uint button = _gesture_click.get_current_button();
- int scale = this.get_scale_factor();
- _mouse_left = button == Gdk.BUTTON_PRIMARY ? false : _mouse_left;
- _mouse_middle = button == Gdk.BUTTON_MIDDLE ? false : _mouse_middle;
- _mouse_right = button == Gdk.BUTTON_SECONDARY ? false : _mouse_right;
- _buffer.append(LevelEditorApi.set_mouse_state((int)x*scale
- , (int)y*scale
- , _mouse_left
- , _mouse_middle
- , _mouse_right
- ));
- if (button == Gdk.BUTTON_PRIMARY)
- _buffer.append(LevelEditorApi.mouse_up((int)x*scale, (int)y*scale));
- if (camera_modifier_pressed()) {
- if (!_mouse_left || !_mouse_middle || !_mouse_right)
- _buffer.append("LevelEditor:camera_drag_start('idle')");
- } else if (!_mouse_middle || !_mouse_right) {
- _buffer.append("LevelEditor:camera_drag_start('idle')");
- bool is_flying = _tick_callback_id > 0;
- if (!_mouse_right && is_flying) {
- // Wait a little to prevent camera movement keys
- // from activating unwanted accelerators.
- _enable_accels_id = GLib.Timeout.add_full(GLib.Priority.DEFAULT, 300, on_enable_accels);
- if (_tick_callback_id != 0) {
- remove_tick_callback(_tick_callback_id);
- _tick_callback_id = 0;
- }
- }
- }
- if (_buffer.len != 0) {
- _runtime.send_script(_buffer.str);
- _buffer.erase();
- _runtime.send(DeviceApi.frame());
- }
- }
- public void on_button_pressed(int n_press, double x, double y)
- {
- uint button = _gesture_click.get_current_button();
- int scale = this.get_scale_factor();
- this.grab_focus();
- _mouse_left = button == Gdk.BUTTON_PRIMARY ? true : _mouse_left;
- _mouse_middle = button == Gdk.BUTTON_MIDDLE ? true : _mouse_middle;
- _mouse_right = button == Gdk.BUTTON_SECONDARY ? true : _mouse_right;
- _buffer.append(LevelEditorApi.set_mouse_state((int)x*scale
- , (int)y*scale
- , _mouse_left
- , _mouse_middle
- , _mouse_right
- ));
- if (camera_modifier_pressed()) {
- if (_mouse_left)
- _buffer.append("LevelEditor:camera_drag_start('tumble')");
- if (_mouse_middle)
- _buffer.append("LevelEditor:camera_drag_start('track')");
- if (_mouse_right)
- _buffer.append("LevelEditor:camera_drag_start('dolly')");
- } else if (_mouse_middle) {
- _buffer.append("LevelEditor:camera_drag_start('tumble')");
- } else if (_mouse_right) {
- _buffer.append("LevelEditor:camera_drag_start('flythrough')");
- if (_tick_callback_id == 0)
- _tick_callback_id = add_tick_callback(on_tick);
- if (_enable_accels_id > 0)
- GLib.Source.remove(_enable_accels_id);
- ((LevelEditorApplication)GLib.Application.get_default()).set_conflicting_accels(false);
- }
- if (button == Gdk.BUTTON_PRIMARY)
- _buffer.append(LevelEditorApi.mouse_down((int)x*scale, (int)y*scale));
- if (_buffer.len != 0) {
- _runtime.send_script(_buffer.str);
- _buffer.erase();
- _runtime.send(DeviceApi.frame());
- }
- }
- public bool on_key_pressed(uint keyval, uint keycode, Gdk.ModifierType state)
- {
- if (keyval == Gdk.Key.Escape)
- GLib.Application.get_default().activate_action("cancel-place", null);
- if (keyval == Gdk.Key.Up)
- _buffer.append("LevelEditor:key_down(\"move_up\")");
- if (keyval == Gdk.Key.Down)
- _buffer.append("LevelEditor:key_down(\"move_down\")");
- if (keyval == Gdk.Key.Right)
- _buffer.append("LevelEditor:key_down(\"move_right\")");
- if (keyval == Gdk.Key.Left)
- _buffer.append("LevelEditor:key_down(\"move_left\")");
- if (_keys.has_key(keyval)) {
- if (!_keys[keyval]) {
- _buffer.append(LevelEditorApi.key_down(key_to_string(keyval)));
- if (keyval == Gdk.Key.w)
- _buffer.append("LevelEditor._camera.actions.forward = true;");
- if (keyval == Gdk.Key.s)
- _buffer.append("LevelEditor._camera.actions.back = true;");
- if (keyval == Gdk.Key.a)
- _buffer.append("LevelEditor._camera.actions.left = true;");
- if (keyval == Gdk.Key.d)
- _buffer.append("LevelEditor._camera.actions.right = true;");
- if (keyval == Gdk.Key.q)
- _buffer.append("LevelEditor._camera.actions.up = true;");
- if (keyval == Gdk.Key.e)
- _buffer.append("LevelEditor._camera.actions.down = true;");
- }
- _keys[keyval] = true;
- }
- if (_buffer.len != 0) {
- _runtime.send_script(_buffer.str);
- _buffer.erase();
- _runtime.send(DeviceApi.frame());
- }
- return Gdk.EVENT_PROPAGATE;
- }
- public void on_key_released(uint keyval, uint keycode, Gdk.ModifierType state)
- {
- if (_keys.has_key(keyval)) {
- if (_keys[keyval]) {
- _buffer.append(LevelEditorApi.key_up(key_to_string(keyval)));
- if (keyval == Gdk.Key.w)
- _buffer.append("LevelEditor._camera.actions.forward = false");
- if (keyval == Gdk.Key.s)
- _buffer.append("LevelEditor._camera.actions.back = false");
- if (keyval == Gdk.Key.a)
- _buffer.append("LevelEditor._camera.actions.left = false");
- if (keyval == Gdk.Key.d)
- _buffer.append("LevelEditor._camera.actions.right = false");
- if (keyval == Gdk.Key.q)
- _buffer.append("LevelEditor._camera.actions.up = false");
- if (keyval == Gdk.Key.e)
- _buffer.append("LevelEditor._camera.actions.down = false");
- }
- _keys[keyval] = false;
- }
- if (_buffer.len != 0) {
- _runtime.send_script(_buffer.str);
- _buffer.erase();
- _runtime.send(DeviceApi.frame());
- }
- }
- public void on_motion(double x, double y)
- {
- int64 now = GLib.get_monotonic_time();
- if (now - _motion_last_time >= (1000*1000)/MOTION_EVENTS_RATE) {
- _motion_last_time = now;
- int scale = this.get_scale_factor();
- _runtime.send_script(LevelEditorApi.set_mouse_state((int)x*scale
- , (int)y*scale
- , _mouse_left
- , _mouse_middle
- , _mouse_right
- ));
- _runtime.send(DeviceApi.frame());
- }
- }
- public void on_scroll(double dx, double dy)
- {
- if (_keys[Gdk.Key.Shift_L]) {
- _runtime.send_script(LevelEditorApi.mouse_wheel(-dy));
- } else {
- _runtime.send_script("LevelEditor:camera_drag_start_relative('dolly')");
- _runtime.send_script("LevelEditor._camera:update(1,0,%.17f,1,1)".printf(-dy * 32.0));
- _runtime.send_script("LevelEditor:camera_drag_start('idle')");
- _runtime.send(DeviceApi.frame());
- }
- }
- public bool on_event_box_focus_out_event(Gdk.EventFocus ev)
- {
- camera_modifier_reset();
- _keys[Gdk.Key.Control_L] = false;
- _keys[Gdk.Key.Shift_L] = false;
- _runtime.send_script(LevelEditorApi.key_up(key_to_string(Gdk.Key.Control_L)));
- _runtime.send_script(LevelEditorApi.key_up(key_to_string(Gdk.Key.Shift_L)));
- return Gdk.EVENT_PROPAGATE;
- }
- public void on_size_allocate(Gtk.Allocation ev)
- {
- int scale = this.get_scale_factor();
- if (_allocation.x == ev.x
- && _allocation.y == ev.y
- && _allocation.width == ev.width
- && _allocation.height == ev.height
- )
- return;
- if (_last_window_id != _window_id) {
- _last_window_id = _window_id;
- native_window_ready(_window_id, ev.width*scale, ev.height*scale);
- }
- _allocation = ev;
- _runtime.send(DeviceApi.resize(_allocation.width*scale, _allocation.height*scale));
- // Ensure there is some delay between the last resize() and the last frame().
- if (_resize_timer_id == 0) {
- _resize_timer_id = GLib.Timeout.add_full(GLib.Priority.DEFAULT, 200, () => {
- _runtime.send(DeviceApi.frame());
- _resize_timer_id = 0;
- return GLib.Source.REMOVE;
- });
- }
- }
- public void on_event_box_realized()
- {
- this.get_window().ensure_native();
- #if CROWN_PLATFORM_LINUX
- this.get_display().sync();
- _window_id = gdk_x11_window_get_xid(this.get_window());
- #elif CROWN_PLATFORM_WINDOWS
- _window_id = gdk_win32_window_get_handle(this.get_window());
- #endif
- }
- public void on_enter(double x, double y)
- {
- this.grab_focus();
- }
- public bool on_tick(Gtk.Widget widget, Gdk.FrameClock frame_clock)
- {
- _runtime.send(DeviceApi.frame());
- return GLib.Source.CONTINUE;
- }
- public bool on_enable_accels()
- {
- ((LevelEditorApplication)GLib.Application.get_default()).set_conflicting_accels(true);
- _enable_accels_id = 0;
- return GLib.Source.REMOVE;
- }
- }
- } /* namespace Crown */
|