My first month with ICED

A month with ICED.

I had a little pet project GUI I wanted to do with Rust and wen t looking for a GUI toolkit for the task and decided ICED was worth a try. I’d previously done a larger project with Rust and GTK4 and found the pairing of GTK and Rust clumsy.

So how did I go with ICED.
image

The Good

Not surprisingly ICED and Rust fit together very well. ICED is written in Rust and uses the Rust idioms throughout, so if you are reasonably competent in Rust and I am no expert, you should have little trouble using ICED.

Like with most things Rust, if your ICED program builds, then it probably works as expected. There is still the opportunity for out of bounds panics, but not much else seems to go wrong.

The learning curve

The examples found at, iced/examples at master · iced-rs/iced · GitHub, are good and quite comprehensive. They can be a little hard to follow at first, because they contain no comments and this is one place where I feel comments could be really helpful.

The other thing about ICED is the model that it follows is intrinsically very simple and easy to get to terms with.

ICED is however very different from all the other GUI toolkits I have used over 30 years, from Smalltalk V through Visual Basic, Delphi, Java Swing * SWT and GTK. ICED is a retained mode GUI, but not as you know it.

Widgets are not ‘owned’ by the application and do not hold any state that can be accessed by the program. In a traditional GUI you would construct a window or part thereof when the window is constructed and query and update those widgets as required. The GUI toolkit paints them on the display.

ICED is different. At each interaction, the application receives a “Message” and then updates it’s state as required and constructs the whole widget stack and returns it to ICED. The application needs to manage all the state it is interested in, such as, if a checkbox is checked or not. Some widgets, that have complex states, such as TextEditor, break this pattern and directly update a stateful “object” that the application needs to retain.

ICED is new

ICED is still very new and very much a work in progress. There is a large collection of widgets, which should be suitable for most purposes, but there are still things missing. The part I found most lacking at this time was the handling of input Focus. The UI can’t really be used from the keyboard alone as moving Focus about using the keyboard (or programatically) is not really possible yet.

Scaling

ICED has, like most GUI toolkits, an event loop that controls the interactions, this has two main entry points to your application, fn update(&mut self, message: Message) which passed messages in and fn view(&self) -> Element<Message> which is called to get the interface to display.

As the interface grows, the number of different message types needed to be handled grows accordingly and there is no obvious or straight foward way to decompose the logic, especially as multiple windows and/or views and panels are added.

As I said earlier, ICED is new, and multi-window support has recently been added, but how to neatly decompose your application to keep it maintainable is something to consider, before embarking on a large project with ICED.

Summary

I liked using ICED and found it easy to learn and predictable in use. If you are starting out with ICED; make good use of the examples and the community, who are both helpful and knowledgable.

1 Like

I don’t have much experience with native GUI toolkits and just recently learned the difference between retained and immediate modes. I was under the impression Iced was in the latter category as you draw the whole tree every time. Did I not get it right?

Have a look at this tutorial GUI in Rust with iced #1: Getting Started – Gherman Nicolisin – Software Dev and Blockchain enthusiast

In all honesty I believe the author gets the difference between the two modes right, but then fails to put Iced in the correct one. As the article states, a retained mode GUI toolkit will retain your GUI components and provide a reference for you to keep. That doesn’t happen in Iced: if I want my app to draw a button, I construct a new button on each invocation of view and return it along every other widget I want to render, which were also constructed from scratch.

As shown in PR #1811, Iced supports incremental rendering for tiny-skia backend, but currently not supported for wgpu backend. Incremental rendering for wgpu is pending state in the roadmap.

My understanding is that, Iced is not an immediate mode library, but it does not support incremental rendering, used usually in retained mode to reduce rendering cost.

When we say “immediate mode”, it usually implies that UI code is called every frame. I show an example of a immediate mode style using egui crate.

ui.heading("My Counter Application");
if ui.button("Increment").clicked() {
    count += 1;
}
ui.label(format!("{count}'"));

Action for button is in if-state and does not require mechanisms like callback or Iced’s Message. Code for UI rendering is called every frame, and the inner block of if ui.button("Increment").clicked() is called only in a frame that button is clicked. This is the style of immediate mode.

(In actual, egui rerenders UI only when there is something to change. The explanation above explains the typical coding style with immediate mode.)

Compared to that, Iced’s style is not an immediate mode. However, as I previously wrote, Iced does not support incremental rendering for wgpu backend. This means that, when something is changed (Message is passed and view() is called), it renders all UIs. This is not performant compared to retained mode which supports incremental rendering, but is more performant than immediate mode because immediate mode typically renders all UI every frame even when nothing is changed.

Note that Elm model does not determine that the library is retained or immediate. Elm language supports partial rendering using virtual DOM.

Yeah, after reading your comment a couple of times and re-reading some of the implications of a GUI being “immediate mode”, I think I agree with Iced not meeting all the requirements of it. But the same thing can be said for retained mode, though. For example, in Retained Mode Versus Immediate Mode it reads that, with a retained mode library,

To change what is rendered, the application issues a command to update the scene—for example, to add or remove a shape

This is the sort of API I usually see in retained mode toolkits: create a widget, add it to the view, keep a reference to it, and update the object whenever you want to alter how it’s drawn.

That’s not the API Iced provides. It looks much more like immediate mode, where you re-build the entire widget tree on each invocation of view. But yes, it doesn’t do it on every frame, and Iced does retain the scene and reconciles it with the new new tree that’s returned by view every time (to the best of my knowledge) so I agree that it is retained mode after all.

It is certain that Iced is both a retained-mode and a declarative UI framework. In a declarative UI framework, the runtime library commonly utilizes a virtual DOM and a Diff algorithm to determine the minimal area requiring redrawing, thus eliminating the need to redraw the entire interface each time.

Frameworks that also utilize this approach include Flutter, Vue, and others.

Regarding the differences between declarative UI and imperative UI, you can search Google for a more detailed introduction.

1 Like