It would also be really nice to have a GUI framework that was built upon the components that come with nannou for consistency, such as event handling, graphics, colours, text layout, math (points, vectors, matrices), etc.
Seeing as so much work has already gone into it, it would be worth experimenting with how tightly we can integrate conrod so that it feels this transparent and tightly integrated. I can foresee there being some limitations though:
[f64; 2] for Point typesRange, Rect, etc)Rectangle, Triangles, etc)color module (nowhere near as comprehensive as the palette crate which we will probs use).If we decide to roll our own, we could still likely take a lot of logic directly from conrod to make the process easier (e.g. text layout, scroll logic, widget graph, etc).
It could be worth experimenting with a design that allowed for both Retained and Immediate style GUI too. I can imagine a retained-mode GUI providing an optional immediate-mode layer on top, where the immediate layer acted as a cache for retained widgets (this is pretty much how conrod works internally anyways, despite only providing an immediate API).
If we're to take this route, it would be worth fleshing out the graphics API first so that this can be built on top.
Another issue worth considering if we're to go the conrod route is that we'd have to synchronise conrod's glium dependency version with nannou's. This could get a bit annoying if e.g. we want to update to the latest glium but conrod is still a version behind, or if we want to update to the latest conrod but we haven't yet updated to use the same version of glium. This probably wouldn't be a huge issue in practise but is worth noting.
One major problem that has frequently occurred when using conrod for GUI in our last few projects is the need for quick iteration. I've slowly come to the realisation that it is simply not practical to manually hard-code entire GUIs with Rust - or really any form of heavily compiled language - purely due to the inevitable compilation time. Even a few seconds is far longer than one should have to wait when such a vast amount of trivial tweaking, iteration and refactoring of GUI layout is involved, especially in significantly complex GUI projects like the spatial audio server. It can make tasks that should take minutes become hours, hours become days, days become weeks, etc.
One thing I'd like to start thinking about is how to best approach a fully dynamic (yet type-safe) GUI system - one that would allow creating entirely new widgets capable of producing near arbitrary events, all at runtime. Currently, this is not trivially possible with Conrod. While the immediate mode approach of GUI is very well suited for writing GUIs in code, it makes it particularly difficult to treat widgets as data and programatically compose them together to create new ones at runtime. If instead we can come up with a system that does easily allow for this in a scalable, efficient fashion, I think it could become a standout feature within Nannou.
Here are some basic requirements that would massively increase flexibility compared to our situation with conrod today:
Inevitably, this will mean moving more work from compile time to run-time. Composition of widgets that would previously be hard-coded and statically compiled will now require some sort of representation in memory. GUI layouts that would previously be described by builder methods and the ordering of hard-coded widget instantiation will now need to be described in a way that can be changed while the program runs.
Fortunately however, none of these requirements ever come close to showing up as a bottleneck within GUIs of any significant size. Currently, there are two primary bottlenecks that show up in conrod GUIs.
set_widgets or "update" pass. One of the downsides of immediate mode GUI is that, at least to some extent, all widgets that should currently be viewable within a window must be instantiated each time an update occurs. Most often, widget implementations will cache as much expensive computation and resources as possible in order to make this instantiation cost minimal, however in GUIs that involve thousands of widgets or more this cost can eventually reach a point of significance. The cost becomes even more noticeable when the GUI is required to update every frame, perhaps in response to some event generated within the program itself as is commonly the case in games or real-time applications.In the proposed dynamic GUI system, the first bottleneck described above would be no more or less optimisable than it currently is. This is because the process of rendering the GUI is already a dynamic, run-time process. If the GUI system can be designed in a way that is general enough to allow for the requirements mentioned above, then it might also allow for a more generalised analysis of exactly what elements can be cropped / re-used / optimised away.
The second bottleneck described above would become a non-issue with a more run-time oriented GUI approach. This is because this entire bottleneck is a side-effect of the immediate-mode design, one of the purposes of which is to ease the burden of manually writing out GUIs in code - a problem that doesn't exist if we are moving towards a run-time oriented approach.
Like conrod, the GUI would need a fundamental set of "special", primitive widgets, from which all other widgets may be composed. These might include:
This list should aim to be the absolute minimal set of widgets needed, from which all other widgets can be created in an efficient manner. A simple example would be a Button composed of a Rectangle, ColourArea and an InteractiveArea.
One slightly trickier design problem is how to allow for this dynamic GUI representation to interact with the logic of the rest of the program. E.g. What is the ideal way for widgets to receive unique events from the program, emit certain events that can be handled by the program and to alter each other's layout at run-time. When implementing a List widget, how should we allow for the widget to specify that all child elements should shift upwards when a user scrolls down but only until the bottom element reaches the bottom of the list area? How can we allow for a custom Slider to both emit values upon interaction as well as receive and snap to certain values upon certain program events? This is something that conrod and the immediate approach in general is very good at. It's easy to code complex behaviours and interactions between widgets when it's just code, and the immediate mode design means that the GUI is always a reflection of the application state (though at the cost of requiring widgets to be frequently instantiated). Going to have to think more on this.
Most helpful comment
Dynamic GUI?
One major problem that has frequently occurred when using conrod for GUI in our last few projects is the need for quick iteration. I've slowly come to the realisation that it is simply not practical to manually hard-code entire GUIs with Rust - or really any form of heavily compiled language - purely due to the inevitable compilation time. Even a few seconds is far longer than one should have to wait when such a vast amount of trivial tweaking, iteration and refactoring of GUI layout is involved, especially in significantly complex GUI projects like the spatial audio server. It can make tasks that should take minutes become hours, hours become days, days become weeks, etc.
One thing I'd like to start thinking about is how to best approach a fully dynamic (yet type-safe) GUI system - one that would allow creating entirely new widgets capable of producing near arbitrary events, all at runtime. Currently, this is not trivially possible with Conrod. While the immediate mode approach of GUI is very well suited for writing GUIs in code, it makes it particularly difficult to treat widgets as data and programatically compose them together to create new ones at runtime. If instead we can come up with a system that does easily allow for this in a scalable, efficient fashion, I think it could become a standout feature within Nannou.
Requirements
Here are some basic requirements that would massively increase flexibility compared to our situation with conrod today:
Performance
Inevitably, this will mean moving more work from compile time to run-time. Composition of widgets that would previously be hard-coded and statically compiled will now require some sort of representation in memory. GUI layouts that would previously be described by builder methods and the ordering of hard-coded widget instantiation will now need to be described in a way that can be changed while the program runs.
Fortunately however, none of these requirements ever come close to showing up as a bottleneck within GUIs of any significant size. Currently, there are two primary bottlenecks that show up in conrod GUIs.
set_widgetsor "update" pass. One of the downsides of immediate mode GUI is that, at least to some extent, all widgets that should currently be viewable within a window must be instantiated each time an update occurs. Most often, widget implementations will cache as much expensive computation and resources as possible in order to make this instantiation cost minimal, however in GUIs that involve thousands of widgets or more this cost can eventually reach a point of significance. The cost becomes even more noticeable when the GUI is required to update every frame, perhaps in response to some event generated within the program itself as is commonly the case in games or real-time applications.In the proposed dynamic GUI system, the first bottleneck described above would be no more or less optimisable than it currently is. This is because the process of rendering the GUI is already a dynamic, run-time process. If the GUI system can be designed in a way that is general enough to allow for the requirements mentioned above, then it might also allow for a more generalised analysis of exactly what elements can be cropped / re-used / optimised away.
The second bottleneck described above would become a non-issue with a more run-time oriented GUI approach. This is because this entire bottleneck is a side-effect of the immediate-mode design, one of the purposes of which is to ease the burden of manually writing out GUIs in code - a problem that doesn't exist if we are moving towards a run-time oriented approach.
Design
Like conrod, the GUI would need a fundamental set of "special", primitive widgets, from which all other widgets may be composed. These might include:
This list should aim to be the absolute minimal set of widgets needed, from which all other widgets can be created in an efficient manner. A simple example would be a
Buttoncomposed of aRectangle,ColourAreaand anInteractiveArea.The Unsolved
One slightly trickier design problem is how to allow for this dynamic GUI representation to interact with the logic of the rest of the program. E.g. What is the ideal way for widgets to receive unique events from the program, emit certain events that can be handled by the program and to alter each other's layout at run-time. When implementing a
Listwidget, how should we allow for the widget to specify that all child elements should shift upwards when a user scrolls down but only until the bottom element reaches the bottom of the list area? How can we allow for a customSliderto both emit values upon interaction as well as receive and snap to certain values upon certain program events? This is something that conrod and the immediate approach in general is very good at. It's easy to code complex behaviours and interactions between widgets when it's just code, and the immediate mode design means that the GUI is always a reflection of the application state (though at the cost of requiring widgets to be frequently instantiated). Going to have to think more on this.