354 lines
12 KiB
Rust
354 lines
12 KiB
Rust
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<Mutex<PathBuf>> = 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<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]
|
|
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::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<Self>,
|
|
) -> ComponentParts<Self> {
|
|
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<dyn Any + Send + Sync + 'static>);
|
|
|
|
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::<FileActionGroup>::new();
|
|
relm4::main_application().set_accelerators_for_action::<OpenAction>(&["<primary>O"]);
|
|
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::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<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))
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|