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>, #[do_not_track] map_tile_getter: Rc, new_layer: i8, #[no_eq] widgets: Vec, #[no_eq] layers: Rc>>, #[no_eq] sidebar: Controller, } pub struct MonitorWidgets { paned: gtk::Paned, } #[relm4::component(pub)] impl Component for MonitorModel { type CommandOutput = MonitorCommand; type Input = MonitorInputMsg; type Init = Rc>>; 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=>k::Paned{ #[wrap(Some)] #[name="render"] set_start_child=>k::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=>k::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, 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, ) -> ComponentParts { let sidebar_sender = sender.clone(); let sidebar: Controller = 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, 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, 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 }); } }