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

Fixed _input being falsely detected when a Godot GUI element is on top of the other element. #9

Merged
merged 3 commits into from
Oct 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
61 changes: 61 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ Godot has a perfectly valid GUI system, so why `egui`? Here are my personal reas

These are minimal usage instructions. See the example project in `./example_project/` for a more advanced project.

### Configuration

First, import the `godot_egui` crate as a library in your project.

_Cargo.toml_
Expand Down Expand Up @@ -57,6 +59,65 @@ class_name = "GodotEgui"
library = ExtResource( 1 )
```

### Retreving Godot Input

In order to handle input, `GodotEgui` exposes the `handle_godot_input` and `mouse_was_captured` functions that can be used to pass input events from your node into `GodotEgui`.

To handle input from `_input` or `_unhandled_input` use the following:

```rust
#[export]
// fn _unhandled_input(&mut self, owner: TRef<Control>, event: Ref<InputEvent>) also works.
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");
}

```

To handle input from `_gui_input` use the following:

```rust
#[export]
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) {
// `_gui_input` uses accept_event() to stop the propagation of events.
owner.accept_event();
}
}).expect("map_mut should succeed");
}
```

### Important

`GodotEgui` translates the infomration from Godot's [`InputEvent`](https://docs.godotengine.org/en/stable/classes/class_inputevent.html#class-inputevent) class into the input format that `egui` expects. `GodotEgui` does not make any assumptions about how or when this data is input, it only translates and forwards the information to `egui`.

When determining how to handle the events, please refer to Godot's input propagation rules (see [the official documentation](https://docs.godotengine.org/en/stable/tutorials/inputs/inputevent.html) for more details), this cannot be easily translated perfectly for every architecture. As such, it is


**Note**: Regarding using `_gui_input` with `GodotEgui`. You will need to ensure that you are properly configuring `Control.mouse_filter` and that you understand how this works with `egui`.

- `Control::MOUSE_FILTER_STOP` - Consumes all input events. and it will not propagate up.
- `Control::MOUSE_FILTER_PASS` - Consumes all input events., but will allow unhandled events to propagate to the parent node.
- `Control::MOUSE_FILTER_IGNORE` - Does not consume input events.

**Note regarding `Control::MOUSE_FILTER_PASS`**:

The Godot engine assumption appears to be that most Controls that accept input will not overlap with other sibling nodes. As each `GodotEgui` constitutes it's own `egui::Context` (i.e. application), it will need to process any events that occur inside the `Control.rect.size`. This can lead to scenarios where input may be expected to propagate (i.e. `egui` does not detect that the mouse position overlaps any `egui::Widget` types) but does not.

The only current workaround is show in the [GodotEguiWindowExample](example_project/GodotEguiWindowExample.tscn) where UIs are parented to eachother in the order or priority they should response to the events.

### Drawing the `egui::Ui`

Finally, get a reference to that node as a `RefInstance<GodotEgui, Shared>` in your code and do this to draw the GUI using:

```rust
Expand Down
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
26 changes: 18 additions & 8 deletions egui_stylist_addon/src/stylist.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use egui_stylist::StylistState;
use gdnative::api::{FileDialog};
use gdnative::api::FileDialog;
use gdnative::prelude::*;
use godot_egui::GodotEgui;

