Skip to content

Allow eframe apps to be detached#3340

Closed
rockisch wants to merge 4 commits intoemilk:masterfrom
rockisch:run-detached
Closed

Allow eframe apps to be detached#3340
rockisch wants to merge 4 commits intoemilk:masterfrom
rockisch:run-detached

Conversation

@rockisch
Copy link

@rockisch rockisch commented Sep 15, 2023

One choice that I'm not really sure which I prefer is whether we should check for window.id() == window_id on WindowEvent events inside the on_event method or not. Since we're already exposing the window, the user could easily check that outside. However, when writing some example code, it ended up looking really bad and confusing. At the same time, people who will use this are probably people who have a good understanding of winit and GUI development in general, so maybe that's not an issue.

This gist shows how the 2 approaches would look like:
https://gist.github.com/rockisch/c672345661dd5411f5611b7ab14faea0

This initial version of the PR has the check inside, but I could remove it if wanted.

@emilk
Copy link
Owner

emilk commented Sep 18, 2023

This looks very promising! Can you please add a working example too? 🙏

@rockisch
Copy link
Author

Added a working example and fixed the CI issues (was on a Windows machine before, so I couldn't run the check.sh script)

@rockisch rockisch requested a review from emilk September 19, 2023 11:51
@rockisch rockisch marked this pull request as ready for review September 19, 2023 11:51
@rockisch
Copy link
Author

Btw, I went and changed one of my projects to this approach, and I noticed that although it does work, communication between the 'outer' code and the 'detached eframe app' is kinda clunky, since there's no way to access or modify the app data from the returned reference.

The way I'm doing this at the moment is by leaking a RefCell, more or less like this:

struct SharedState {
    flag: bool
}

struct MyApp {
    shared: &'static RefCell<SharedState>
}

let shared_state = Box::leak(Box::new(RefCell::new(SharedState { flag: true }))) as &_;
let detached_app = eframe::run_detached_native(
    ...,
    Box::new(|_cc| Box::new(MyApp { shared: shared_state }))
)
// 'shared_state' can be accessed and modified

How would you feel about exposing a get_app(&self) -> MyApp and get_app_mut(&mut self) -> MyApp on Detached? I took a look at the source code and I feel like I could add those with minimal changes.

@CoryRobertson
Copy link

not sure I follow entirely but would this pr allow you to open a completely detached eframe, meaning you would have two separate event loops?

@Aaron1011
Copy link
Contributor

What's the status of this PR? This would be very useful for Ruffle

@samyak-jain
Copy link

Btw, I went and changed one of my projects to this approach, and I noticed that although it does work, communication between the 'outer' code and the 'detached eframe app' is kinda clunky, since there's no way to access or modify the app data from the returned reference.

The way I'm doing this at the moment is by leaking a RefCell, more or less like this:

struct SharedState {
    flag: bool
}

struct MyApp {
    shared: &'static RefCell<SharedState>
}

let shared_state = Box::leak(Box::new(RefCell::new(SharedState { flag: true }))) as &_;
let detached_app = eframe::run_detached_native(
    ...,
    Box::new(|_cc| Box::new(MyApp { shared: shared_state }))
)
// 'shared_state' can be accessed and modified

How would you feel about exposing a get_app(&self) -> MyApp and get_app_mut(&mut self) -> MyApp on Detached? I took a look at the source code and I feel like I could add those with minimal changes.

Is the problem that we need to give away ownership of the MyApp instance? Shouldn't the shared state be accessible when you are implementing the update method on MyApp?

Btw, if there's anyway I can contribute, I would be happy to help.
Thanks for taking your time to implement this!

Copy link
Owner

@emilk emilk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a merging in of master and testing with multi-viewports, otherwise its looking good

@@ -0,0 +1,7 @@
Example showing some UI controls like `Label`, `TextEdit`, `Slider`, `Button`.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a good description

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And the screenshot is not very useful either

);
// Winit window managed by the application
let winit_window = winit::window::WindowBuilder::new()
.build(&event_loop)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Giving this a title of e.g. "winit window" would help explain to the user what is going on

Comment on lines +78 to +88
ui.heading("My egui Application");
ui.horizontal(|ui| {
let name_label = ui.label("Your name: ");
ui.text_edit_singleline(&mut self.name)
.labelled_by(name_label.id);
});
ui.add(egui::Slider::new(&mut self.age, 0..=120).text("age"));
if ui.button("Click each year").clicked() {
self.age += 1;
}
ui.label(format!("Hello '{}', age {}", self.name, self.age));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of showing the generic egui application, it would be better to show some text about what is going on in this example

/// An eframe app detached from the event loop.
///
/// This is useful to run `eframe` apps while still having control over the event loop.
/// For example, when you need to have a window managed by `egui` and another managed
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// For example, when you need to have a window managed by `egui` and another managed
/// For example, when you need to have a window managed by `eframe` and another managed

/// Execute an app in a detached state.
///
/// This allows you to control the event loop itself, which is necessary in a few
/// occasions, like when you need a screen not managed by `egui`.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// occasions, like when you need a screen not managed by `egui`.
/// occasions, like when you need a screen not managed by `eframe`.

Comment on lines +422 to +423
// Ignore window events for other windows
if let Some(window) = self.winit_app.window() {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you need to merge in master and handle the case of multi-viewports

@JackCrumpLeys
Copy link

JackCrumpLeys commented Apr 8, 2025

This seems dead. I would be interested in reviving and finishing off the PR. Would that be OK? I am not sure how much has changed here do you think it might be better to restart?

@wpbrown
Copy link
Contributor

wpbrown commented Apr 15, 2025

Yes this is quite divergent at this point. I have a new approach off master here that could use some review. #6750

@lucasmerlin lucasmerlin mentioned this pull request Apr 16, 2025
1 task
@emilk emilk closed this in #6750 Apr 29, 2025
@emilk emilk closed this in c075053 Apr 29, 2025
darkwater pushed a commit to darkwater/egui that referenced this pull request Aug 24, 2025
<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md)
before opening a Pull Request!

* Keep your PR:s small and focused.
* The PR title is what ends up in the changelog, so make it descriptive!
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to test and add commits to your PR.
* Remember to run `cargo fmt` and `cargo clippy`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.

Please be patient! I will review your PR, but my time is limited!
-->

* Closes emilk#2875
* Closes emilk#3340
* [x] I have followed the instructions in the PR template

Adds `create_native`. Similiar to `run_native` but it returns an
`EframeWinitApplication` which is a `winit::ApplicationHandler`. This
can be run on your own event loop. A helper fn `pump_eframe_app` is
provided to pump the event loop and get the control flow state back.

I have been using this approach for a few months.

---------

Co-authored-by: Will Brown <opensource@rebeagle.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Eframe] Allow eframe to run on an existing event loop

7 participants