Native UI development for Nim: https://github.com/nim-lang/uirelays
Extracted from NimEdit so we have some evidence the API scales well. It supports the development of custom drivers, the interface has deliberately been kept minimal.
Coming soon: A tiny DSL for layout computations.
Hi.
Two doubts: Will "uirelays" be having responsive layouts later? Also, the "touch inputs", in case needed for mobile experiments?
How is a UI library being compared to a game library?
I looked at the example code and it’s pretty much identical to what I’d write with raylib – there is a main loop where you react to events and draw the GUI using basic primitives like drawRectangle. I’m struggling to see any significant differences.
From what I can tell, this is an immediate-mode GUI with manual layouting? What are the advantages compared to mature options like Raylib?
That's essentially what it looks to be. Though with a bit of layout support now.
Overall the API would be good for simple apps, but looks hard to scale to more complex apps. Who knows where it could go though.
Start adding things like reusable combo boxes, lists, tables, theming, etc and it starts getting harder. It's sort of where I got to with Figuro and paused -- I'm not happy with ways to combine reusable components without MVC and/or OO patterns.
Meanwhile, I’m building an app in both figdraw and uirelays to learn them. And also submitting PRs. uirelays is wayyyy better than raygui. And I will not bother with C libraries again either. We can make better Nim libraries.
Definitely pure Nim libraries are better!
FigDraw is just to pull out the core rendering pieces and improve upon them. The idea is to provide a high performance renderer for others to build UI libraries on top. I'll probably port uirelays to FigDraw out of curiosity. :)
It's interesting to note how LLMs are able to identify existing patterns and suggest a name for them.
I don't really see much innovation in this. Encapsulating access to lower-level stuff somewhere is not a new idea. Global dependency injection via function pointers is older than most of us here, I think.
However, the value here is identifying the patterns and their useful combinations and presenting them as a system.
On the other hand, the danger is an LLM is very prone to misidentifying the pattern and substituting a common name with its own new label, hiding the pitfalls and issues that are already codified in existing code/literature under another name. You could call this "pattern laundering." By sanitizing "global mutable function pointers" into "Relays" we make the earlier experience harder to reference.
If someone runs into a bug with a "Relay" searching for it won't yield much useful info.
What the blog post omits, is some pitfalls the "relay" pattern carries, over the OO approach, for example. It is to some extent hostile to concurrency and multi-tenancy. If your app ever needs to talk to two backends simultaneously (like a real window and a headless buffer), the architecture falls apart. Hiding state management is convenient, until you need to access it.
Again, the systematization itself and imposing actual structure on old mechanism is valuable: strictly defined lifecycles, clear separation between drivers and adapters, etc.
“Pattern laundering” is just rhetoric. Naming a pattern and documenting its use is normal engineering, not deception. If you need more than what relays can offer, don't use relays, feel free to write your own abstraction that supports multiple-backends at the same time, etc.
I never claimed it was wholly novel; I wrote:
based on the idea of “relays” which is a new fancy name for dependency injections via global callbacks.
The novelty is in how the pattern is applied:
Both points are in the post—if read to the end.
I never claimed it was wholly novel; I wrote:
based on the idea of “relays” which is a new fancy name for dependency injections via global callbacks.
Well, you wrote it in a description field of the repo (had to look a bit to find the citation), I think it's important enough to mention in the blog post, hence my reply. And it's not just rhetoric, drawing direct lineages is important for education purposes, otherwise it reads as marketing.
I stand by the point that this is a repeating issue with using LLMs that ~we~ you, as esteemed engineers, should explicitly deal with, otherwise they will just happily multiply the isolated knowledge islands ad infinitum.
I notice the benefits of the system as presented, and underlined it as valuable above multiple times, so please don't read it as simple negativity.
Thanks, helped a lot.
I think dependency injection should be a feature of the language or stdlib for reason you cite: memory manager, testing, plugin system, etc.
I liked RequireJS (JS)/callPackage (Nix) solutions: A module is a function, with args (the dependencies), and they aren't "imported" but called with dependency resolved. In nix resolution comes from a 'service locator' (nixpkgs).
import callPackge
driver = callPackage(./sdl3.nix, {});
timer = callPackage(./timer.nix, { driver = driver });
timer.sleep(3);But you usually don call it directly, but use it in another module that expects it as arg
proc mod*(timer) =
timer.sleep(3)No idea how that would translate to Nim type system since JS and Nix have loose type system. Also nix has single export value, ie:
proc modA(timer) =
timer.sleep(3)
proc modB(timer) =
timer.sleep(3)This wouldn't be valid in nix, since it could have only one Array, Dict, Int, String, Proc as result of code evaluation.
I have no idea how this pattern is called.
I think dependency injection should be a feature of the language or stdlib for reason you cite: memory manager, testing, plugin system, etc.
100% agree! I just used module level dependency injection for my "chroniclers". I also used it for a work project with Ormin and db's.
I think it's an underdeveloped aspect of Nim. It's possible but not could be much more powerful. It also seems to align quite well with Nimony's plugin first philosophy.
Module level "relays" let you statically overload the procs or globals etc.
After lengthy discussions with Claude, relays in a raytracer as an experiment, very first working version.
https://gist.github.com/ingoogni/c4ca054e37a79706ff9c05c75b435467
I have started a POC some month ago to create a GL-Backend (base) in Nim that could be used for a Retained UI Framework. It is a learning project to understand the complexity of a Retained UI Framework. Some ideas and steps are mentioned in the readme and may vary.