Expand All @@ -16,6 +16,20 @@ impl GodotEguiStylist {
fn new(_: &Control) -> Self {
Self { style: StylistState::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 Expand Up @@ -157,13 +171,9 @@ fn read_file(filepath: &str) -> String {
pub fn load_theme(path: GodotString) -> egui_theme::EguiTheme {
// Load the GodotEguiTheme via the ResourceLoader and then extract the EguiTheme
let file_path = path.to_string();

// We should allow for both godot resources as well as the vanilla .ron files to be published.
let theme = {
let file = read_file(&file_path);
ron::from_str(&file).expect("this should load")
};
theme

let file = read_file(&file_path);
ron::from_str(&file).expect("this should load")
}

pub fn save_theme(path: GodotString, theme: egui_theme::EguiTheme) {
Expand Down
10 changes: 6 additions & 4 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 = false
handle_gui_input = true

[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
}
continous_update = true
consume_mouse_events = true
disable_texture_filtering = false
scroll_speed = 20.0
EguiTheme = "res://godot-editor.eguitheme"
disable_texture_filtering = false
continous_update = true
input_mode = 0
8 changes: 8 additions & 0 deletions example_project/GodotEguiWindowExample.gdns
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[gd_resource type="NativeScript" load_steps=2 format=2]

[ext_resource path="res://godot_egui.gdnlib" type="GDNativeLibrary" id=1]

[resource]
resource_name = "GodotEguiWindowExample"
class_name = "GodotEguiWindowExample"
library = ExtResource( 1 )
18 changes: 18 additions & 0 deletions example_project/GodotEguiWindowExample.tscn
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[gd_scene load_steps=3 format=2]

[ext_resource path="res://GodotEgui.gdns" type="Script" id=1]
[ext_resource path="res://GodotEguiWindowExample.gdns" type="Script" id=2]

[node name="GodotEguiWindowExample" type="Control"]
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource( 2 )
__meta__ = {
"_edit_use_anchors_": false
}

[node name="GodotEgui" type="Control" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
mouse_filter = 1
script = ExtResource( 1 )
21 changes: 21 additions & 0 deletions example_project/multi-demo-example.tscn
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[gd_scene load_steps=3 format=2]

[ext_resource path="res://GodotEguiExample.tscn" type="PackedScene" id=1]
[ext_resource path="res://GodotEguiWindowExample.tscn" type="PackedScene" id=2]

[node name="multi-demo-example" type="Node2D"]

[node name="GodotEguiExample" parent="." instance=ExtResource( 1 )]
margin_right = 1024.0
margin_bottom = 600.0
mouse_filter = 1

[node name="GodotEguiWindowExample" parent="GodotEguiExample" instance=ExtResource( 2 )]
margin_right = 1024.0
margin_bottom = 600.0
mouse_filter = 1

[node name="GodotEguiWindowExample2" parent="GodotEguiExample/GodotEguiWindowExample" instance=ExtResource( 2 )]
margin_right = 1024.0
margin_bottom = 600.0
mouse_filter = 1
46 changes: 43 additions & 3 deletions 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,15 +85,41 @@ 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.
let frame = egui::Frame { margin: egui::vec2(20.0, 20.0), ..Default::default() };
Expand All @@ -91,11 +131,10 @@ impl GodotEguiExample {
if self.dynamically_change_pixels_per_point {
gui.set_pixels_per_point(instance, (self.elapsed_time.sin() * 0.20) + 0.8);
}

// We use the `update` method here to just draw a simple UI on the central panel. If you need more
// fine-grained control, you can use update_ctx to get access to egui's context directly.
gui.update_ctx(instance, /* Some(frame), */ |ctx| {

egui::CentralPanel::default().frame(frame).show(ctx, |ui| {
ui.columns(2, |columns| {
let ui = &mut columns[0];
Expand Down Expand Up @@ -222,6 +261,7 @@ impl GodotEguiExample {

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

Expand Down
60 changes: 60 additions & 0 deletions example_project/src/window.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use gdnative::prelude::*;
use godot_egui::*;

#[derive(NativeClass)]
#[inherit(Control)]
pub struct GodotEguiWindowExample {
gui: Option<Instance<GodotEgui, Shared>>,
}

#[methods]
impl GodotEguiWindowExample {
fn new(_: &Control) -> Self {
Self { gui: None }
}
#[export]
#[gdnative::profiled]
pub fn _ready(&mut self, owner: TRef<Control>) {
godot_print!("Initializing godot egui");
let gui = owner
.get_node("GodotEgui")
.and_then(|godot_egui| unsafe { godot_egui.assume_safe() }.cast::<Control>())
.and_then(|godot_egui| godot_egui.cast_instance::<GodotEgui>())
.expect("Expected a `GodotEgui` child with the GodotEgui nativescript class.");

self.gui = Some(gui.claim());
}

/// 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]
fn _process(&mut self, _owner: TRef<Control>, _: f64) {
if let Some(gui) = &self.gui {
let gui = unsafe { gui.assume_safe() };
gui.map_mut(|egui, o| {
egui.update_ctx(o, |ctx| {
egui::Window::new("Test Window")
.frame(egui::Frame::default())
.min_height(100.0)
.min_width(50.0)
.resizable(true)
.show(ctx, |ui| {
ui.label("This is a window!");
});
})
})
.expect("this should work");
}
}
}