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
Fixed clippy lints.
Ran rust fmt to ensure the formatting was correct.
  • Loading branch information
jacobsky committed Sep 15, 2021
1 parent 1501202 commit 402ea31
Show file tree
Hide file tree
Showing 4 changed files with 83 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
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 402ea31

Please sign in to comment.