506 lines
19 KiB
Rust
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 = >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<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;
|
|
}
|
|
}
|