use super::sidebar::SideBarOutputMsg; use super::{ control_panel::{ControlPanelInputMsg, ControlPanelModel}, messages::{MonitorInputMsg, MonitorOutputMsg}, monitor::MonitorModel, setting::SettingModel, ControlPanelOutputMsg, TimelineMsg, }; use crate::components::sidebar::{SideBarInputMsg, SideBarModel}; use crate::data_utils::tools; use crate::pipeline::element::DataTarget; use crate::pipeline::element_imp::{Context, ElementInput, GridImpConfig}; use crate::pipeline::runner::Runner; use crate::pipeline::OffscreenRenderer; use crate::predefined::color_mapper::{BoundaryNorm, ColorMapper, ColorMapperComb, Discrete}; use crate::predefined::widgets::ColorBar; use crate::utils::meshgrid; use crate::widgets::{AssoElement, DynamicCol}; use crate::{ actions::register_layer_actions, pipeline::element::{Element, InstantElement, InstantElementDrawerType, TimeSeriesElement}, }; use crate::{ coords::{ cms::CMS, proj::{Mercator, ProjectionS}, Mapper, }, data::MetaInfo, errors::RenderError, pipeline::{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 gtk::glib::clone; use gtk::prelude::*; use ndarray::{ArrayView1, ArrayViewD}; use once_cell::sync::Lazy; use radarg_plugin_interface::{PluginResult, VecResult}; 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 LayerMsg { Add(Layer), Remove(usize), SwitchToTime(usize), Select(Vec), RemoveSelected, } #[derive(Debug)] pub enum AppMsg { CloseRequest, Close, OpenDialog, LayerManager(LayerMsg), Layer, NewElement(Element), DeleteElement(ElementKey), } 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] sidebar: Controller, selected_layer: Vec, #[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::glib::Propagation::Proceed }, 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::glib::Propagation::Proceed } }, 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, DynamicCol { set_end_width: 300, set_hexpand: true, set_vexpand: true, #[wrap(Some)] #[name="paned"] set_child_paned = >k::Paned{ #[local_ref] #[wrap(Some)] set_start_child=render->gtk::Frame{}, #[local_ref] #[wrap(Some)] set_end_child=sidebar->gtk::Box{}, } } }, }, 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![ Layer::new(true, "Layer 1".to_string(), AssoElement::Test), Layer::new(true, "Layer 2".to_string(), AssoElement::Test), ])); let control = ControlPanelModel::builder().launch(layers.clone()).forward( sender.input_sender(), |msg| match msg { ControlPanelOutputMsg::OpenFile((key, time)) => AppMsg::Close, }, ); let sidebar = SideBarModel::builder() .launch(layers.clone()) .forward(sender.input_sender(), |msg| { AppMsg::LayerManager(match msg { SideBarOutputMsg::SelectLayer(idx) => LayerMsg::Select(idx), SideBarOutputMsg::NewLayer(layer) => LayerMsg::Add(layer), SideBarOutputMsg::SwitchToTimeSeries(idx) => LayerMsg::SwitchToTime(idx), }) }); let render = MonitorModel::builder() .launch(layers.clone()) .forward(sender.input_sender(), |a| match a { MonitorOutputMsg::LayerRenderFinished => AppMsg::Close, _ => 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 = { let dialog_dispatcher = dispatcher.clone(); let dialog_sidebar_sender = sidebar.sender().clone(); let dialog_render_sender = render.sender().clone(); 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 (lat_start, lat_end) = meta.lat_range.unwrap(); let (lon_start, lon_end) = meta.lon_range.unwrap(); let imp = tools(&data); // 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 context = Context::new(dialog_cms, canvas); use femtovg::Color; let mut runner = Runner::new( imp, Arc::new(GridImpConfig { color_map: ColorMapperComb::BoundaryNormDiscrete(ColorMapper::new( Discrete::new(vec![ Color::rgb(0, 172, 164), Color::rgb(192, 192, 254), Color::rgb(122, 114, 238), Color::rgb(30, 38, 208), Color::rgb(166, 252, 168), Color::rgb(0, 234, 0), Color::rgb(16, 146, 26), Color::rgb(252, 244, 100), Color::rgb(200, 200, 2), Color::rgb(140, 140, 0), Color::rgb(254, 172, 172), Color::rgb(254, 100, 92), Color::rgb(238, 2, 48), Color::rgb(212, 142, 254), Color::rgb(170, 36, 250), ]), BoundaryNorm::new( vec![ 0i8, 5i8, 10i8, 15i8, 20i8, 25i8, 30i8, 35i8, 40i8, 45i8, 50i8, 55i8, 60i8, 65i8, 70i8, 75i8, ], false, Some(-125), ), )), }), context, ); let target = runner.run(&data); let data_target = DataTarget::new(Some(data), target); let element = Element::create_instant( InstantElementDrawerType::Prepared(data_target), dialog_dispatcher.clone(), "ET".to_string(), ) .get_instance(); let layer = Layer::new( true, "New Layer".to_string(), AssoElement::Instant(element), ); dialog_sidebar_sender.emit(SideBarInputMsg::AddMetaItems(meta.to_map())); dialog_render_sender.emit(MonitorInputMsg::SetRenderRange( lon_start, lon_end, lat_start, lat_end, )); // let data_target = element_impl.render(&data, &mut canvas, &mut dialog_cms); // let data_target = DataTarget::new(Some(data), data_target); // let element = Element::create_instant( // InstantElementDrawerType::Prepared((data_target, element_impl)), // dialog_dispatcher.clone(), // "ET".to_string(), // ) // .get_instance(); // let layer = Layer::new( // true, // "New Layer".to_string(), // AssoElement::Instant(element), // ); // dialog_sidebar_sender.emit(SideBarInputMsg::AddMetaItems(meta.to_map())); // dialog_render_sender.emit(MonitorInputMsg::SetRenderRange( // lon_start, lon_end, lat_start, lat_end, // )); // let layer = Layer::new(true, "New Layer".to_string(), AssoElement::Test); AppMsg::LayerManager(LayerMsg::Add(layer)) } _ => AppMsg::Close, }) }; 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, selected_layer: vec![], sidebar, control, render, layers, setting, tracker: 0, }; let render = model.render.widget(); let sidebar = model.sidebar.widget(); let widgets = view_output!(); let mut group = RelmActionGroup::::new(); relm4::main_application().set_accelerators_for_action::(&["O"]); register_layer_actions(&widgets.main_window, sender.clone()); 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::LayerManager(msg) => match msg { LayerMsg::Add(layer) => { (*self.layers).borrow_mut().push(layer); self.sidebar.sender().send(SideBarInputMsg::RefreshList); } LayerMsg::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.sidebar.sender().send(SideBarInputMsg::RefreshList); } LayerMsg::Select(idx) => { self.set_selected_layer(idx); } LayerMsg::RemoveSelected => { let mut layer = (*self.layers).borrow_mut(); let selected = self.selected_layer.clone(); for idx in selected { layer.remove(idx); } self.sidebar.sender().send(SideBarInputMsg::RefreshList); } LayerMsg::Remove(idx) => { let mut layers = (*self.layers).borrow_mut(); let mut layer = layers.remove(idx); if let AssoElement::TimeSeries(e) = layer.pop_associated_element() { let size = Arc::strong_count(&e); } self.sidebar.sender().send(SideBarInputMsg::RefreshList); } }, AppMsg::CloseRequest => { relm4::main_application().quit(); } AppMsg::Close => {} 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)) None } 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; } }