use super::{ control_panel::{ControlPanelInputMsg, ControlPanelModel}, messages::{MonitorInputMsg, MonitorOutputMsg}, monitor::MonitorModel, setting::SettingModel, ControlPanelOutputMsg, TimelineMsg, }; use crate::data_utils::plugin_result_impl; use crate::pipeline::element::{ Element, InstantElement, InstantElementDrawerType, TimeSeriesElement, }; use crate::pipeline::{GridElementImpl, OffscreenRenderer}; use crate::widgets::AssoElement; use crate::{ coords::{ cms::CMS, proj::{Mercator, ProjectionS}, Mapper, }, data::MetaInfo, errors::RenderError, pipeline::{utils::data_to_element, Dispatcher, Pipeline, RenderResult}, plugin_system::init_plugin, widgets::render::Layer, CONFIG, PLUGIN_MANAGER, }; use abi_stable::std_types::RStr; use adw::prelude::*; use chrono::{prelude::*, Duration}; use futures::future::BoxFuture; use glib::clone; use gtk::prelude::*; use once_cell::sync::Lazy; use radarg_plugin_interface::PluginResult; use relm4::actions::{AccelsPlus, RelmAction, RelmActionGroup}; use relm4::*; use relm4::{gtk, Component, ComponentParts, ComponentSender, RelmWidgetExt, SimpleComponent}; use relm4_components::open_dialog::{ OpenDialog, OpenDialogMsg, OpenDialogResponse, OpenDialogSettings, }; use smallvec::SmallVec; use std::marker::PhantomData; use std::{ any::Any, borrow::{Borrow, BorrowMut}, cell::RefCell, collections::{BTreeMap, HashMap}, path::PathBuf, rc::Rc, sync::{Arc, Mutex}, }; use tokio::{sync::oneshot, task}; use tracing::{debug, error, info, warn}; use tracing_subscriber::fmt::layer; relm4::new_action_group!(FileActionGroup, "file"); relm4::new_stateless_action!(OpenAction, FileActionGroup, "open"); pub static FILE_PATH_ROOT: Lazy> = Lazy::new(|| Mutex::new(PathBuf::new())); pub type ElementKey = String; #[derive(Debug)] pub enum AppMsg { CloseRequest, Close, OpenDialog, SwitchToTime(usize), NewElement(Element), DeleteElement(ElementKey), NewLayer(Layer), } pub type Buffer = Rc, Option>>>>; type RcDispatcher = Rc; #[tracker::track] pub struct AppModel { #[do_not_track] dispatcher: RcDispatcher, #[do_not_track] cms: CMS, waiting_for: Option>, #[do_not_track] open_dialog: Controller, #[do_not_track] control: Controller, #[do_not_track] render: Controller, #[do_not_track] layers: Rc>>, #[do_not_track] elements: Vec>>, #[do_not_track] setting: Controller, } #[derive(Debug)] pub enum AppCommand { PrepareFinished(Vec>), TestBuffer((String, RenderResult)), Test, } #[relm4::component(pub)] impl Component for AppModel { type CommandOutput = AppCommand; type Init = (); type Input = AppMsg; type Output = (); view! { #[root] main_window=adw::ApplicationWindow { set_default_width: 1200, set_default_height: 900, set_focus_on_click:true, connect_close_request[sender] => move |_| { sender.input(AppMsg::CloseRequest); gtk::Inhibit(true) }, gtk::Box{ set_orientation: gtk::Orientation::Vertical, set_valign:gtk::Align::Fill, set_spacing:2, gtk::HeaderBar{ pack_start=&adw::ViewSwitcher{ set_stack: Some(&view_stack), } }, #[name="view_stack"] adw::ViewStack{ set_hexpand:true, set_vexpand:true, }, }, connect_close_request[sender] => move |_| { sender.input(AppMsg::CloseRequest); gtk::Inhibit(true) } }, popover_child = gtk::Spinner { set_spinning: true, }, home_page = gtk::Box{ set_orientation: gtk::Orientation::Vertical, set_hexpand:true, set_vexpand:true, gtk::Box{ set_orientation: gtk::Orientation::Horizontal, set_margin_top: 2, set_margin_start: 10, set_margin_end: 10, #[name="popover_menu_bar"] gtk::PopoverMenuBar::from_model(Some(&main_menu)){ set_hexpand: true, }, }, model.control.widget(), #[name="monitor_toast"] adw::ToastOverlay{ set_hexpand: true, set_vexpand: true, model.render.widget(), }, }, home_stack_page = view_stack.add_titled(&home_page, Some("home"), "Home") -> adw::ViewStackPage{ set_icon_name:Some("home-filled"), }, setting_stack_page = view_stack.add_titled(model.setting.widget(), Some("setting"), "Setting") -> adw::ViewStackPage{ set_icon_name:Some("settings-filled") }, } menu! { main_menu: { "File" { "Open" => OpenAction, "Open Folder" => OpenAction, }, "Edit" { "New Layer" => OpenAction, "Undo" => OpenAction, "Redo" => OpenAction, }, "Plugins" { "Plugin1" => OpenAction, "Plugin2" => OpenAction, }, } } fn init( params: Self::Init, root: &Self::Root, sender: ComponentSender, ) -> ComponentParts { let layers = Rc::new(RefCell::new(Vec::with_capacity(20))); let control = ControlPanelModel::builder().launch(layers.clone()).forward( sender.input_sender(), |msg| match msg { ControlPanelOutputMsg::OpenFile((key, time)) => AppMsg::Close, }, ); let render = MonitorModel::builder() .launch(layers.clone()) .forward(sender.input_sender(), |a| match a { MonitorOutputMsg::LayerRenderFinished => AppMsg::Close, MonitorOutputMsg::LayerSwitchToTime(idx) => AppMsg::SwitchToTime(idx), _ => AppMsg::Close, }); let setting = SettingModel::builder() .launch(()) .forward(sender.input_sender(), |a| AppMsg::Close); let mut dispatcher = Rc::new(Dispatcher::new(5, 5, chrono::Duration::minutes(1))); let cms = CMS::new(Mercator::default().into(), (3000.0, 3000.0)); let dialog_dispatcher = dispatcher.clone(); let dialog_render_sender = render.sender().clone(); let dialog = OpenDialog::builder() .transient_for_native(&root) .launch(OpenDialogSettings::default()) .forward(sender.input_sender(), move |response| match response { OpenDialogResponse::Accept(path) => { *FILE_PATH_ROOT.lock().unwrap() = path.clone(); let data = Self::open_file_only(path); let meta: MetaInfo = (&data.meta).clone().into(); let element_impl = plugin_result_impl(&data); let mut renderer = OffscreenRenderer::new(3000, 3000).unwrap(); let mut canvas = renderer.create_canvas(); let mut dialog_cms = CMS::new(Mercator::default().into(), (3000.0, 3000.0)); let mut data_target = element_impl.render(&data, &mut canvas, &mut dialog_cms); data_target.data = Some(Arc::new(data) as Arc); let element = Element::create_instant( InstantElementDrawerType::Prepared((data_target, Arc::new(element_impl))), dialog_dispatcher.clone(), "ET".to_string(), ) .get_instance(); let layer = Layer::new(true, "New Layer".to_string(), AssoElement::Instant(element)); dialog_render_sender.emit(MonitorInputMsg::AddMetaItem(meta.to_map())); AppMsg::NewLayer(layer) } _ => AppMsg::Close, }); relm4_icons::initialize_icons(); let buffer: Buffer = Rc::new(RefCell::new(HashMap::new())); let model = AppModel { cms, dispatcher, waiting_for: None, elements: Vec::with_capacity(20), open_dialog: dialog, control, render, layers, setting, tracker: 0, }; let widgets = view_output!(); let mut group = RelmActionGroup::::new(); relm4::main_application().set_accelerators_for_action::(&["O"]); let action: RelmAction = { RelmAction::new_stateless(move |_| { sender.input(AppMsg::OpenDialog); }) }; group.add_action(action); group.register_for_widget(&widgets.main_window); ComponentParts { model, widgets } } fn update_with_view( &mut self, widgets: &mut Self::Widgets, msg: Self::Input, _sender: ComponentSender, root: &Self::Root, ) { self.reset(); match msg { AppMsg::NewLayer(layer) => { (*self.layers).borrow_mut().push(layer); self.render.sender().send(MonitorInputMsg::RefreshLayerList); } AppMsg::CloseRequest => { relm4::main_application().quit(); } AppMsg::Close => {} AppMsg::SwitchToTime(idx) => { let mut layer = (*self.layers).borrow_mut(); let switched_layer = layer.get_mut(idx).unwrap(); let asso_element = switched_layer.pop_associated_element(); if let AssoElement::Instant(e) = asso_element { let dispatcher = self.dispatcher.clone(); let cms = self.cms.clone(); let (mut series, start_time) = e.to_time_series(dispatcher, cms); switched_layer.set_time(start_time); series.register(start_time).unwrap(); let element = Arc::new(Mutex::new(series)); switched_layer.set_associated_element(AssoElement::TimeSeries(element.clone())); self.elements.push(element); } self.render.sender().send(MonitorInputMsg::RefreshLayerList); } AppMsg::OpenDialog => { self.open_dialog.emit(OpenDialogMsg::Open); } _ => {} } self.update_view(widgets, _sender); } fn update_cmd( &mut self, message: Self::CommandOutput, sender: ComponentSender, root: &Self::Root, ) { match message { AppCommand::PrepareFinished(mut v) => {} _ => { println!("test"); } } } } impl AppModel { fn open_file( path: impl AsRef, dispatcher: Rc, cms: CMS, ) -> Option<(Option>, Element)> { let plugin = PLUGIN_MANAGER.get_plugin_by_name("etws_loader").unwrap(); let mut result = plugin .load(RStr::from_str(path.as_ref().to_str().unwrap())) .unwrap(); let block = result.blocks.first().unwrap(); data_to_element(block, dispatcher, cms) .map(|v| (Some(Box::new(result) as Box), v)) } fn open_file_only(path: impl AsRef) -> PluginResult { let plugin = PLUGIN_MANAGER.get_plugin_by_name("etws_loader").unwrap(); let mut result = plugin .load(RStr::from_str(path.as_ref().to_str().unwrap())) .unwrap(); return result; } }