woab 0.9.0

Widgets on Actors Bridge - a GUI microframework for combining GTK with Actix
use actix::prelude::*;
use gtk4::prelude::*;

#[derive(woab::Factories)]
pub struct Factories {
    win_app: woab::BuilderFactory,
    row_addend: woab::BuilderFactory,
}

#[derive(woab::WidgetsFromBuilder)]
pub struct WindowWidgets {
    win_app: gtk4::ApplicationWindow,
    buf_sum: gtk4::TextBuffer,
    #[allow(unused)]
    lst_addition: gtk4::ListBox,
}

struct WindowActor {
    factories: std::rc::Rc<Factories>,
    widgets: WindowWidgets,
    addends: Vec<actix::Addr<AddendActor>>,
}

impl actix::Actor for WindowActor {
    type Context = actix::Context<Self>;

    fn started(&mut self, ctx: &mut Self::Context) {
        self.widgets.win_app.show();
        ctx.address().do_send(Recalculate);
    }
}

impl actix::Handler<woab::Signal> for WindowActor {
    type Result = woab::SignalResult;

    fn handle(&mut self, msg: woab::Signal, ctx: &mut Self::Context) -> Self::Result {
        Ok(match msg.name() {
            "click_button" => {
                AddendActor::create(|addend_ctx| {
                    let bld = self.factories.row_addend.instantiate_route_to(addend_ctx.address());
                    self.addends.push(addend_ctx.address());
                    let widgets: AddendWidgets = bld.widgets().unwrap();
                    self.widgets.lst_addition.append(&widgets.row_addend);
                    AddendActor {
                        widgets,
                        window: ctx.address(),
                        number: Some(0),
                    }
                });
                ctx.address().do_send(Recalculate);
                None
            }
            _ => msg.cant_handle()?,
        })
    }
}

struct CheckForRemovedAddends;

impl actix::Message for CheckForRemovedAddends {
    type Result = ();
}

impl actix::Handler<CheckForRemovedAddends> for WindowActor {
    type Result = ();

    fn handle(&mut self, _msg: CheckForRemovedAddends, ctx: &mut Self::Context) -> Self::Result {
        self.addends.retain(|a| a.connected());
        ctx.address().do_send(Recalculate);
    }
}

#[derive(woab::Removable)]
#[removable(self.widgets.row_addend in gtk4::ListBox)]
struct AddendActor {
    #[allow(unused)]
    widgets: AddendWidgets,
    #[allow(unused)]
    window: actix::Addr<WindowActor>,
    number: Option<isize>,
}

impl actix::Actor for AddendActor {
    type Context = actix::Context<Self>;
}

#[derive(woab::WidgetsFromBuilder)]
struct AddendWidgets {
    #[allow(unused)]
    row_addend: gtk4::ListBoxRow,
}

impl actix::Handler<woab::Signal> for AddendActor {
    type Result = woab::SignalResult;

    fn handle(&mut self, msg: woab::Signal, ctx: &mut Self::Context) -> Self::Result {
        Ok(match msg.name() {
            "addend_changed" => {
                let woab::params!(buffer: gtk4::TextBuffer) = msg.params()?;
                let new_number = buffer.text(&buffer.start_iter(), &buffer.end_iter(), true).parse().ok();
                if new_number != self.number {
                    self.number = new_number;
                    self.window.do_send(Recalculate);
                }
                None
            }
            "remove_addend" => {
                self.widgets
                    .row_addend
                    .parent()
                    .unwrap()
                    .downcast::<gtk4::ListBox>()
                    .unwrap()
                    .remove(&self.widgets.row_addend);
                ctx.stop();
                self.window.do_send(CheckForRemovedAddends);
                None
            }
            _ => msg.cant_handle()?,
        })
    }
}

struct Recalculate;

impl actix::Message for Recalculate {
    type Result = ();
}

impl actix::Handler<Recalculate> for WindowActor {
    type Result = ();

    fn handle(&mut self, _: Recalculate, ctx: &mut Self::Context) -> Self::Result {
        let futures =
            futures_util::future::join_all(self.addends.iter().map(|addend| addend.send(GetNumber)).collect::<Vec<_>>());
        ctx.spawn(futures.into_actor(self).map(|result, actor, _ctx| {
            let mut sum = 0;
            for addend in result {
                if let Some(addend) = addend.unwrap() {
                    sum += addend;
                } else {
                    actor.widgets.buf_sum.set_text("#N/A");
                    return;
                }
            }
            actor.widgets.buf_sum.set_text(&format!("{}", sum));
        }));
    }
}

struct GetNumber;

impl actix::Message for GetNumber {
    type Result = Option<isize>;
}

impl actix::Handler<GetNumber> for AddendActor {
    type Result = Option<isize>;

    fn handle(&mut self, _: GetNumber, _ctx: &mut Self::Context) -> Self::Result {
        self.number
    }
}

fn main() -> woab::Result<()> {
    let factories = std::rc::Rc::new(Factories::read(std::io::BufReader::new(std::fs::File::open(
        "examples/example.ui",
    )?))?);

    woab::main(Default::default(), move |app| {
        woab::shutdown_when_last_window_is_closed(app);
        let factories = factories.clone();
        WindowActor::create(|ctx| {
            let bld = factories.win_app.instantiate_route_to(ctx.address());
            bld.set_application(app);
            WindowActor {
                widgets: bld.widgets().unwrap(),
                factories,
                addends: Vec::new(),
            }
        });
        Ok(())
    })
}