Building C++ GUI Code that Calls Ren-C Wasm and Runs in Browser

There is a quite active Dear ImGui immediate mode GUI library running on Emscript: WebGui.

Dear ImGui provides just about any widget one might wish for.

Wasm WebGui demo runs at 60 FPS.

My roadmap is to embed Ren-C and bind the ImGui functions I need. I already have a skeleton parser that converts text into ImGUI function calls.

The code allows creating / modifying windows along with a few widgets at runtime.

Dear ImGUI uses a minimal C++ set and remains class and standard container free.

The only piece I'd be missing is the RPL interpreter if I am comprehending Ren-C feature set.

I'd also like to be able to grant filesystem access for the Wasm web instance using WASI API. Here's an example where WASI is already implements accessing an OS directory from a Web instance:

https://www.perplexity.ai/search/C-example-of-4jA7fNiiQSWtKthpleu2pQ?s=c

The use case can be distributing a chat GUI application launched through a browser running from the Internet using emscripten WebSocket support or even full TCP UDP connections.

If your use case is in a browser, then you want to leverage the ReplPad.

You could simply put the GUI in its own dockable window tab--which could sometimes be full screen, and sometimes share the screen with a Repl. See how the code editor widget was implemented. Instead of a code editor, imagine a graphics window there.

Unfortunately there's no way in ImGui itself to embed an iframe. So the Repl would have to be side by side or flipped to via a tab.

I believe what you want here isn't WASI. You want a WebAssembly "side module" written in C/C++ that the ReplPad can load into a tab (or allow to take over the whole screen)... and share the interpreter state between that side module and the ReplPad.

If this were done correctly, the ReplPad's Wasm could export the Ren-C API directly to the side module, so you could call into the interpreter without needing to use Emscripten facilities to jump out to JavaScript just to call the JS wrappers to call back into the Wasm interpreter again. Doing it incorrectly and calling JavaScript as a middleman could be a good first test.

You're better off in your C code using calls to Rebol for networking, and let the ReplPad's implementation of reading URLs and such (that go through JavaScript) do the work...rather than use Emscripten's janky layer that does the same thing but piped through weird and brittle emulations of Posix stuff.

(No offense to Emscripten intended--it's hard work done to provide for those who need it. But if you're taking a Rebol interpreter for granted and have access to the API that's already implementing natives in JavaScript, that's preferable to use.)

It does seem to have an active community. But my personal opinion is that Dear ImGui is very ugly, compared to e.g. TurboBadger.

And for that matter, I think modern CSS/HTML is pretty well tailored to the domain of UI layout with buttons and such. Reinventing it without any truly fundamental great ideas seems like wasted effort to me. So I'd think that you're better off just learning a bit of JavaScript and picking some JS UI framework. That's what Brian Otto did, if you missed this talk:

However... I do think there are some applications for C++ GUI code that doesn't really overlap what browsers are designed to natively do reasonably well. So putting a Dear ImGui window in a tab and having it be able to make calls into the interpreter wouldn't be a bad thing to be able to do.

The trick though is that aspect of getting it to be able to make plain C calls into the libRebol API.

By embedding I mean a direct call to the Ren-C API rather than running a Wasm instance within Dear ImGui.

By "embedding" in that sentence I was talking about having a widget inside of ImGui that shows HTML, and hence could have an IFRAME for the ReplPad.

Directly calling the Ren-C API from an ImGui-based Wasm blob loaded by ReplPad would require linking up symbols to the APIs of the already running instance in the browser. The link I provided about side modules suggests how this might be done. (I've seen Blazor do it, with lots of little wasm components that are able to talk to each other.)

I do indeed want to use Emscripten Dear ImGUI as the overall application container.

This buys cross OS cross hardware portability.

Besides the same code can be used to compile platform specific executables as well.

It also seems to me the friction-less approach to bring graphics to Ren-C.

OS file access is a lower priority as long as there is a way to backup the virtual file system.

As far as the Ren-C Repl, I presume there is a C version which accesses some sort of graphics library, or is it text only, that I can get to work within an ImGUI window.

It might make sense to provide the full WASI API to the emscirpten GUI instance.

This could be the equivalent to running a Wasm file server.

It could be possible for ReplPad to push code to the GUI instance, for debugging purpose for instance.

Due to how extensive and how fast Dear ImGUI window API is, my first thought was to centralize within the GUI.

It's easy to break ImGUI by pushing unlabelled widgets, but the bindings can take care of that.

To reuse the console module you have to provide it with a few functions like WRITE-STDOUT and READ-STDIN...connected with some kind of text widget.

Things like command history have to be part of your READ-STDIN implementation. The ReplPad and native executables thus have different history code--nothing is currently shared there.

But your real headaches are going to come from things like Ctrl-C cancellation and coordinating the event loop to yield while waiting for input so graphics messages can be processed. The ReplPad is proof this can be done (it uses escape instead of Ctrl-C) but it's far from easy to get right.

If you're saying you're trying to build r3-with-imgui.wasm for a browser, or flip a switch and build native desktop EXEs with Dear ImGui, then all you really want is to link with Ren-C as a C library. You don't need any webassembly for the native executables, and really there's no WASI in this picture at all. (The browsers don't provide WASI so emscripten speaks directly to JavaScript, and you don't need WASI for the native exes).

But if you look at main.c you can see what it takes to start up an interpreter instance. Imagine redoing that with ImGui. It may not seem that hard, but just not even considering WebAssembly at all, I will promise you that getting a console working in a GUI window with a textedit in it would be a pretty big undertaking. I suggest trying something like that before making too much of a bigger plan, if this is the direction you favor vs. my advice...

(Notice how Red--even when it's completely focused on the sole task of providing GUI, with full access to the OS APIs--after all these years can't let you type in the GUI console while a view window is up. :-/)

Maybe I'm wrong, but I'm fairly confident Emscripten's Posix emulation is not good enough to support libuv. So you'd be stuck doing your own implementations of things like READ on a URL or a FILE!. I've put forward the idea of just a basic I/O extension, and BASIC-READ and BASIC-WRITE are sort of a pointer into how that might be done.

It's much more advisable to just target the browser as your "OS independence" for a GUI app, and leverage all the painstaking work that has gone into ReplPad (not just the console experience itself, but all the bridging to JavaScript to get features like READ of URL! and such).

As mentioned earlier, I already have that part working in ImGUI.

In immediate mode graphics all widgets are pushed as vertices to the graphics card and rendered.

My editable text window contains my widget token loaded from a text file and the new window including the widgets is updated each frame all within a single application.

When I add or modify a token, in the text window, it displays in the target window or windows within a single frame.

1 Like

I'll take a look at main.c

It makes sense to try to get rebValue() to work first
before attempting to embed the interpreter.

If rebValue() works, you have embedded the interpreter, because it uses the interpreter.

See %a-lib.c for API implementation notes:

ren-c/src/core/a-lib.c at master · metaeducation/ren-c · GitHub

If you mean "embedding the Repl / Console" then yes, being able to build an EXE which calls rebValue is how you get the ball rolling.

1 Like