radar-g/src/components/monitor/monitor.rs
2024-03-16 12:57:30 +08:00

282 lines
10 KiB
Rust

use super::messages::{MonitorInputMsg, MonitorOutputMsg};
use crate::coords::cms::CMS;
use crate::pipeline::offscreen_renderer::OffscreenRenderer;
use crate::widgets::predefined::color_mapper::BoundaryNorm;
use crate::widgets::predefined::widgets::ColorBar;
use crate::widgets::render::RenderConfig;
use crate::widgets::widget::{Widget, WidgetType};
use crate::widgets::WidgetFrame;
use crate::{
coords::{proj::Mercator, Mapper},
widgets::dynamic_col::DynamicCol,
widgets::render::{Layer, Render},
};
use geo::k_nearest_concave_hull;
use gtk::glib::clone;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use tracing::*;
use super::sidebar::{sidebar::SideBarModel, SideBarInputMsg, SideBarOutputMsg};
use crate::coords::Range;
use crate::map_tile::MapTile;
use crate::map_tile_utils::lat_lon_to_zoom;
use crate::pipeline::element::Target;
use crate::utils::estimate_zoom_level;
use adw::prelude::*;
use femtovg::ImageId;
use fns::debounce;
use relm4::{component::Component, *};
use slippy_map_tiles::Tile;
use tokio::task;
use tracing::instrument::WithSubscriber;
#[derive(Debug)]
pub enum MonitorCommand {
LoadedTile,
None,
}
#[tracker::track]
pub struct MonitorModel {
render_cfg: RenderConfig,
render_range: (f64, f64, f64, f64),
sidebar_open: bool,
sidebar_width: i32,
zoom: u8,
#[do_not_track]
last_call: Rc<RefCell<std::time::Instant>>,
#[do_not_track]
map_tile_getter: Rc<MapTile>,
new_layer: i8,
#[no_eq]
widgets: Vec<WidgetFrame>,
#[no_eq]
layers: Rc<RefCell<Vec<Layer>>>,
#[no_eq]
sidebar: Controller<SideBarModel>,
}
pub struct MonitorWidgets {
paned: gtk::Paned,
}
#[relm4::component(pub)]
impl Component for MonitorModel {
type CommandOutput = MonitorCommand;
type Input = MonitorInputMsg;
type Init = Rc<RefCell<Vec<Layer>>>;
type Output = MonitorOutputMsg;
view! {
#[root]
adw::BreakpointBin {
set_hexpand: true,
set_vexpand: true,
set_height_request: 500,
set_width_request: 700,
#[wrap(Some)]
#[name="test"]
set_child = &DynamicCol{
set_end_width: 300,
set_hexpand: true,
set_vexpand: true,
#[wrap(Some)]
#[name="paned"]
set_child_paned=&gtk::Paned{
#[wrap(Some)]
#[name="render"]
set_start_child=&gtk::Frame{
add_css_class: "rb",
set_margin_all: 5,
#[name="widget_layer"]
gtk::Overlay{
#[name = "renderer"]
#[wrap(Some)]
set_child = &Render{
#[track = "model.changed(MonitorModel::render_cfg())"]
set_cfg: model.render_cfg,
#[track = "model.changed(MonitorModel::render_range())"]
set_view: model.render_range,
set_tiles: model.map_tile_getter.clone(),
connect_render_status_notify[sender] => move |r| {
sender.output(MonitorOutputMsg::LayerRenderFinished);
},
connect_range_changing_notify[sender] => move |r| {
sender.input(MonitorInputMsg::RefreshTiles);
},
connect_scale_notify[sender] => move |r| {
let scale = r.scale();
{
let initial = r.scale_rate();
let mut rate_start = initial_rate.lock().unwrap();
if rate_start.is_none() {
*rate_start = Some(scale);
}
}
debouncer.call(scale);
},
set_interior_layers: model.layers.clone(),
},
add_overlay=&gtk::Button{
set_label:"Add",
set_margin_all:10,
set_valign: gtk::Align::Start,
set_halign: gtk::Align::End,
},
#[track = "model.changed(MonitorModel::new_layer())"]
#[iterate]
add_overlay: &model.widgets
},
},
#[wrap(Some)]
set_end_child=model.sidebar.widget(),
}
}
}
}
fn update_with_view(
&mut self,
widgets: &mut Self::Widgets,
message: Self::Input,
sender: ComponentSender<Self>,
root: &Self::Root,
) {
self.reset();
match message {
MonitorInputMsg::RefreshRender => {
widgets.renderer.queue_render();
}
MonitorInputMsg::RefreshLayerList => {
self.sidebar.sender().send(SideBarInputMsg::RefreshList);
sender.input(MonitorInputMsg::RefreshRender);
}
MonitorInputMsg::AddMetaItem(map) => {
self.sidebar.emit(SideBarInputMsg::AddMetaItems(map))
}
MonitorInputMsg::SetRenderRange(lon_start, lon_end, lat_start, lat_end) => {
let current_rate = widgets.renderer.scale_rate();
self.set_render_range((lat_start, lat_end, lon_start, lon_end));
let new_rate = widgets.renderer.scale_rate();
let zoom: f64 = (current_rate / new_rate).log2();
sender.input(MonitorInputMsg::ChangeZoom(zoom));
}
MonitorInputMsg::ClearMetaItems => self.sidebar.emit(SideBarInputMsg::ClearMetaItems),
MonitorInputMsg::UpdateMetaItem(map) => {
self.sidebar.emit(SideBarInputMsg::ClearMetaItems);
self.sidebar.emit(SideBarInputMsg::AddMetaItems(map))
}
MonitorInputMsg::RefreshTiles => {
let ((x1, x2), (y1, y2)) = widgets.renderer.render_range();
self.load_tile(&sender, ((y1 as f32, y2 as f32), (x1 as f32, x2 as f32)));
}
MonitorInputMsg::AddWidget(widget) => match widget.widget_type() {
WidgetType::Cairo => {
let frame = WidgetFrame::new();
frame.set_widget(widget);
self.widgets.push(frame);
}
WidgetType::OpenGl => {}
_ => {}
},
MonitorInputMsg::ChangeZoom(zoom) => {
let new_zoom = (self.zoom as f64 + zoom).clamp(0.0, 19.0).round() as u8;
if self.zoom != new_zoom {
self.zoom = new_zoom;
self.map_tile_getter.set_zoom(new_zoom);
sender.input(MonitorInputMsg::RefreshTiles);
}
}
MonitorInputMsg::RemoveWidget => {}
MonitorInputMsg::None => {}
_ => {}
}
self.update_view(widgets, sender);
}
fn init(
init: Self::Init,
root: Self::Root,
sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
let sidebar_sender = sender.clone();
let sidebar: Controller<SideBarModel> = SideBarModel::builder()
.launch(init.clone())
.forward(sender.input_sender(), move |msg| match msg {
SideBarOutputMsg::SwitchToTimeSeries(layer) => {
sidebar_sender.output(MonitorOutputMsg::LayerSwitchToTime(layer));
MonitorInputMsg::None
}
_ => MonitorInputMsg::None,
});
let render_cfg = RenderConfig {
padding: [0.0, 0.0, 0.0, 0.0],
};
let new_sender = sender.clone();
let mut model = MonitorModel {
render_range: (4.0, 53.3, 73.3, 135.0),
new_layer: 0,
widgets: vec![],
render_cfg,
sidebar_open: true,
sidebar_width: 400,
last_call: Rc::new(RefCell::new(std::time::Instant::now())),
layers: init,
zoom: 4,
map_tile_getter: Rc::new(MapTile::default()),
sidebar,
tracker: 0,
};
let initial_rate = Arc::new(Mutex::new(None));
let debouncer_rate = initial_rate.clone();
let debouncer_sender = sender.clone();
let debouncer = fns::debounce(
move |zoom: f64| {
let rate: f64 = debouncer_rate.lock().unwrap().take().unwrap();
let zoom: f64 = (rate / zoom).log2();
debouncer_sender.input(MonitorInputMsg::ChangeZoom(zoom));
debouncer_sender.input(MonitorInputMsg::RefreshRender);
},
std::time::Duration::from_millis(500),
);
let widgets = view_output! {};
ComponentParts { model, widgets }
}
fn update_cmd_with_view(
&mut self,
widgets: &mut Self::Widgets,
message: Self::CommandOutput,
sender: ComponentSender<Self>,
root: &Self::Root,
) {
self.reset();
match message {
MonitorCommand::LoadedTile => {
sender.input(MonitorInputMsg::RefreshRender);
}
_ => {}
}
self.update_view(widgets, sender);
}
}
impl MonitorModel {
fn load_tile(&self, sender: &ComponentSender<Self>, range: ((f32, f32), (f32, f32))) {
let task = self.map_tile_getter.load_tiles(range, sender.clone());
sender.oneshot_command(async move {
task.await;
MonitorCommand::None
});
}
}