Posted on • Originally published atwebbureaucrat.gitlab.io on
Iced for Desktop Development in Rust
As someone who loves Elm and has recently fallen in love with Rust, I was delighted to learn there are not just one buttwo Rust frameworks modeled on the Elm Architecture. If you're not familiar, the Elm Architecture is a pain-free way of developing fast, responsive, asynchronous-by-default front-ends. Let's see it in action by developing a simple "Hello, world" app.
Note: I'm usingiced
version0.10.0
. Iced is currently under rapid development with frequent breaking changes. It's possible these directions won't work as expected in a future release. As always, if something breaks,open an issue or submit a pull request.
Setup
Let's start with a fresh application:
cargo init hello-icedcdhello-icedcargo add icedcargo build
If the build succeeds, we're off to a good start.
Writing a State Model for Iced in Rust
The easiest part of the Elm Architecture to understand is the model state, so let's start there. The model state represents the whole state of the application--everything you would need to know in order to draw any given application screen. As you can imagine, state models are often quite large, but our application will need only one piece of information: whom to greet (by default: the world). That's easy.
structHello{addressee:String}
Writing a Message Enum for Iced in Rust
Next, we need our Message. A message is an enum that represents all the ways that our model canupdate according to the logic of our application. For example, you might have a counter that can increment or a counter that can either increment and decrement, or a counter that can increment, decrement, or reset, all depending on the logic of your application.
In our case, we only have one field in our modelstruct
, and it can only be updated through a text input, so let's write an enum to that effect:
#[derive(Clone,Debug)]enumHelloMessage{TextBoxChange(String)}
We can see that our enum takes aString
. This represents the new string to which we will update our model. Also note that it derivesClone
andDebug
. This is required by the framework.
Implementingiced::Application
Now the real work begins: turning our model into an IcedApplication
. Let's begin:
structHello{addressee:String,}#[derive(Clone,Debug)]enumHelloMessage{TextBoxChange(String)}impliced::ApplicationforHello{}
We need four types for our application.
Executor
- The only type provided by the framework isDefault
, which will suffice for our purposes.Flags
- represents any data we want to initialize our application. We have no flags to pass, so the unit type will suffice.Message
- this is theHelloMessage
we defined earlier.Theme
- the theme of the application.
structHello{addressee:String,}#[derive(Clone,Debug)]enumHelloMessage{TextBoxChange(String)}impliced::ApplicationforHello{typeExecutor=iced::executor::Default;typeFlags=();typeMessage=HelloMessage;typeTheme=iced::Theme;}
Next we need a function that initializes our model. Our model will default to greeting, well, the world.
Start by addinguse iced::Command;
at the top. A command is an asynchronous action that the framework will take on next. We don't need to start any commands on startup, so we'll initialize withCommand::none()
.
impliced::ApplicationforHello{typeExecutor=iced::executor::Default;typeFlags=();typeMessage=HelloMessage;typeTheme=iced::Theme;fnnew(_flags:())->(Hello,Command<Self::Message>){(Hello{addressee:String::from("world"),},Command::none())}
We also need a function which takes the&self
state model and returns a title for the title bar at the top of the application.
impliced::ApplicationforHello{typeExecutor=iced::executor::Default;typeFlags=();typeMessage=HelloMessage;typeTheme=iced::Theme;fnnew(_flags:())->(Hello,Command<Self::Message>){(Hello{addressee:String::from("world"),},Command::none())}fntitle(&self)->String{String::from("Greet the World")}}
Now we're getting to the real meat of the application: theupdate
andview
functions.
Theupdate
function takes our state model and mutates it based on the given message. It also gives us the opportunity to start anotherCommand
after mutating our state. For example, a common use case for a submit action message is to change part of the state to represent that the state is "Loading..." and then to start a command to fetch some data based on the submission. We only have one kind of message, so our update function will be very simple.
fnupdate(&mutself,message:Self::Message)->Command<Self::Message>{matchmessage{HelloMessage::TextBoxChange(string)=>{self.addressee=string;Command::none()}}}
Finally, we can write our view. Let's start byuse
ing some relevant parts.
useiced::{Command,Element,Length,Renderer,Settings};useiced::widget::{Column,Row,Text};
We build out our view programmatically using thepush
method, and then convert the final result to anElement
usinginto()
.
fnview(&self)->Element<Self::Message>{lettext=Text::new(format!("Hello, {0}.",self.addressee)).width(Length::Fill).horizontal_alignment(iced::alignment::Horizontal::Center);letrow1=Row::new().push(text);lettext_input:iced::widget::TextInput<'_,HelloMessage,Renderer>=iced::widget::text_input("world",self.addressee.as_str()).on_input(HelloMessage::TextBoxChange);letrow2=Row::new().push(text_input);Column::new().push(row1).push(row2).into()}
Writing amain
function torun
an IcedApplication
Finally, we need torun
our application from themain
function. Make sure touse
iced::Application
to bring therun
function into scope.
useiced::{Application,Command,Element,Length,Renderer,Settings};useiced::widget::{Column,Row,Text};pubfnmain()->iced::Result{Hello::run(Settings::default())}
Wrapping up
In conclusion, I hope this has been a helpful introduction to the Iced framework and the Elm Architecture. The architecture has a learning curve, so I figure the more learning resources out there, the better.
For reference, this is the full source code:
src/main.rs
useiced::{Application,Command,Element,Length,Renderer,Settings};useiced::widget::{Column,Row,Text};pubfnmain()->iced::Result{Hello::run(Settings::default())}structHello{addressee:String,}#[derive(Clone,Debug)]enumHelloMessage{TextBoxChange(String)}impliced::ApplicationforHello{typeExecutor=iced::executor::Default;typeFlags=();typeMessage=HelloMessage;typeTheme=iced::Theme;fnnew(_flags:())->(Hello,Command<Self::Message>){(Hello{addressee:String::from("world"),},Command::none())}fntitle(&self)->String{String::from("Greet the World")}fnupdate(&mutself,message:Self::Message)->Command<Self::Message>{matchmessage{HelloMessage::TextBoxChange(string)=>{self.addressee=string;Command::none()}}}fnview(&self)->Element<Self::Message>{lettext=Text::new(format!("Hello, {0}.",self.addressee)).width(Length::Fill).horizontal_alignment(iced::alignment::Horizontal::Center);letrow1=Row::new().push(text);lettext_input:iced::widget::TextInput<'_,HelloMessage,Renderer>=iced::widget::text_input("world",self.addressee.as_str()).on_input(HelloMessage::TextBoxChange);letrow2=Row::new().push(text_input);Column::new().push(row1).push(row2).into()}}
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse