Iced + impl Program for MyApp + canvas::Program: Unable to setup app program

In my Iced programs, normally I have 1 state variable "State" which contains the data. I then use "impl State" to assign logic functions such as update and view. I want to attempt to plot some numbers on a canvas so I crated a simple Iced program that created a canvas and allowed the radius of the circle to changed. It worked. Now I am trying to modify this program so that the canvas has 1 structure containing its data and the app has 1 structure containing its data. In addition, I created 2 additional empty structures to hold the logic for the canvas and app. I attempt to pull this all off by creating a program with "impl Program for MyApp" but have hit a wall. There are a couple of issues that I have notated in the code. If someone could help point me in the right direction it will be greatly appreciated.

use iced::alignment::Horizontal;
use iced::widget::{button, canvas, column, text};  
use iced::{Color, Element, Length, Rectangle, Renderer, Size, Task, Theme, mouse};  
use iced::widget::row;

use iced::Program;

#[derive(Debug, Clone)]  
enum ChangeSize {
    IncSize,
    DecSize,
}

#[derive(Debug, Clone)]  
enum CanvasMessage {  
    IncreaseRadius,  
    DecreaseRadius,  
}  

enum Screen {  
    Main,  
    Canvas(CanvasState),  
}  
#[derive(Debug, Clone)]  
enum Message {  
    ShowCanvas,  
    BackToMain,  
    Canvas(ChangeSize),   
}  
struct AppState {
    screen: Screen,
    canvas_state: CanvasState,
}
struct MyApp {}

#[derive(Default)]
struct CanvasState {
    circle_radius: f32,
}

struct MyCanvas {}

impl<Message> canvas::Program<Message> for MyCanvas {  

    type State = CanvasState;  
  
    fn draw(  
        &self,  
        state: &CanvasState,  
        renderer: &Renderer,  
        _theme: &Theme,  
        bounds: Rectangle,  
        _cursor: mouse::Cursor  
    ) -> Vec<canvas::Geometry> {  

        let mut frame = canvas::Frame::new(renderer, bounds.size());  
        let circle = canvas::Path::circle(frame.center(), state.circle_radius);  
        frame.fill(&circle, Color::BLACK);  
        vec![frame.into_geometry()]  
    }  
}  

impl Program for MyApp {

    type State = AppState;
    type Message = Message;
    type Theme = Theme;
    type Renderer = Renderer;
    type Executor = Default;  
// determine default executor

    fn window(&self) { 
// determine how to indicate default
    }

    fn settings(&self) -> iced::Settings {
        iced::Settings::default()
    }

    fn name() -> &'static str {
        let program_name = "Canvas";
        &program_name
    }

    fn boot(&self) -> (Self::State, iced::Task<Self::Message>) {
        let initial_state = Self::State {
            screen: Screen::Main,
            canvas_state: CanvasState { circle_radius: 25.0 }
        };

        (initial_state, Task::none())
    }

    fn update(&self, state: &mut Self::State , message: Message) -> Task<Message> {    
        match message {  
            Message::ShowCanvas => {  
                state.screen = Screen::Canvas(CanvasState { circle_radius: state.canvas_state.circle_radius });
                Task::none()  
            }  
            Message::BackToMain => {  
                state.screen = Screen::Main;  
                Task::none()  
            }
            Message::Canvas(msg) => {
                match msg {
                    ChangeSize::DecSize => {
                        state.canvas_state.circle_radius -= 10.0;         
                    }
                    ChangeSize::IncSize => {
                        state.canvas_state.circle_radius += 10.0;                     
                    }
                }
                state.screen = Screen::Canvas(CanvasState { circle_radius: state.canvas_state.circle_radius });
                Task::none()  
            }
        }  
    }  

    fn view(&self, state: &'a Self::State, app_id: iced::window::Id) -> Element<'_, Message> {
// 'a appears to be a problem
        match &state.screen {  
            Screen::Main => {  
                column![  
                    text("Main Screen"),  
                    button("Show Canvas").on_press(Message::ShowCanvas)  
                ]  
                .into()  
            }  
            Screen::Canvas(canvas_program) => {  
                column![  
                    button("Back").on_press(Message::BackToMain),  

                    canvas(canvas_program.clone()) 
// the trait iced::widget::canvas::Program not implemented for CanvasState
                        .width(Length::Fill)  
                        .height(Length::Fill), 

                    row![
                        button("Decrease").on_press(Message::Canvas(ChangeSize::DecSize)),  
                        button("Increase").on_press(Message::Canvas(ChangeSize::IncSize)),  
                    ]
                    .spacing(100)
                ]  
                .align_x(Horizontal::Center)
                .spacing(100)
                .width(Length::Fill)
                .into()  
            }  
        }  
    }

}

fn main() -> iced::Result {  


}

Without seeing the full error messages, I can't be sure of what's going on, but I am slightly suspicious of this line. If you try to clone &T where T: !Clone, the method-call syntax sugar would apply an "autoref". In this case, I think that canvas_program.clone() refers to <&CanvasProgram as Clone>::clone(&canvas_program)... which clones the &CanvasProgram reference, not the CanvasProgram.

Looks like you didn't derive Clone for CanvasState? (I'd recommend derive(Debug, Default, Clone, Copy) on CanvasState. There are clippy lints for missing Debug and Copy implementations - generally, almost every type should be Debug, and most types which could be Copy should be Copy.)

Think of lifetime names (other than '_ or 'static) like 'a as variables. You need to introduce/declare a variable before you use it. Lifetime parameter names are declared as generic parameters, like fn view<'a>(..) (i.e., it would be part of the function signature)... but traits define the exact function signatures of their methods. You should copy the exact signature of the trait method, including lifetimes. Looking at iced v0.14.0's docs, I think it's something like the following:

    fn view<'a>(
        &self,
        state: &'a Self::State,
        window: Id,
    ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer>;

Warning: I have not used iced before, so I cannot give big-picture help.

you are supposed to pass an argument of type impl canvas::Program, not the associated canvas::Program::State. I didn't read all your code, but it seems the type MyCanvas fits here, try:

canvas(MyCanvas{})
    .width(Length::Fill)
    .height(Length::Fill)

as for the lifetime:

just copy the exact signature of the method in the trait definition, your signature is wrong.

Thank you for the responses. I think I have it worked out. Now for one follow up question.
What line of code do I place in the main function to run the program. In previous programs, applications, I used iced::application .....
Does not seem to work here. Also tried MyApp::run() and that does not work. Any help will be appreciated.

are you studying the inner working of iced?

typically an application does not need to implement the Program directly, the Application wrapper is what most applications should use.

the Program trait is intended for advanced users, e.g., in case you want to write a port (sometimes referred to as "backend" or "windowing system") to a new platform.

the default backend of iced is iced_winit. specifically, you use iced_winit::run() to run the your own Program:

After additional reading and your input, I have realized that using Program was an error on my part. I just did not understand that it is not meant for this type of application. It is now clear to me that I should rewrite the code as an Application. Thank you all for the input.

just to clarify a bit, when I said application typically don't implement Program directly previouly, I was referring to the iced::Program trait only, which serves as the abstraction layer between the application logic and the windowing platform.

the Programs (yes, plural) under the widget namespace have different purposes: they provide the mechanism to add application specific logic for (semi-)customizable widgets. current version of iced has two such widgets, both of which must be drawn by user code: the Canvas widget and the Shader widget.


btw, I just read your code in OP, I think you probably misunderstood the use case of the associated type canvas::Program::State. these are widget states, not application states.

what's the difference? normally, widget states are private to the widget implementation and are not exposed in user-facing API. typical examples of widgets states include keyboard input focus, animation timer, text selection range, mouse hovering or dragging information, things along those lines. for example, the state for the button widget is just a boolean flag indicating whether the button is pressed, while the state for the text widget is a little more complex.

but the canvas widget is an exception because it is customizable. same for the other customizable one, the shader widget.

also note, since they are widget states, each instance of the same widget has its own copy of states. this helps understand whether a piece of information should be a widget state or app state.

for example, suppose you want to have two independent canvas to visualize the same model data, just in slightly different manner, like, with different scales, different angles, or different colors, etc. in this case, the model is shared by all views, so it should be part of the app states, while the viewing parameters should be part of the widget states.

you can learn how to use the canvas widget in the bezier example.

now back or your original code, if you put the radius information into the widget state, then you can only modify the value in the cavas::Program::update() method, for example in response to keyboard or mouse events, but the information is not directly accessible to the application logic.[1].


  1. I say not directly, you can always use some hacky workarounds, such as Rc<RefCell<Radius>>, but my point is, logically widget states are private to the widget, inaccessible to the app which is a user of the widget ↩︎

To better understand using the application trait I created a simple program based on two on line examples. I cannot get it to work. The indicated problem is that it sees Application as a structure and not a trait. I have not idea how to resolve this. Rust is very challenging for me.
Could this be an issue with my .toml file?
iced = { version = "0.14.0", features = ["advanced", "canvas"]}

use iced::{Application, Element, Settings, Theme, executor};
use iced::widget::Text;
use std::process::Command;
// example indicates use iced::Command but Command not found

pub fn main() -> iced::Result {
    Hello::run(Settings::default())
}

#[derive(Debug, Clone, Copy)]
pub enum Message {}

struct Hello {}

impl Application for Hello {
    // expected trait found structure

    type Executor = executor::Default;
    type Message = Message;
    type Theme = Theme;
    type Flags = ();

    fn new(_flags: Self::Flags) -> (Self, Command<Self::Message>) {
        (Hello {}, Command::none())
    } 

    fn title(&self) -> String {
        String::from("a cool app")
    }

    fn update(&mut self, _message: Self::Message) -> Command<Self::Messsage> {
        Command::none()
    }

    fn view(&mut self) -> Element<Self::Message> {
        Text::new("Hello World").into()
    }

    fn theme(&self) -> Theme {
        Theme::Moonfly
    }

}

it's true that Application is a struct, not a trait.

and as the documentation of Application says, you use the iced::application() function to construct an Application.

yes it is. anything can be challenging, especially for people new to it. and different people may find it challenging in different ways, depending on their background.

but the feeling might come from iced's unconventional architecture for GUI, not rust the language per se. there are plenty of different GUI libraries for rust, including many traditional retained-mode GUIs too. https://areweguiyet.com/

with iced, typical applications don't need to implement special traits to integrate with the "framework" (implementing traits is rust's analogue of "inheriting" classes in many OO language and frameworks).

instead, you just model you application states as regular rust data types, and implement the ui rendering (a.k.a. view()) and event handling (a.k.a. update()) as regular rust functions. there's an optional boot() function too, if you need to perform some async operations as part of the initialization.

under the hood, there is a private Program wrapper to connect the dots for you, and for advanced use cases, it is possible to use the low level interface directly, but it is not required for common use cases.

check out the examples in the iced repositories to start:

if you are new to this approach of doing GUI, I strongly recommend you start with the book, make sure to understand the minimal example first:

although it's not completed yet, it should be a good starting point to get yourself familiar with the basic ideas and concepts, this is especially useful for people who never heard of elm or frp[1].

but the elm architecture might no suit every application and every user. if you don't like iced, you can try different ones.

  • if you have worked in graphics or games, try immediate mode GUI, egui being the most popular one in the rust ecosystem;
  • if you like functional programming, try the elm/frp architecture like iced or relm4;
  • if you are experienced in conventional retained mode GUI toolkits, you can check out slint, or bindings to existing foreign library like gtk, qt, fltk, etc;
  • if you come from the web world, there are also html based GUI frameworks like tauri and dioxus

point is, don't get frustrated if you can't get iced to work. learning new things always takes time and efforts. start with core concepts, take your steps, steady and slow, you'll eventually get there.


  1. elm originated from frp but has drifted away from classic frp long ago. a farewell to frp ↩︎

Thank you for all of the input, I would still be working on hello world without it. I finally figured out that in the version of iced I use (0.14) they do not want you to use traits like I was trying to do with Program and Application. As indicated above, they want you to assemble the Application thru a helper function "application" in which you indicate a boot, update and view functions. I think this method will make managing state between a canvas and the application easier for me to navigate when I incorporate a canvas. Below is my first pass at an application, doing it I think the correct way. Onward and upward!

use iced::alignment::Horizontal;
use iced::widget::{button, column, text};
use iced::Theme;
use iced::{Size, window};
use iced::widget;
use iced::Length;
pub fn main() -> iced::Result {
    iced::application(boot, update, view)
        .theme(Theme::Light)
        .centered()
        .window(window::Settings {
            size: Size {
                width: 800.0,
                height: 800.0,
            },
            ..window::Settings::default()
        })
        .run()
}
#[derive(Debug, Clone)]
enum Message {
    Increment,
}
struct AppState {
    num: u64
}
// in boot fn can also do -> (AppState, Task<Message>)
fn boot() -> AppState {
    let initial_values = AppState {
        num: 0,
    };
    initial_values
}
fn update(state: &mut AppState, message: Message) {
    match message {
        Message::Increment => state.num += 1,
    }
}
fn view(state: &AppState) -> iced::Element<'_, Message> {    
    let mycol = column![
        text(state.num),
        button("+").on_press(Message::Increment),
    ];
    widget::container(mycol)
    .padding(iced::padding::top(100))
    .width(Length::Fill)
    .align_x(Horizontal::Center)
    .into()
}