What is Conrod?
It provides an immediate-mode API that wraps a retained-mode widget state graph. This allows us to expose a simple, robust and reactive interface while approaching the performance of traditional, retained GUI frameworks.
We provide a set of primitive and commonly useful widgets, along with a Widget API that allows for designing new, custom widgets composed of unique logic and other widgets.
Widgets are instantiated using the “Builder” aka “Method Chaining” pattern within an update loop. In this sense, we can think of a Widget and its builder methods as a function with a highly flexible and readable set of arguments.
If you have an awesome looking GUI made with conrod, please send us a screenshot and we’ll add it here :)
Here’s a youtube demo of the all widgets example.
Here’s another youtube demo of conrod being used to create a basic synth editor.
A multimedia timeline widget.
The canvas.rs example.
- Reactive and Immediate API. Produce the UI from your application state in one place, rather than as a separate entity spread across callbacks, instantiation, updates and drawing.
- A Widget trait to simplify the composition and design of widgets.
- Scrolling that is opt-in, generalised and easy-to-use. Call
.scroll_kids(true)on a widget and it will become a scrollable container for its children.
- Powerful layout and positioning:
- Placement -
- Alignment -
- Relative -
.x_y_relative(20.0, 42.0), etc.
- Absolute -
- Draggable pop-up / floating canvasses -
- WidgetMatrix and PositionMatrix for instantiating a grid of widgets.
- Placement -
- **Theme**s for unique style and layout defaults.
- widget_ids! macro for easily and safely generating unique widget identifiers.
- Generic over events and graphics backends - compatible with:
- Provides a collection of commonly useful widgets.
Primitive widgets are the fundamental graphical components in any conrod user interface. They are a
special set of widgets from which all other widgets may be composed. For example, a
may be composed of one
Rectangle primitive widget and one
Text primitive widget. A
DropDownList may be composed of n
Button widgets, and in turn n
widgets and n
Text primitive widgets.
When drawing the widget graph to the screen, conrod only draws the primitive widgets. This allows widget designers to create custom widgets by simply instantiating other widgets, without having to directly write any graphics code themselves. This also greatly simplifies the caching of common widget state like text, positioning and styling as the primitive widgets take care of all of this automatically.
The following are the primitive widgets provided by conrod:
- Text - automatic line-wrapping, line spacing, etc.
If conrod is lacking a primitive widget that you require, please let us know! There are currently plans to allow for a user to design their own primitive widgets, however these will likely remain low priority until requested.
Common use widgets are simply widgets that are commonly required by GUIs. There is nothing special about these widgets in the sense that they could just as easily be implemented in an external crate by third-party users. However, their usage is common enough that we choose to distribute them with conrod itself.
The following are the common use widgets provided by conrod:
- Canvas - a container-like widget
- Scrollbar - emits scroll events to some target scroll widget
- Tabs - for easily switching between multiple Canvasses with ease
- TextBox - an editable one-line text field
- TextEdit - an editable block of text
The following are planned, but not yet implemented:
- Menu Bar / Tool Bar
- Right-click Context Menu
- Graph / Chart
- File/Directory Navigator
- Advanced graph visualisation and control
If you notice that we’re missing important widgets, or if you have a widget that you feel would make a nice addition to conrod, please let us know by leaving an issue or pull request! Be sure to check issues with the widget label first, as your desired widget may have already been requested.
The term “Immediate Mode” describes a style of user interface API.
In an immediate mode GUI, widgets are instantiated using functions in an update or draw loop. This is quite different to the more traditional “retained mode”, where widget types are constructed during the setup stage.
Immediate mode encourages a less stateful, more data-driven design as the user interface is instantiated as a result of the application state every frame or update. On the other hand, retained user interfaces tend to work the other way around, driving the application by sending events or triggering callback functions upon user interaction.
Immediate mode tends to be the easier style to use for highly dynamic interfaces and those that require frequent synchronisation with application state. This is because immediate widgets handle changes in application state at the same place in which they are instantiated (during the update or draw stage). While retained interface logic is often divided up between instantiation, updating, drawing and event handling, immediate interfaces aim to consolidate all UI related logic within one place.
Historically, due to their statelessness and frequent re-instantiation, immediate mode interfaces have been known to pay the price of performance for their convenience. Although retained interfaces can be more tedious to maintain and synchronise with application state, they are often less CPU intensive as the majority of their state remains untouched following the application’s setup stage.
Conrod aims to adopt the best of both worlds by providing an immediate mode API over a hidden,
retained widget state graph. From the user’s perspective conrod widgets tend to feel stateless, as
though they only exist for the lifetime of the scope in which they are instantiated. In actuality,
each widget’s state is cached (or “retained”) within the
Ui’s internal widget graph. This allows
for efficient re-use of allocations and the ability to easily defer the drawing of widgets to a
stage that is more suited to the user’s application. As a result, Conrod should be able to provide
the convenience of an immediate mode API alongside performance that approaches that of traditional,
The “Builder Pattern” describes the process of “building” a type by chaining together method calls. Conrod uses this pattern within its API for widget instantiation. This pattern is perhaps easiest to grasp with an example.
Let’s say we have a
Button widget. We want to make sure that a user may instantiate it with
specific dimensions, position, color, label, label font size and label color. A naiive constructor
might look like this:
Button::new(dimensions, position, color::RED, "DONT PRESS", label_font_size, label_color, || do_stuff(), BUTTON_ID, &mut ui);
The problem is that most of the time users will just want the
Button to choose defaults straight
Theme, rather than specifying every parameter for every instance of a
themselves. We want parameters to be optional. Perhaps then, we should make optional parameters
Button::new(Some(dimensions), Some(position), None, "DONT PRESS", None, None, Some(|| do_stuff()), BUTTON_ID, &mut ui);
Although the user no longer has to think about the values for parameters they don’t care about,
this is still extremely verbose and the
None parameters seem quite ambiguous.
In some languages, this problem might be solved using a combination of features called “named parameters” and “default parameters”. If rust had these features, the above might look like this:
Button::new(dimensions=dimensions, position=position, label="DONT PRESS", react=|| do_stuff(), id=BUTTON_ID, ui=&mut ui);
Certainly an improvement in conciseness and readability. Although these features are not available to us in Rust, there is a way in which we can emulate their behaviour - This is where the builder pattern comes in.
By moving each parameter out of the
new function args and into their own methods, we can treat
methods almost as though they are named parameters as in the above example. Here is what the above
might look like using the builder pattern:
Button::new() .dimensions(dimensions) .position(position) .label("DONT PRESS") .react(|| do_stuff()) .id(BUTTON_ID) .ui(&mut ui);
This is in fact the method of widget instantiation used by conrod, though with some slight differences in method naming (best to check the examples directory for a proper demo).
Although this certainly seems like the nicest solution from an API perspective, the
attentive rustacean may notice that this requires extra work for the widget designer compared to
the previous examples. Previously, all work for widget instantiation was done within the
function. The builder pattern implementation introduces a few differences worth noticing:
- A method must be implemented on
Buttonfor every optional parameter.
Button::newfunction must return some type that can be treated as a set of
Optionarguments which can be set as it is passed from builder method to builder method.
- There must be some method that indicates the end of building and the instantiation of the widget.
In Conrod, we are of the opinion that, in the common case, the extra work on behalf of the widget designers is worth the benefits gained by the users. However, we also make a significant effort to address each of the above points best we can in order to reduce the workload of widget developers:
- We provide a
builder_methods!macro which reduces the boilerplate of builder method implementation.
- In Conrod, widgets are treated as a set of arguments to their state, rather than containing
their own state themselves. This suits the immediate mode style of conrod, where widget state is
hidden and stored within the
- All Conrod widgets must take a unique identifier and a mutable reference to the
Ui. We consolidate this into a single
.set(ID, &mut ui)method, which we also use as the indication method to stop building and instantiate the widget within the
All of these points will be covered later in more detail within the widget implementation section of the guide.