radar-g/src/components/app.rs
2024-03-07 18:08:16 +08:00

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;
}
}