Search code examples
user-interfacerustfltk

Global application state with FLTK library


I'm writing an application using the fltk library. The architecture of the application involves a global state that stores which page to display now (viewer) and store the viewer data. There are buttons that sends a message to update and changes the global state indicating which viewer to display now.

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

#[derive(Clone, Copy)]
struct GlobalState {
    viewer: Viewer,
}

impl GlobalState {
    fn set_viewer(&mut self, v: Viewer) {
        self.viewer = v;
    }
}

#[derive(Clone, Copy)]
enum Viewer {
    Page1,
    Page2
}

fn main() {
    let mut gs = GlobalState {viewer: Viewer::Page1};

    let app = app::App::default().with_scheme(app::Scheme::Gtk);
    let (s, r) = app::channel::<Message>();
    let mut wind = Window::default().with_size(800, 600);

    left_side(&mut gs);

    let mut col = Column::new(155,70,800 - 150,600 - 65,None);
    s.send(Message::Update);
    col.end();
    wind.end();
    wind.show();

    while app.wait() {
        if let Some(msg) = r.recv() {
            match msg {
                Message::Update => {
                    match gs.viewer {
                        Viewer::Page1 => {
                            col.clear();
                            let view = view_ohm();
                            col.add(&view);
                        },
                        Viewer::Page2 => {
                            col.clear();
                            let view = view_ohm();
                            col.add(&view);
                        },
                        _ => ()
                    }
                }
                _ => (),
            }
        }
    }
}

fn left_side(gs: &mut GlobalState) {
    let btn_width = 130;
    let btn_height = 30;

    let (s, r) = app::channel::<Message>();

    let mut grp = Group::default().with_size(65, 600);
    let mut col = Pack::default()
        .with_size(btn_width, 600)
        .center_of_parent()
        .with_type(PackType::Vertical);
    col.set_spacing(2);

    let mut btn = Button::new(0, 0, btn_width, btn_height, "Page 1");
    btn.emit(s, Message::Update);
    btn.set_callback(|_| {
        gs.set_viewer(Viewer::Page1)
    });

    let mut btn = Button::new(0, 0, btn_width, btn_height, "Page 2");
    btn.emit(s, Message::Update);
    btn.set_callback(|_| {
        gs.set_viewer(Viewer::Page2)
    });

    col.end();
    grp.end();
}

Questions:

  1. the code doesn't compile with error:
    error[E0521]: borrowed data escapes outside of function
      --> src/main.rs:89:5
       |
    74 |   fn left_side(gs: &mut GlobalState) {
       |                --  - let's call the lifetime of this reference `'1`
       |                |
       |                `gs` is a reference that is only valid in the function body
    ...
    89 | /     btn.set_callback(|_| {
    90 | |         gs.set_viewer(Viewer::Page1)
    91 | |     });
       | |      ^
       | |      |
       | |______`gs` escapes the function body here
       |        argument requires that `'1` must outlive `'static`
    
    error[E0524]: two closures require unique access to `*gs` at the same time
      --> src/main.rs:95:22
       |
    89 |       btn.set_callback(|_| {
       |       -                --- first closure is constructed here
       |  _____|
       | |
    90 | |         gs.set_viewer(Viewer::Page1)
       | |         -- first borrow occurs due to use of `*gs` in closure
    91 | |     });
       | |______- argument requires that `*gs` is borrowed for `'static`
    ...
    95 |       btn.set_callback(|_| {
       |                        ^^^ second closure is constructed here
    96 |           gs.set_viewer(Viewer::Page2)
       |           -- second borrow occurs due to use of `*gs` in closure
  1. my application architecture working or is there a better one? The application has several pages (one page one viewer) and each viewer has its own global state and the data that is stored in it and processes this data.

  2. What is the best way to store viewer data in the global state?


Solution

  • This code wraps your GlobalState in an Rc<RefCell<GlobalState>>:

    #[derive(Clone, Copy)]
    enum Message {
        Update
    }
    
    #[derive(Clone, Copy)]
    struct GlobalState {
        viewer: Viewer,
    }
    
    impl GlobalState {
        fn set_viewer(&mut self, v: Viewer) {
            self.viewer = v;
        }
    }
    
    #[derive(Clone, Copy)]
    enum Viewer {
        Page1,
        Page2
    }
    
    fn main() {
        let mut gs = Rc::new(RefCell::new(GlobalState {viewer: Viewer::Page1}));
    
        let app = app::App::default().with_scheme(app::Scheme::Gtk);
        let (s, r) = app::channel::<Message>();
        let mut wind = Window::default().with_size(800, 600);
    
        left_side(gs.clone());
    
        let mut col = Column::new(155,70,800 - 150,600 - 65,None);
        col.end();
        wind.end();
        wind.show();
    
        while app.wait() {
            if let Some(msg) = r.recv() {
                match msg {
                    Message::Update => {
                        let gs = gs.borrow();
                        match gs.viewer {
                            Viewer::Page1 => {
                                col.clear();
                                let view = view_ohm();
                                col.add(&view);
                            },
                            Viewer::Page2 => {
                                col.clear();
                                let view = view_ohm();
                                col.add(&view);
                            },
                            _ => ()
                        }
                    }
                    _ => (),
                }
            }
        }
    }
    
    fn left_side(gs: Rc<RefCell<GlobalState>>) {
        let btn_width = 130;
        let btn_height = 30;
    
        let s = app::Sender::get();
    
        let mut grp = Group::default().with_size(65, 600);
        let mut col = Pack::default()
            .with_size(btn_width, 600)
            .center_of_parent()
            .with_type(PackType::Vertical);
        col.set_spacing(2);
    
        let mut btn = Button::new(0, 0, btn_width, btn_height, "Page 1");
    
        btn.set_callback({
            let gs = gs.clone();
            move |_| {
            gs.borrow_mut().set_viewer(Viewer::Page1);
            s.send(Message::Update);
        }});
    
        let mut btn = Button::new(0, 0, btn_width, btn_height, "Page 2");
    
        btn.set_callback(move |_| {
            gs.borrow_mut().set_viewer(Viewer::Page2);
            s.send(Message::Update);
        });
    
        col.end();
        grp.end();
    }