Skip to content
This repository has been archived by the owner on Apr 9, 2024. It is now read-only.

Commit

Permalink
Refactored egui's input handling. GodotEgui now no longer receives …
Browse files Browse the repository at this point in the history
…godot input callbacks. Instead `GodotEgui` requires the parent node to pass the relevant `InputEvent` received from any of Godot's callbacks into `handle_godot_input` with a flag for whether it is gui input as only gui handles mouse positions differently.

This also includes a fix to allow for free rotation of the egui nodes via the the parent's rotation.
  • Loading branch information
jacobsky committed Sep 15, 2021
1 parent 37e610c commit 798505c
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 27 deletions.
1 change: 1 addition & 0 deletions egui_stylist_addon/addons/egui_stylist/egui_stylist.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ __meta__ = {
[node name="godot_egui" type="Control" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
focus_mode = 1
size_flags_horizontal = 3
size_flags_vertical = 3
script = ExtResource( 2 )
Expand Down
15 changes: 14 additions & 1 deletion egui_stylist_addon/src/stylist.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use egui_stylist::StylerState;
use gdnative::api::{FileDialog};
use gdnative::api::FileDialog;
use gdnative::prelude::*;
use godot_egui::GodotEgui;

Expand All @@ -16,6 +16,19 @@ impl GodotEguiStylist {
fn new(_: &Control) -> Self {
Self { style: StylerState::default(), godot_egui: None, file_dialog: None }
}

/// Updates egui from the `_gui_input` callback
#[export]
pub fn _gui_input(&mut self, owner: TRef<Control>, event: Ref<InputEvent>) {
let gui = unsafe { self.godot_egui.as_ref().expect("GUI initialized").assume_safe() };
gui.map_mut(|gui, instance| {
gui.handle_godot_input(instance, event, true);
if gui.mouse_was_captured(instance) {
owner.accept_event();
}
}).expect("map_mut should succeed");
}

#[export]
fn _ready(&mut self, owner: TRef<Control>) {
let gui = owner
Expand Down
6 changes: 4 additions & 2 deletions example_project/GodotEguiExample.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,18 @@ script = ExtResource( 2 )
__meta__ = {
"_edit_use_anchors_": false
}
handle_input = true
handle_gui_input = false

[node name="GodotEgui" type="Control" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
mouse_filter = 2
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
scroll_speed = 20.0
continous_update = true
consume_mouse_events = true
disable_texture_filtering = false
scroll_speed = 20.0
EguiTheme = "res://godot-editor.eguitheme"
41 changes: 40 additions & 1 deletion example_project/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use egui::ComboBox;
use gdnative::prelude::*;
use godot_egui::GodotEgui;
mod window;
use window::GodotEguiWindowExample;

pub fn load_texture(path: &str) -> Ref<Texture> {
let loader = ResourceLoader::godot_singleton();
Expand All @@ -26,6 +28,10 @@ pub struct GodotEguiExample {
/// # Warning: This setting is very performance intensive and for demonstration purposes only.
#[property(default = false)]
dynamically_change_pixels_per_point: bool,
#[property]
handle_gui_input: bool,
#[property]
handle_input: bool,
}

#[gdnative::methods]
Expand All @@ -44,12 +50,20 @@ impl GodotEguiExample {
show_font_settings: false,
text_edit_text: "This is a text edit!".to_owned(),
dynamically_change_pixels_per_point: false,
handle_gui_input: false,
handle_input: false,
}
}

#[export]
#[gdnative::profiled]
pub fn _ready(&mut self, owner: TRef<Control>) {
owner.set_process_input(self.handle_input);
if self.handle_gui_input {
owner.set_mouse_filter(Control::MOUSE_FILTER_STOP);
} else {
owner.set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
}
godot_print!("Initializing godot egui");
let gui = owner
.get_node("GodotEgui")
Expand All @@ -71,14 +85,38 @@ impl GodotEguiExample {
.stroke(egui::Stroke::new(3.0, egui::Color32::from_rgb(200, 100, 100)))
.name("wave")
}
/// Updates egui from the `_input` callback
#[export]
#[gdnative::profiled]
pub fn _input(&mut self, owner: TRef<Control>, event: Ref<InputEvent>) {
let gui = unsafe { self.gui.as_ref().expect("GUI initialized").assume_safe() };
gui.map_mut(|gui, instance| {
gui.handle_godot_input(instance, event, false);
if gui.mouse_was_captured(instance) {
// Set the input as handled by the viewport if the gui believes that is has been captured.
unsafe { owner.get_viewport().expect("Viewport").assume_safe().set_input_as_handled() };
}
}).expect("map_mut should succeed");
}

/// Updates egui from the `_gui_input` callback
#[export]
#[gdnative::profiled]
pub fn _gui_input(&mut self, owner: TRef<Control>, event: Ref<InputEvent>) {
let gui = unsafe { self.gui.as_ref().expect("GUI initialized").assume_safe() };
gui.map_mut(|gui, instance| {
gui.handle_godot_input(instance, event, true);
if gui.mouse_was_captured(instance) {
owner.accept_event();
}
}).expect("map_mut should succeed");
}
#[export]
#[gdnative::profiled]
pub fn _process(&mut self, _owner: TRef<Control>, delta: f64) {
let gui = unsafe { self.gui.as_ref().expect("GUI initialized").assume_safe() };

self.elapsed_time += delta;


// A frame can be passed to `update` specifying background color, margin and other properties
// You may also want to pass in `None` and draw a background using a regular Panel node instead.
Expand Down Expand Up @@ -222,6 +260,7 @@ impl GodotEguiExample {

fn init(handle: InitHandle) {
handle.add_class::<GodotEguiExample>();
handle.add_class::<GodotEguiWindowExample>();
godot_egui::register_classes(handle);
}

Expand Down
49 changes: 26 additions & 23 deletions godot_egui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,6 @@ pub struct GodotEgui {
#[property]
scroll_speed: f32,

/// Whether or not this egui should call set_input_as_handled after receiving a mouse event.
#[property]
consume_mouse_events: bool,

/// When enabled, no texture filtering will be performed. Useful for a pixel-art style.
#[property]
disable_texture_filtering: bool,
Expand Down Expand Up @@ -115,7 +111,6 @@ impl GodotEgui {
mouse_was_captured: false,
continous_update: false,
scroll_speed: 20.0,
consume_mouse_events: true,
disable_texture_filtering: false,
pixels_per_point: 1f64,
theme_path: "".to_owned(),
Expand All @@ -134,7 +129,10 @@ impl GodotEgui {
/// Run when this node is added to the scene tree. Runs some initialization logic, like registering any
/// custom fonts defined as properties
#[export]
fn _ready(&mut self, _owner: TRef<Control>) {
fn _ready(&mut self, owner: TRef<Control>) {
// This node should **never** take input events or be capable of taking focus from another node.
owner.set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
owner.set_focus_mode(Control::FOCUS_NONE);
// Run a single dummy frame to ensure the fonts are created, otherwise egui panics
self.egui_ctx.begin_frame(egui::RawInput::default());
self.egui_ctx.set_pixels_per_point(self.pixels_per_point as f32);
Expand Down Expand Up @@ -163,36 +161,45 @@ impl GodotEgui {
godot_error!("file {} does not exist", &self.theme_path)
}
}

fn maybe_set_mouse_input_as_handled(&self, owner: TRef<Control>) {
if self.mouse_was_captured && self.consume_mouse_events {
unsafe { owner.get_viewport().expect("Viewport").assume_safe().set_input_as_handled() }
}
/// Is used to indicate if the mouse was captured during the previous frame.
#[export]
pub fn mouse_was_captured(&self, _owner: TRef<Control>) -> bool {
self.mouse_was_captured
}

/// Callback to listen for input. Translates input back to egui events.
/// Call from the user code to pass the input event into `Egui`.
/// `event` should be the raw `InputEvent` that is handled by `_input`, `_gui_input` and `_unhandled_input`.
/// `is_gui_input` should be true only if this event should be processed like it was emitted from the `_gui_input` callback.
#[export]
fn _input(&mut self, owner: TRef<Control>, event: Ref<InputEvent>) {
pub fn handle_godot_input(&mut self, owner: TRef<Control>, event: Ref<InputEvent>, is_gui_input: bool) {
let event = unsafe { event.assume_safe() };
let mut raw_input = self.raw_input.borrow_mut();
let pixels_per_point = self.egui_ctx.pixels_per_point();
// Transforms mouse positions in viewport coordinates to egui coordinates.
let mouse_pos_to_egui = |mouse_pos: Vector2| {
// NOTE: The egui is painted inside a control node, so its global rect offset must be taken into account
let transformed_pos = mouse_pos - owner.get_global_rect().origin.to_vector();
let transformed_pos = if is_gui_input {
// Note: The `_gui_input` callback adjusts the offset before adding the event.
mouse_pos
} else {
// NOTE: The egui is painted inside a control node, so its global rect offset must be taken into account.
let offset_position = mouse_pos - owner.get_global_rect().origin.to_vector();
// This is used to get the correct rotation when the root node is rotated.
owner.get_global_transform()
.inverse()
.expect("screen space coordinates must be invertible")
.transform_vector(offset_position)
};
// It is necessary to translate the mouse position which refers to physical pixel position to egui's logical points
// This is found using the inverse of current `pixels_per_point` setting.
let points_per_pixel = 1.0 / pixels_per_point;
egui::Pos2 { x: transformed_pos.x * points_per_pixel, y: transformed_pos.y * points_per_pixel }
};

if let Some(motion_ev) = event.cast::<InputEventMouseMotion>() {
self.maybe_set_mouse_input_as_handled(owner);
raw_input.events.push(egui::Event::PointerMoved(mouse_pos_to_egui(motion_ev.position())))
}

if let Some(button_ev) = event.cast::<InputEventMouseButton>() {
self.maybe_set_mouse_input_as_handled(owner);
if let Some(button) = enum_conversions::mouse_button_index_to_egui(button_ev.button_index()) {
raw_input.events.push(egui::Event::PointerButton {
pos: mouse_pos_to_egui(button_ev.position()),
Expand Down Expand Up @@ -389,7 +396,7 @@ impl GodotEgui {
// `output.needs_repaint` lets `GodotEgui` know whether we need to redraw the clipped mesh and repaint the new texture or not.
if output.needs_repaint {
let clipped_meshes = self.egui_ctx.tessellate(shapes);
self.paint_shapes(owner, clipped_meshes, &self.egui_ctx.texture());
self.paint_shapes(owner, clipped_meshes);
}
}

Expand All @@ -410,10 +417,6 @@ impl GodotEgui {
.show(egui_ctx, draw_fn);
})
}

pub fn mouse_was_captured(&self) -> bool {
self.mouse_was_captured
}
}

/// Helper method that registers all GodotEgui `NativeClass` objects as scripts.
Expand Down

0 comments on commit 798505c

Please sign in to comment.