A minimal Application - Counter
Our goal is to create a simple counter where we have a number displayed that we can increase or decrease with two buttons.
Creating a new Project
First of all, make sure Rust is installed in your system. If not head over to Rust Installation Page.
After installing rust, create a new binary crate by executing,
$ cargo new counter-app-iced
$ cd counter-app-iced
Add Iced crate by executing,
$ cargo add iced
Now, build the app using
$ cargo run
On successful build, you can see the text Hello World
is printed on console. Now we are ready to create our beautiful GUIs using Iced.
1. Defining the State
For the state, we define a struct. For the counter, we need to store the current value of the counter.
use iced::widget;
struct Counter {
// This will be our state of the counter app
// a.k.a the current count value
count: i32,
}
#[derive(Debug, Clone, Copy)]
enum Message {
// Emitted when the increment ("+") button is pressed
IncrementCount,
// Emitted when decrement ("-") button is pressed
DecrementCount,
}
// Implement our Counter
impl Counter {
fn new() -> Self {
// initialize the counter struct
// with count value as 0.
Self { count: 0 }
}
fn update(&mut self, message: Message) -> iced::Task<Message> {
// handle emitted messages
match message {
Message::IncrementCount => self.count += 1,
Message::DecrementCount => self.count -= 1,
}
iced::Task::none()
}
fn view(&self) -> iced::Element<'_, Message> {
// create the View Logic (UI)
let row = widget::row![
widget::button("-").on_press(Message::DecrementCount),
widget::text(self.count),
widget::button("+").on_press(Message::IncrementCount)
];
widget::container(row)
.center_x(iced::widget::Fill)
.center_y(iced::widget::Fill)
.width(iced::Length::Fill)
.height(iced::Length::Fill)
.into()
}
}
fn main() -> Result<(), iced::Error> {
// run the app from main function
iced::application("Counter Example", Counter::update, Counter::view).run_with(|| (Counter::new(), iced::Task::none()))
}
2. Defining the Message
For our counter, we have two major events that matter to us. Increasing and decreasing the counter.
The message is represented as an enum with two variants, IncrementCounter
and DecrementCounter
.
use iced::widget;
struct Counter {
// This will be our state of the counter app
// a.k.a the current count value
count: i32,
}
#[derive(Debug, Clone, Copy)]
enum Message {
// Emitted when the increment ("+") button is pressed
IncrementCount,
// Emitted when decrement ("-") button is pressed
DecrementCount,
}
// Implement our Counter
impl Counter {
fn new() -> Self {
// initialize the counter struct
// with count value as 0.
Self { count: 0 }
}
fn update(&mut self, message: Message) -> iced::Task<Message> {
// handle emitted messages
match message {
Message::IncrementCount => self.count += 1,
Message::DecrementCount => self.count -= 1,
}
iced::Task::none()
}
fn view(&self) -> iced::Element<'_, Message> {
// create the View Logic (UI)
let row = widget::row![
widget::button("-").on_press(Message::DecrementCount),
widget::text(self.count),
widget::button("+").on_press(Message::IncrementCount)
];
widget::container(row)
.center_x(iced::widget::Fill)
.center_y(iced::widget::Fill)
.width(iced::Length::Fill)
.height(iced::Length::Fill)
.into()
}
}
fn main() -> Result<(), iced::Error> {
// run the app from main function
iced::application("Counter Example", Counter::update, Counter::view).run_with(|| (Counter::new(), iced::Task::none()))
}
3. Implementing the Counter
To create a window, we implement the update
function for our Counter
.
Initial state
Next, we need to set out initial value of our state, which is the count
value. The new
function helps us to do exactly that. The state of the Counter
is returned with it's initial count
value as 0
.
use iced::widget;
struct Counter {
// This will be our state of the counter app
// a.k.a the current count value
count: i32,
}
#[derive(Debug, Clone, Copy)]
enum Message {
// Emitted when the increment ("+") button is pressed
IncrementCount,
// Emitted when decrement ("-") button is pressed
DecrementCount,
}
// Implement our Counter
impl Counter {
fn new() -> Self {
// initialize the counter struct
// with count value as 0.
Self { count: 0 }
}
fn update(&mut self, message: Message) -> iced::Task<Message> {
// handle emitted messages
match message {
Message::IncrementCount => self.count += 1,
Message::DecrementCount => self.count -= 1,
}
iced::Task::none()
}
fn view(&self) -> iced::Element<'_, Message> {
// create the View Logic (UI)
let row = widget::row![
widget::button("-").on_press(Message::DecrementCount),
widget::text(self.count),
widget::button("+").on_press(Message::IncrementCount)
];
widget::container(row)
.center_x(iced::widget::Fill)
.center_y(iced::widget::Fill)
.width(iced::Length::Fill)
.height(iced::Length::Fill)
.into()
}
}
fn main() -> Result<(), iced::Error> {
// run the app from main function
iced::application("Counter Example", Counter::update, Counter::view).run_with(|| (Counter::new(), iced::Task::none()))
}
Update logic
Now we have to handle the messages that are emitted by the View Logic. The update
function does exactly that. The update
function get called every time when the View Logic emits a message. We use the rust's powerful match
expression to handle messages. Here we use the match
expression to increase the count when IncrementCount
is emitted and decrease the count when DecrementCount
is emitted.
use iced::widget;
struct Counter {
// This will be our state of the counter app
// a.k.a the current count value
count: i32,
}
#[derive(Debug, Clone, Copy)]
enum Message {
// Emitted when the increment ("+") button is pressed
IncrementCount,
// Emitted when decrement ("-") button is pressed
DecrementCount,
}
// Implement our Counter
impl Counter {
fn new() -> Self {
// initialize the counter struct
// with count value as 0.
Self { count: 0 }
}
fn update(&mut self, message: Message) -> iced::Task<Message> {
// handle emitted messages
match message {
Message::IncrementCount => self.count += 1,
Message::DecrementCount => self.count -= 1,
}
iced::Task::none()
}
fn view(&self) -> iced::Element<'_, Message> {
// create the View Logic (UI)
let row = widget::row![
widget::button("-").on_press(Message::DecrementCount),
widget::text(self.count),
widget::button("+").on_press(Message::IncrementCount)
];
widget::container(row)
.center_x(iced::widget::Fill)
.center_y(iced::widget::Fill)
.width(iced::Length::Fill)
.height(iced::Length::Fill)
.into()
}
}
fn main() -> Result<(), iced::Error> {
// run the app from main function
iced::application("Counter Example", Counter::update, Counter::view).run_with(|| (Counter::new(), iced::Task::none()))
}
Tip: Use
count.saturating_add(1)
orcount.saturating_sub(1)
for more error proof and optimized code.
View logic
The only thing left is to define our View (a.k.a UI). Define your View Logic in the view
function. In iced, all UI components are called widgets. For a counter, we need two button
widgets (one for incrementing and another for decrementing) and a text
widget. They need to be aligned one after another in a horizontal manner. So, we use row
widget macro to align our widgets in a horizontal manner.
use iced::widget;
struct Counter {
// This will be our state of the counter app
// a.k.a the current count value
count: i32,
}
#[derive(Debug, Clone, Copy)]
enum Message {
// Emitted when the increment ("+") button is pressed
IncrementCount,
// Emitted when decrement ("-") button is pressed
DecrementCount,
}
// Implement our Counter
impl Counter {
fn new() -> Self {
// initialize the counter struct
// with count value as 0.
Self { count: 0 }
}
fn update(&mut self, message: Message) -> iced::Task<Message> {
// handle emitted messages
match message {
Message::IncrementCount => self.count += 1,
Message::DecrementCount => self.count -= 1,
}
iced::Task::none()
}
fn view(&self) -> iced::Element<'_, Message> {
// create the View Logic (UI)
let row = widget::row![
widget::button("-").on_press(Message::DecrementCount),
widget::text(self.count),
widget::button("+").on_press(Message::IncrementCount)
];
widget::container(row)
.center_x(iced::widget::Fill)
.center_y(iced::widget::Fill)
.width(iced::Length::Fill)
.height(iced::Length::Fill)
.into()
}
}
fn main() -> Result<(), iced::Error> {
// run the app from main function
iced::application("Counter Example", Counter::update, Counter::view).run_with(|| (Counter::new(), iced::Task::none()))
}
In the above code, we can see that the button
's on_press
function accepts the message type to be emitted.
Note: By default, the
view()
function returns the typeElement<'_, Message>
. So, we use.into()
for conversion purpose.
That's pretty much everything for a simple counter app. Now, let's run it.
4. Running the Counter
To run the counter we first create a application with the application
function.
With that function we define our Window Title and the update and view functions.
This illicitly defines out state type and the message type.
Then we run the counter with an initial state and a start task that does nothing.
use iced::widget;
struct Counter {
// This will be our state of the counter app
// a.k.a the current count value
count: i32,
}
#[derive(Debug, Clone, Copy)]
enum Message {
// Emitted when the increment ("+") button is pressed
IncrementCount,
// Emitted when decrement ("-") button is pressed
DecrementCount,
}
// Implement our Counter
impl Counter {
fn new() -> Self {
// initialize the counter struct
// with count value as 0.
Self { count: 0 }
}
fn update(&mut self, message: Message) -> iced::Task<Message> {
// handle emitted messages
match message {
Message::IncrementCount => self.count += 1,
Message::DecrementCount => self.count -= 1,
}
iced::Task::none()
}
fn view(&self) -> iced::Element<'_, Message> {
// create the View Logic (UI)
let row = widget::row![
widget::button("-").on_press(Message::DecrementCount),
widget::text(self.count),
widget::button("+").on_press(Message::IncrementCount)
];
widget::container(row)
.center_x(iced::widget::Fill)
.center_y(iced::widget::Fill)
.width(iced::Length::Fill)
.height(iced::Length::Fill)
.into()
}
}
fn main() -> Result<(), iced::Error> {
// run the app from main function
iced::application("Counter Example", Counter::update, Counter::view).run_with(|| (Counter::new(), iced::Task::none()))
}
Note: The main function should have a return type of
Result<(), iced::Error>
.
5. Full Code
Now that we completed our simple counter application, the complete code will look like this.
use iced::widget;
struct Counter {
// This will be our state of the counter app
// a.k.a the current count value
count: i32,
}
#[derive(Debug, Clone, Copy)]
enum Message {
// Emitted when the increment ("+") button is pressed
IncrementCount,
// Emitted when decrement ("-") button is pressed
DecrementCount,
}
// Implement our Counter
impl Counter {
fn new() -> Self {
// initialize the counter struct
// with count value as 0.
Self { count: 0 }
}
fn update(&mut self, message: Message) -> iced::Task<Message> {
// handle emitted messages
match message {
Message::IncrementCount => self.count += 1,
Message::DecrementCount => self.count -= 1,
}
iced::Task::none()
}
fn view(&self) -> iced::Element<'_, Message> {
// create the View Logic (UI)
let row = widget::row![
widget::button("-").on_press(Message::DecrementCount),
widget::text(self.count),
widget::button("+").on_press(Message::IncrementCount)
];
widget::container(row)
.center_x(iced::widget::Fill)
.center_y(iced::widget::Fill)
.width(iced::Length::Fill)
.height(iced::Length::Fill)
.into()
}
}
fn main() -> Result<(), iced::Error> {
// run the app from main function
iced::application("Counter Example", Counter::update, Counter::view).run_with(|| (Counter::new(), iced::Task::none()))
}
In the next section, you will see about common layouting techniques that iced offers.