radar-g/src/components/app.rs
2024-04-15 22:29:35 +08:00

506 lines
19 KiB
Rust

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<Mutex<PathBuf>> = Lazy::new(|| Mutex::new(PathBuf::new()));
pub type ElementKey = String;
#[derive(Debug)]
pub enum LayerMsg {
Add(Layer),
Remove(usize),
SwitchToTime(usize),
Select(Vec<usize>),
RemoveSelected,
}
#[derive(Debug)]
pub enum AppMsg {
CloseRequest,
Close,
OpenDialog,
LayerManager(LayerMsg),
Layer,
NewElement(Element),
DeleteElement(ElementKey),
}
pub type Buffer = Rc<RefCell<HashMap<String, BTreeMap<DateTime<Utc>, Option<RenderResult>>>>>;
type RcDispatcher = Rc<Dispatcher>;
#[tracker::track]
pub struct AppModel {
#[do_not_track]
dispatcher: RcDispatcher,
#[do_not_track]
cms: CMS,
waiting_for: Option<DateTime<Utc>>,
#[do_not_track]
open_dialog: Controller<OpenDialog>,
#[do_not_track]
control: Controller<ControlPanelModel>,
#[do_not_track]
render: Controller<MonitorModel>,
#[do_not_track]
sidebar: Controller<SideBarModel>,
selected_layer: Vec<usize>,
#[do_not_track]
layers: Rc<RefCell<Vec<Layer>>>,
#[do_not_track]
elements: Vec<Arc<Mutex<TimeSeriesElement>>>,
#[do_not_track]
setting: Controller<SettingModel>,
}
#[derive(Debug)]
pub enum AppCommand {
PrepareFinished(Vec<Result<RenderResult, RenderError>>),
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 = &gtk::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<Self>,
) -> ComponentParts<Self> {
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::<FileActionGroup>::new();
relm4::main_application().set_accelerators_for_action::<OpenAction>(&["<primary>O"]);
register_layer_actions(&widgets.main_window, sender.clone());
let action: RelmAction<OpenAction> = {
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<Self>,
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<Self>,
root: &Self::Root,
) {
match message {
AppCommand::PrepareFinished(mut v) => {}
_ => {
println!("test");
}
}
}
}
impl AppModel {
fn open_file(
path: impl AsRef<std::path::Path>,
dispatcher: Rc<Dispatcher>,
cms: CMS,
) -> Option<(Option<Box<dyn Any + Send + Sync>>, 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<dyn Any + Send + Sync>), v))
None
}
fn open_file_only(path: impl AsRef<std::path::Path>) -> 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;
}
}