-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Alternative version of callback-taking functions for making API bindings #1004
Comments
I might be wrong, but i think the point of closures is that they will run only if necessary (ui is shown). With a straight sequence style of begin and end functions, we will have to throw around if shown else not shown kind of code. Or is there a more idiomatic code style? |
egui passes closures to functions to call, like this: ui.collapsing_header("Foo", |ui| {
// …
}); These aren't really "callbacks" in the classical sense since they are called immediately (or never). The reason for them is that the parent One alternative could be this (used e.g. by Dear ImGui and Nuklear): if ui.collapsing_header_begin("Foo") {
// …
}
ui.collapsing_header_end(); This has two downsides: A) it is way to easy to put the We could of course consider adding support for a lower lever API like this, but due to B) above it would require a huge rewrite and redesign of egui. Another alternative us using linear types (types you must manually close): if let Some(child) = parent.collapsing_header("Foo") {
// …
child.end(parent); // must call, or it won't compile
} Linear types aren't supported by Rust (though you can approximate them with hacks that yields linker errors if you fail to close them), but it also creates a lot of If Rust had something like Pythons with ui = ui.collapsing_header("Foo") {
// …
} and have the compiler insert the |
Thanks for the explanation! What about something like RAII objects? This seems to be the pattern the standard library uses to run tear-down code, for example unlocking mutexes. |
It might be ok to impl |
A RAII approach would also require a I would be more interested in investigating a more low-level C-like API (with begin/end) and then build the current |
Another downside of the current closure approach is code bloat and long compile times (due to excessive monomorphization) |
+1, I want to bind egui to python but the callback approach is much worse than something I can wrap in a context manager I'm imagining this to prototype it:
but I'd really like to be able to do something like this (which I confirmed properly nests scope in Python without persistently shadowing the outer ui variable)
My vote is to try this with Drop:
I looked into I think |
I did a survey of how egui tends uses the inner responses internally after calling add_contents. It mostly accesses the rect and propagates the closure's return value up.
|
Yes, a fluent API is great for programmers, but not friendly to automation, which is what a scripting system ultimately is. There should be a low-level, imperative, step-by-step API for machine-controlled environments. |
This is incomplete, but I'm working on a concept here:
Basically, a method like Children don't need a reference to their parent, the context manager can be a short lived object that has a reference to the parent, the child, the mutable Result object, and its internal state necessary for exit(). The This can be made compatible with the existing API:
You could also provide this as separate API traits / structs, used as a backend for old callback API, but with similar method names. |
This approach seems somewhat clean for defining the cleanup function at call sites: If necessary the Context object could be either dynamic or templated, allowing each specific call site to stash arbitrary data. |
If there is going to be a partial low level rewrite of egui api, then we might as well check if the new api will be suitable to write bindings for at the very least in wasm/deno_core/pyo3 or rustpython/mlua. If the new api is fine for one of these and not for others, it would be really sad. Ideally egui will have a simpler C based abi / ffi compatible as that will be the most compatible with all languages. (including rust dylibs) |
Yes, I'm specifically doing this for PyO3. (Technically you could probably bind to the previous API, but it would require a really awkward rust -> language -> rust -> language stack to satisfy the egui callback API.) I've prototyped a new context manager style here: https://gist.github.com/d830916a42f650eae3a590e24b0d1504 It allows you to write widgets something like this, with both the setup and teardown code in the same function: fn horizontal(&mut self, render: bool) -> Option<ContextManager> {
if !render { return None; }
let child = Ui{id: self.id + 1, parent: self.id, data: 0};
let local = 1;
self.cm(child, move |parent, child| {
println!("cm exit parent={:?} child={:?} local={}", parent, child, local);
child.data += 1;
parent.data += 1;
})
}
fn cm(&mut self, child: Ui, f: impl FnOnce(&mut Ui, &mut Ui) + 'static) -> Option<ContextManager> {
Some(ContextManager::new(f, self, child))
} It can be polyfilled to the old callback API style like this: fn horizontal_cb<R>(&mut self, f: impl FnOnce(&mut Self) -> R, render: bool) -> Option<R> {
match self.horizontal(render) {
Some(mut cm) => Some(f(&mut cm.child)),
None => None,
}
} (You'd combine this with my Ui trait e.g. shown here #1004 (comment) so both the @emilk thoughts? |
I started a port here: https://github.com/talonvoice/egui/tree/no-callback I ported everything in ui.rs to my context manager approach, with a Here's an example of one of the method pairs: https://github.com/talonvoice/egui/blob/no-callback/egui/src/ui.rs#L892-L928 I have two notes:
|
If you implement |
I am automatically generating my GUI and it would greatly simplify my life if there would be a simple "low-level" API like |
I'm working out how to wrap egui's API for my scripting language. Something that would make this a lot easier is if there was an alternative API to the functions that take callbacks (like
SidePanel::show
,Window::show
, andUi::horizontal
) which could be written in straight-line code. For example, an object that exposes a&mut UI
to draw widgets, and has afinish
method which drops it.It looks like I can emulate this with some pairs of
Ui::new
andUi::allocate_rect
, but I'll have to rewrite all the useful stuff those functions do.The text was updated successfully, but these errors were encountered: