lvgl / lv_binding_rust

LVGL bindings for Rust. A powerful and easy-to-use embedded GUI with many widgets, advanced visual effects (opacity, antialiasing, animations) and low memory requirements (16K RAM, 64K Flash).
MIT License
687 stars 71 forks source link

updating display from an independent context #149

Open fulup-bzh opened 1 year ago

fulup-bzh commented 1 year ago

I'm trying to use lvgm-rs with an asynchronous framework and I miserably fail to find the good design pattern.

To expose something simple I restarted from demo/arc example. I try to split arc.rs with 3 functions.

Note: doing so, we loose default RUST live cycle and everything created on the heap by 1st function disappear before calling second function.

The 1st issue to solve seems tp comes from screen.add_style(Part::Main, &mut screen_style); because of this screen depend on screen_style which is a Box that will be deleted at function return. But this might only be the visible part of the iceberg.

Any idea on how to implement a pattern to update the display from an asynchronous event would be more than welcome. Alternatively of you have some sample that could help, please let me known.

If we find a working solution, I commit to push an example on github, as I cannot be the only one with this need.

Handle + Main function

struct DisplayHandle<'a> {
    pub sim_display: SimulatorDisplay<Rgb565>,
    pub window: RefCell<Window>,
    pub screen: Screen<'a>,
    pub arc: RefCell<Arc<'a>>,
}

fn main() -> Result<(), LvError> {

    let handle= mk_display() ?;

    // handle.update should be callable from an asynchronous function (i.e. a system timer)
    display_update(handle)?;

    Ok(())
}

Function creating the display layout return a static/leak box to make sure the handle is never deleted

fn mk_display() -> Result< &'static DisplayHandle<'static>, LvError> {
    const HOR_RES: u32 = 240;
    const VER_RES: u32 = 240;

    println!("meminfo init: {:?}", mem_info());
    let mut sim_display: SimulatorDisplay<Rgb565> =
        SimulatorDisplay::new(Size::new(HOR_RES, VER_RES));

    let output_settings = OutputSettingsBuilder::new().scale(1).build();
    let window = Window::new("Arc Example", &output_settings);

    let buffer = DrawBuffer::<{ (HOR_RES * VER_RES) as usize }>::default();

    let display = Display::register(buffer, HOR_RES, VER_RES, |refresh| {
        sim_display.draw_iter(refresh.as_pixels()).unwrap();
    })?;

    let mut screen = display.get_scr_act()?;

    let mut screen_style = Style::default();
    screen_style.set_bg_color(Color::from_rgb((255, 255, 255)));
    screen_style.set_radius(0);
    screen.add_style(Part::Main, &mut screen_style);

    // Create the arc object
    let mut arc = Arc::create(&mut screen)?;
    arc.set_size(150, 150);
    arc.set_align(Align::Center, 0, 10);
    arc.set_start_angle(135)?;
    arc.set_end_angle(135)?;

    let mut loading_lbl = Label::create(&mut screen)?;
    loading_lbl.set_text(CString::new("Loading...").unwrap().as_c_str())?;
    loading_lbl.set_align(Align::OutTopMid, 0, 0);
    //loading_lbl.set_label_align(LabelAlign::Center)?;

    let mut loading_style = Style::default();
    loading_style.set_text_color(Color::from_rgb((0, 0, 0)));
    loading_lbl.add_style(Part::Main, &mut loading_style);

    let handle = Box::new(DisplayHandle {
        screen,
        sim_display,
        window: RefCell::new(window),
        arc: RefCell::new(arc),
    });
   // make sure the handle is never going to be deleted
    Ok(Box::leak(handle))
}

For my test, the update function is called directly from main routine, my end goal is call it from an asynchronous timer event, that potentially run in a different thread.

fn display_update (handle: &DisplayHandle) -> Result <(), LvError> {
    let mut angle = 0;
    let mut forward = true;
    let mut i = 0;

    // retrieve mutable arc handle from display handle
    let mut arc= handle.arc.borrow_mut();
    let mut window= handle.window.borrow_mut();

    'running: loop {
        let start = Instant::now();
        if i > 270 {
            forward = if forward { false } else { true };
            i = 1;
            println!("mem info running: {:?}", mem_info());
        }
        angle = if forward { angle + 1 } else { angle - 1 };

        arc.set_end_angle(angle + 135)?;
        i += 1;

        lvgl::task_handler();
        window.update(&handle.sim_display);

        // for event in window.events() {
        //     match event {
        //         SimulatorEvent::Quit => break 'running,
        //         _ => {}
        //     }
        // }
        sleep(Duration::from_millis(15));
        lvgl::tick_inc(Instant::now().duration_since(start));
    }
}
fulup-bzh commented 1 year ago

To help in reproducing my problem, I pushed my code on https://github.com/fulup-bzh/lvgl-async-rs