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, widgets: WindowWidgets, addends: Vec>, } impl actix::Actor for WindowActor { type Context = actix::Context; fn started(&mut self, ctx: &mut Self::Context) { self.widgets.win_app.show(); ctx.address().do_send(Recalculate); } } impl actix::Handler 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 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, number: Option, } impl actix::Actor for AddendActor { type Context = actix::Context; } #[derive(woab::WidgetsFromBuilder)] struct AddendWidgets { #[allow(unused)] row_addend: gtk4::ListBoxRow, } impl actix::Handler 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::() .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 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::>()); 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; } impl actix::Handler for AddendActor { type Result = Option; 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(()) }) }