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

Commit

Permalink
Added additional documentation and comments.
Browse files Browse the repository at this point in the history
Added example code that should have been in the previous commit.
Fixed clippy lints.
Ran rust fmt to ensure the formatting was correct.
  • Loading branch information
jacobsky committed Sep 15, 2021
1 parent 1501202 commit 56c30a7
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 20 deletions.
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
15 changes: 6 additions & 9 deletions egui_stylist_addon/src/stylist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ 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>) {
Expand All @@ -26,7 +26,8 @@ impl GodotEguiStylist {
if gui.mouse_was_captured(instance) {
owner.accept_event();
}
}).expect("map_mut should succeed");
})
.expect("map_mut should succeed");
}

#[export]
Expand Down Expand Up @@ -170,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
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
11 changes: 6 additions & 5 deletions example_project/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ impl GodotEguiExample {
// 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");
})
.expect("map_mut should succeed");
}

/// Updates egui from the `_gui_input` callback
Expand All @@ -109,15 +110,16 @@ impl GodotEguiExample {
if gui.mouse_was_captured(instance) {
owner.accept_event();
}
}).expect("map_mut should succeed");
})
.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 @@ -129,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
16 changes: 10 additions & 6 deletions godot_egui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ impl GodotEgui {
theme_path: "".to_owned(),
}
}

/// Set the pixels_per_point use by `egui` to render the screen. This should be used to scale the `egui` nodes if you are using a non-standard scale for nodes in your game.
#[export]
pub fn set_pixels_per_point(&mut self, _owner: TRef<Control>, pixels_per_point: f64) {
if pixels_per_point > 0f64 {
Expand Down Expand Up @@ -161,13 +163,13 @@ impl GodotEgui {
godot_error!("file {} does not exist", &self.theme_path)
}
}

/// 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
}
/// Call from the user code to pass the input event into `Egui`.
/// 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]
Expand All @@ -184,7 +186,8 @@ impl GodotEgui {
// 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()
owner
.get_global_transform()
.inverse()
.expect("screen space coordinates must be invertible")
.transform_vector(offset_position)
Expand Down Expand Up @@ -304,7 +307,8 @@ impl GodotEgui {
);

// Paint the meshes
for (egui::ClippedMesh(_clip_rect, mesh), vs_mesh) in clipped_meshes.into_iter().zip(self.meshes.iter_mut())
for (egui::ClippedMesh(_clip_rect, mesh), vs_mesh) in
clipped_meshes.into_iter().zip(self.meshes.iter_mut())
{
// Skip the mesh if empty, but clear the mesh if it previously existed
if mesh.vertices.is_empty() {
Expand Down Expand Up @@ -361,7 +365,7 @@ impl GodotEgui {
/// This should only be necessary when you have a `reactive_update` GUI that needs to respond only to changes that occur
/// asynchronously (such as via signals) and very rarely such as a static HUD.
///
/// If the UI should be updated almost every frame due to animations or constant changes with data, favor setting `continous_update` to true instead.
/// If the UI should be updated almost every frame due to animations or constant changes with data, favor setting `continous_update` to true instead.
#[export]
fn refresh(&self, _owner: TRef<Control>) {
self.egui_ctx.request_repaint();
Expand All @@ -386,7 +390,7 @@ impl GodotEgui {

// Complete the frame and return the shapes and output
let (output, shapes) = self.egui_ctx.end_frame();

// Each frame, we set the mouse_was_captured flag so that we know whether egui should be
// consuming mouse events or not. This may introduce a one-frame lag in capturing input, but in practice it
// shouldn't be an issue.
Expand Down

0 comments on commit 56c30a7

Please sign in to comment.