From 73d53b734c5b49bd187cf0b47e613b25f90d0e10 Mon Sep 17 00:00:00 2001 From: Tsuki Date: Sun, 4 Feb 2024 20:58:45 +0800 Subject: [PATCH] sync --- Cargo.lock | 69 +++ Cargo.toml | 2 + src/components/app.rs | 487 +++++++++--------- src/components/control_panel/control_panel.rs | 18 + src/components/control_panel/messages.rs | 4 + src/components/control_panel/thumbnail.rs | 10 +- src/components/monitor/messages.rs | 5 +- src/components/monitor/monitor.rs | 88 ++-- src/components/monitor/sidebar/sidebar.rs | 30 +- src/main.rs | 7 +- src/pipeline/offscreen_renderer.rs | 4 +- src/pipeline/utils.rs | 98 +++- src/render/imp.rs | 317 ------------ src/utils.rs | 10 + src/widgets/render/imp.rs | 29 +- src/widgets/render/mod.rs | 2 + src/widgets/render/predefined/color_mapper.rs | 33 +- src/widgets/render/predefined/mod.rs | 3 +- src/widgets/render/predefined/widgets.rs | 51 ++ src/widgets/render/widget/mod.rs | 3 + src/widgets/render/widget/widget.rs | 7 + 21 files changed, 617 insertions(+), 660 deletions(-) delete mode 100644 src/render/imp.rs create mode 100644 src/widgets/render/predefined/widgets.rs create mode 100644 src/widgets/render/widget/mod.rs create mode 100644 src/widgets/render/widget/widget.rs diff --git a/Cargo.lock b/Cargo.lock index 08df890..6910318 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -537,6 +537,8 @@ dependencies = [ "tokio", "toml 0.8.8", "topojson", + "tracing", + "tracing-subscriber", "tracker", ] @@ -2368,6 +2370,16 @@ dependencies = [ "zip", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num" version = "0.2.1" @@ -2624,6 +2636,12 @@ dependencies = [ "libredox 0.0.2", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "owned_ttf_parser" version = "0.20.0" @@ -3439,6 +3457,15 @@ dependencies = [ "geo-types", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shared_library" version = "0.1.9" @@ -3691,6 +3718,16 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "tiff" version = "0.9.0" @@ -3873,6 +3910,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", ] [[package]] @@ -4000,6 +4063,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "vec_map" version = "0.8.2" diff --git a/Cargo.toml b/Cargo.toml index 0088647..1aa8718 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,6 +83,8 @@ smallvec = "1.13.1" rayon = "1.8.1" futures = "0.3.30" sorted-vec = "0.8.3" +tracing = "0.1.40" +tracing-subscriber = "0.3.18" [build-dependencies] diff --git a/src/components/app.rs b/src/components/app.rs index 34e8073..38799e2 100644 --- a/src/components/app.rs +++ b/src/components/app.rs @@ -6,7 +6,7 @@ use crate::{ self, offscreen_renderer::OffscreenRenderer, render_pipeline::RenderResult, - utils::{Dispatcher, Pipeline}, + utils::{data_to_layer, Dispatcher, Pipeline}, }, plugin_system::init_plugin, widgets::{ @@ -15,6 +15,8 @@ use crate::{ }, PLUGIN_MANAGER, }; +use adw::prelude::*; +use gtk::prelude::*; use std::{ borrow::{Borrow, BorrowMut}, cell::RefCell, @@ -22,10 +24,11 @@ use std::{ rc::Rc, sync::{Arc, Mutex}, }; +use tracing::{debug, error, info, warn}; use super::{ control_panel::{ControlPanelInputMsg, ControlPanelModel}, - messages::MonitorInputMsg, + messages::{MonitorInputMsg, MonitorOutputMsg}, monitor::MonitorModel, setting::SettingModel, ControlPanelOutputMsg, TimelineMsg, @@ -57,18 +60,29 @@ pub enum AppMsg { CloseRequest, Close, OpenDialog, + SwitchTo((String, DateTime, Option)), + RenderLayer((Layer, DateTime)), OpenDialogMulti, - OpenFile((DateTime, Layer)), - CheckTo((String, DateTime)), + RenderSuccess, } - +pub type Buffer = Rc, Option>>>>; +type RcDispatcher = Rc>; +#[tracker::track] pub struct AppModel { - dispatcher: Arc>, - buffer: Rc, Option)>>>>, + #[do_not_track] + dispatcher: RcDispatcher, + #[do_not_track] + buffer: Buffer, + waiting_for: Option>, + #[do_not_track] open_dialog: Controller, + #[do_not_track] control: Controller, + #[do_not_track] target_pipeline: HashMap, + #[do_not_track] render: Controller, + #[do_not_track] setting: Controller, } @@ -106,14 +120,12 @@ impl Component for AppModel { 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) @@ -122,6 +134,34 @@ impl Component for AppModel { 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! { @@ -150,13 +190,17 @@ impl Component for AppModel { let control = ControlPanelModel::builder().launch(0).forward( sender.input_sender(), |msg| match msg { - ControlPanelOutputMsg::OpenFile((key, time)) => AppMsg::CheckTo((key, time)), + ControlPanelOutputMsg::OpenFile((key, time)) => AppMsg::SwitchTo((key, time, None)), }, ); - let render = MonitorModel::builder() - .launch(()) - .forward(sender.input_sender(), |a| AppMsg::Close); + let render = + MonitorModel::builder() + .launch(()) + .forward(sender.input_sender(), |a| match a { + MonitorOutputMsg::LayerRenderFinished => AppMsg::RenderSuccess, + _ => AppMsg::Close, + }); let setting = SettingModel::builder() .launch(()) @@ -167,18 +211,19 @@ impl Component for AppModel { .launch(OpenDialogSettings::default()) .forward(sender.input_sender(), |response| match response { OpenDialogResponse::Accept(path) => { - let plugin = PLUGIN_MANAGER.get_plugin_by_name("etws_loader").unwrap(); - let mut result = plugin.load(RStr::from_str(path.to_str().unwrap())).unwrap(); - let mut block = result.blocks.pop().unwrap(); - data_to_grid_layer(block) + if let Some((a, b)) = Self::open_file(path) { + AppMsg::SwitchTo((b.name.clone(), a, Some(b))) + } else { + AppMsg::Close + } } - OpenDialogResponse::Cancel => AppMsg::Close, + _ => AppMsg::Close, }); let app = relm4::main_application(); relm4_icons::initialize_icons(); - let buffer = Rc::new(RefCell::new(HashMap::new())); + let buffer: Buffer = Rc::new(RefCell::new(HashMap::new())); let mut dispatcher = Dispatcher::new(5, 5, chrono::Duration::minutes(1), buffer.clone()); let mut path_format = HashMap::new(); @@ -189,47 +234,20 @@ impl Component for AppModel { dispatcher.set_path_format(path_format); let model = AppModel { - // time_buffer: HashMap::new(), buffer: buffer, - dispatcher: Arc::new(Mutex::new(dispatcher)), + dispatcher: Rc::new(RefCell::new(dispatcher)), + waiting_for: None, open_dialog: dialog, target_pipeline: HashMap::new(), control, render, setting, + tracker: 0, }; let widgets = view_output!(); let mut group = RelmActionGroup::::new(); - let page_home = gtk::Box::builder() - .hexpand(true) - .vexpand(true) - .orientation(gtk::Orientation::Vertical) - .build(); - - let setting_bar = gtk::Box::builder() - .orientation(gtk::Orientation::Horizontal) - .margin_top(2) - .margin_start(10) - .margin_end(10) - .build(); - let popover_menu_bar = gtk::PopoverMenuBar::from_model(Some(&main_menu)); - popover_menu_bar.set_hexpand(true); - setting_bar.append(&popover_menu_bar); - page_home.append(&setting_bar); - page_home.append(model.control.widget()); - page_home.append(model.render.widget()); - - let view_stack = widgets.view_stack.clone(); - let page_home = view_stack.add_titled(&page_home, Some("renderer"), "Render"); - page_home.set_icon_name(Some("home-filled")); - - let page_setting = model.setting.widget(); - - let page_setting = view_stack.add_titled(page_setting, Some("setting"), "Setting"); - page_setting.set_icon_name(Some("settings-filled")); - app.set_accelerators_for_action::(&["O"]); let action: RelmAction = { RelmAction::new_stateless(move |_| { @@ -242,121 +260,131 @@ impl Component for AppModel { ComponentParts { model, widgets } } - fn update(&mut self, msg: Self::Input, _sender: ComponentSender, root: &Self::Root) { + fn update_with_view( + &mut self, + widgets: &mut Self::Widgets, + msg: Self::Input, + _sender: ComponentSender, + root: &Self::Root, + ) { + self.reset(); match msg { - AppMsg::OpenFile((time, layer)) => { - let layer_name = layer.name.clone(); + AppMsg::SwitchTo((key, datetime, layer)) => { + println!("Switch to {}", datetime); + self.create_pipeline(key.clone()); + self.create_buffer(key.clone()); - // Init Pipeline and buffer by key - let pipeline = self.create_pipeline(layer_name.as_ref()); - let buffer = self.create_buffer(layer_name.clone()); - let tasks = self.create_tasks(layer_name, time); + { + let mut current_buffer = (*self.buffer).borrow_mut(); + let current_buffer = current_buffer.get_mut(key.as_str()).unwrap(); - // Monitor Render Result first and Set the timeline widget's info - self.render.sender().emit(MonitorInputMsg::AddLayer(layer)); - self.control - .sender() - .emit(ControlPanelInputMsg::Selection(Some(time))); - self.control - .sender() - .emit(ControlPanelInputMsg::TimeLine(TimelineMsg::SetStart( - time - Duration::minutes(30), - ))); + if let Some(layer) = layer { + self.control.emit(ControlPanelInputMsg::Disable); + _sender.input(AppMsg::RenderLayer((layer, datetime))); + } else { + if let Some(v) = current_buffer.get_mut(&datetime) { + // Task already in pipeline + if v.is_none() { + // Still on the way, need to show the progress + self.control.emit(ControlPanelInputMsg::Disable); + self.waiting_for = Some(datetime); + info!("Task still on the way"); + let toast = + adw::Toast::builder().title("Task still on the way").build(); + widgets.monitor_toast.add_toast(toast); + } else { + // Task finished, Need to show the result + let v = v.as_ref().unwrap().clone(); - // Start New Pipeline + info!("Task finished"); + _sender.input(AppMsg::RenderLayer((v.layer, datetime))); + } + } else { + // Task not in pipeline, Need to open this file + let layer = (*self.dispatcher) + .borrow() + .get_single_path(&key, datetime, true) + .map(|p| Self::open_file(p)) + .flatten() + .map(|x| x.1); + if layer.is_none() { + // TODO: Show Error + } else { + self.control.emit(ControlPanelInputMsg::Disable); + _sender.input(AppMsg::RenderLayer((layer.unwrap(), datetime))); + } + } + } + } + + let tasks = self.create_tasks(key.clone(), datetime); + let pipeline = self.target_pipeline.get_mut(key.as_str()).unwrap(); let worker = Pipeline::run(pipeline); + let render_sender = self.render.sender(); + let control_sender = self.control.sender(); let new_sender = _sender.clone(); - // Listen to the result, if the result is ok, send it to the buffer - let mut thumb_senders: Vec> = Vec::new(); - let mut thumb_recivers = Vec::new(); - let mut current_buffer = (*self.buffer).borrow_mut(); - let mut current_buffer = current_buffer.get_mut(layer_name.as_str()).unwrap(); + if let Some(tasks) = tasks { + if tasks.len() == 0 { + widgets.monitor_toast.add_toast( + adw::Toast::builder() + .title(format!("no data found: {}", datetime)) + .build(), + ); + return; + } + (*self.buffer) + .borrow_mut() + .get_mut(key.clone().as_str()) + .unwrap() + .extend(tasks.clone()); + let (thumb_recivers, listening_func) = self.create_listening_with_thumb( + key.clone(), + tasks.len(), + new_sender.clone(), + ); - for _ in 0..num { - let (tx, rx) = oneshot::channel(); - thumb_recivers.push(rx); - thumb_senders.push(tx); + control_sender.emit(ControlPanelInputMsg::SetThumb( + thumb_recivers + .into_iter() + .map(|p| (None, Some(p), datetime)) + .collect(), + )); + + let listening = self + .current_pipeline(key.clone()) + .listening_one_by_one(listening_func); + + // Spawn the worker and listen to the result + new_sender.oneshot_command(async move { + worker.await; + listening.await; + AppCommand::Test + }); } - - self.control.emit(ControlPanelInputMsg::SetThumb( - thumb_recivers - .into_iter() - .map(|p| (None, Some(p), time)) - .collect(), - )); - - let mut p = Vec::new(); - for sender in thumb_senders { - let new_sender = _sender.clone(); - let f = move |tx: oneshot::Receiver>| { - Box::pin(async move { - if let Ok(r) = tx.await { - let r = r.unwrap(); - let tex = (&r) - .layer - .render_target() - .lock() - .unwrap() - .as_ref() - .unwrap() - .thumbnail - .clone(); - sender.send(tex.unwrap()).unwrap(); - new_sender - .command_sender() - .emit(AppCommand::TestBuffer((format!("DBZ"), r))); - } - }) as BoxFuture<'static, ()> - }; - p.push(f); - } - - let listening = pipeline.listening_one_by_one(p); - // Spawn the worker and listen to the result - new_sender.oneshot_command(async move { - task::spawn(worker); - listening.await; - AppCommand::Test - }); } AppMsg::CloseRequest => {} AppMsg::Close => {} AppMsg::OpenDialog => { self.open_dialog.emit(OpenDialogMsg::Open); } - AppMsg::CheckTo((key, datetime)) => { - use std::iter::Iterator; - println!("CheckTo"); - let buffer = (*self.buffer).borrow(); - if let Some(t) = buffer.get(key.as_str()) { - let current_pipeline = self.target_pipeline.get_mut(key.as_str()).unwrap(); - current_pipeline.set_current(datetime, true); - let worker = Pipeline::run(current_pipeline); - - if let Some(p) = t.iter().position(|(dt, t)| *dt == datetime) { - let (_, rs) = t.get(p).unwrap(); - if let None = rs.as_ref() { - //TODO: Add a spinner - } else { - let r = rs.as_ref().unwrap(); - self.render.emit(MonitorInputMsg::RemoveLayer(0)); - self.render.emit(MonitorInputMsg::AddLayer(r.layer.clone())); - } - } else { - panic!("Pipeline error"); - } - } else { - panic!("Pipeline error"); - } + AppMsg::RenderLayer((layer, dt)) => { + self.render.emit(MonitorInputMsg::AddLayer(layer)); + self.control.emit(ControlPanelInputMsg::Selection(Some(dt))); + self.control + .emit(ControlPanelInputMsg::TimeLine(TimelineMsg::SetStart( + dt - Duration::minutes(30), + ))); + } + AppMsg::RenderSuccess => { + self.control.emit(ControlPanelInputMsg::Enable); } AppMsg::OpenDialogMulti => {} } } - fn update_cmd_with_view( + fn update_cmd( &mut self, - widgets: &mut Self::Widgets, message: Self::CommandOutput, sender: ComponentSender, root: &Self::Root, @@ -367,11 +395,13 @@ impl Component for AppModel { let datetime = result.time(); let mut buffer = (*self.buffer).borrow_mut(); let mut buffer = buffer.get_mut(key.as_str()).unwrap(); - let _position = buffer - .iter_mut() - .position(|(t, r)| *t == result.time()) - .unwrap(); - buffer[_position].1 = Some(result); + if let Some(waiting_for) = self.waiting_for { + if waiting_for == datetime { + self.waiting_for = None; + sender.input(AppMsg::RenderLayer((result.layer.clone(), datetime))); + } + } + buffer.get_mut(&datetime).unwrap().replace(result); } _ => { println!("test"); @@ -381,17 +411,12 @@ impl Component for AppModel { } impl AppModel { - fn create_buffer( - &mut self, - key: impl Borrow, - ) -> &mut Vec<(DateTime, Option)> { + fn create_buffer(&mut self, key: impl Borrow) { if !(*self.buffer).borrow().contains_key(key.borrow()) { (*self.buffer) .borrow_mut() - .insert(key.borrow().to_string(), Vec::new()); + .insert(key.borrow().to_string(), HashMap::new()); } - - &mut (*(*self.buffer).borrow_mut().get_mut(key.borrow()).unwrap()) } fn create_pipeline(&mut self, key: impl Borrow) -> &mut Pipeline { @@ -410,99 +435,75 @@ impl AppModel { time: DateTime, ) -> Option, Option)>> { let pipeline = self.target_pipeline.get_mut(key.as_ref()).unwrap(); - let time_list = pipeline.set_current(time, true); - let mut buffer = (*self.buffer).borrow_mut(); - let mut buffer = buffer.get_mut(key.as_ref()).unwrap(); + let time_list = pipeline.set_current(time, true, 3); time_list.map(|time_list| time_list.into_iter().map(|t| (t, None)).collect()) } -} -macro_rules! match_in_macro { - ($block:ident,$name:literal, $(($branch:path, $t:ty, $color:expr)),+) => { - { - match $block.data_type { - $( - $branch => { - let datetime = Utc.timestamp_opt($block.datetime,0).unwrap(); - let data: $t = $block.into(); - let layer = Layer::grid_render_layer(data, format!($name), $color); - AppMsg::OpenFile((datetime ,layer)) - }, - )+ - _ => AppMsg::Close, - } + fn current_pipeline(&mut self, key: impl AsRef) -> &mut Pipeline { + self.target_pipeline.get_mut(key.as_ref()).unwrap() + } + + fn create_listening_with_thumb( + &self, + key: impl Borrow, + num: usize, + _sender: ComponentSender, + ) -> ( + Vec>, + Vec< + impl FnOnce( + oneshot::Receiver>, + ) -> BoxFuture<'static, ()> + + Send + + 'static + + Sync, + >, + ) { + let mut thumb_senders: Vec> = Vec::new(); + let mut thumb_recivers = Vec::new(); + let mut current_buffer = (*self.buffer).borrow_mut(); + let mut current_buffer = current_buffer.get_mut(key.borrow()).unwrap(); + + for _ in 0..num { + let (tx, rx) = oneshot::channel(); + thumb_recivers.push(rx); + thumb_senders.push(tx); } - }; -} + let mut p = Vec::new(); + for sender in thumb_senders { + let new_sender = _sender.clone(); + let f = move |tx: oneshot::Receiver>| { + Box::pin(async move { + if let Ok(r) = tx.await { + let r = r.unwrap(); + let tex = (&r) + .layer + .render_target() + .lock() + .unwrap() + .as_ref() + .unwrap() + .thumbnail + .clone(); + sender.send(tex.unwrap()).unwrap(); + new_sender + .command_sender() + .emit(AppCommand::TestBuffer((format!("DBZ"), r))); + } + }) as BoxFuture<'static, ()> + }; + p.push(f); + } + (thumb_recivers, p) + } -fn data_to_grid_layer(block: Block) -> AppMsg { - use crate::utils::*; - use radarg_plugin_interface::PluginResultType; - match block.shape { - DataShape::Matrix => match_in_macro!( - block, - "DBZ", - ( - PluginResultType::DBZ, - Radar2d, - create_dbz_boundarynorm() - ), - (PluginResultType::R, Radar2d, create_dbz_boundarynorm()), - (PluginResultType::V, Radar2d, create_vel_boundarynorm()), - ( - PluginResultType::ZDR, - Radar2d, - create_zdr_boundarynorm() - ), - ( - PluginResultType::PHIDP, - Radar2d, - create_phidp_boundarynorm() - ), - ( - PluginResultType::KDP, - Radar2d, - create_kdp_boundarynorm() - ), - (PluginResultType::CC, Radar2d, create_cc_boundarynorm()), - ( - PluginResultType::HCA, - Radar2d, - create_cpc_boundarynorm() - ), - ( - PluginResultType::QPE, - Radar2d, - create_vil_boundarynorm() - ), - ( - PluginResultType::QPF, - Radar2d, - create_vil_boundarynorm() - ), - ( - PluginResultType::VIL, - Radar2d, - create_vil_boundarynorm() - ), - ( - PluginResultType::OHP, - Radar2d, - create_vil_boundarynorm() - ), - ( - PluginResultType::THP, - Radar2d, - create_vil_boundarynorm() - ), - (PluginResultType::ET, Radar2d, create_et_boundarynorm()), - ( - PluginResultType::EB, - Radar2d, - create_hgt_boundarynorm() - ) - ), - _ => AppMsg::Close, + fn open_file(path: impl AsRef) -> Option<(DateTime, Layer)> { + 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 mut block = result.blocks.pop().unwrap(); + data_to_layer(block) } } diff --git a/src/components/control_panel/control_panel.rs b/src/components/control_panel/control_panel.rs index 1783567..de190f8 100644 --- a/src/components/control_panel/control_panel.rs +++ b/src/components/control_panel/control_panel.rs @@ -20,6 +20,8 @@ use std::path::PathBuf; #[tracker::track] #[derive(Debug)] pub struct ControlPanelModel { + timeline_enabled: bool, + enabled: bool, timeline_start: DateTime, selection: Option>, #[tracker::no_eq] @@ -71,6 +73,8 @@ impl SimpleComponent for ControlPanelModel { set_spacing:10, gtk::Button{ set_icon_name: "rewind-filled", + #[track = "model.changed(ControlPanelModel::enabled())"] + set_sensitive: model.enabled, connect_clicked[sender] => move |_| { sender.input(ControlPanelInputMsg::SelectionRewind); }, @@ -80,6 +84,8 @@ impl SimpleComponent for ControlPanelModel { }, gtk::Button{ set_icon_name: "fast-forward-filled", + #[track = "model.changed(ControlPanelModel::enabled())"] + set_sensitive: model.enabled, connect_clicked[sender] => move |_| { sender.input(ControlPanelInputMsg::SelectionFastForward); }, @@ -209,6 +215,8 @@ impl SimpleComponent for ControlPanelModel { let timeline_start = Utc::now(); let model = ControlPanelModel { + timeline_enabled: true, + enabled: true, selection: None, timeline_start, list_img_wrapper, @@ -231,6 +239,8 @@ impl SimpleComponent for ControlPanelModel { TimelineMsg::SetStart(time) => { self.set_timeline_start(time); } + TimelineMsg::Disable => {} + TimelineMsg::Enable => {} }, ControlPanelInputMsg::Selection(selection) => { self.set_selection(selection); @@ -264,6 +274,14 @@ impl SimpleComponent for ControlPanelModel { } ControlPanelInputMsg::SetThumbByDate((time, thumb)) => {} + + ControlPanelInputMsg::Disable => { + self.set_enabled(false); + } + + ControlPanelInputMsg::Enable => { + self.set_enabled(true); + } } } } diff --git a/src/components/control_panel/messages.rs b/src/components/control_panel/messages.rs index 60eaf9d..a5b4e2a 100644 --- a/src/components/control_panel/messages.rs +++ b/src/components/control_panel/messages.rs @@ -6,6 +6,8 @@ pub enum TimelineMsg { Rewind(Duration), FastForward(Duration), SetStart(DateTime), + Disable, + Enable, } #[derive(Debug)] @@ -22,6 +24,8 @@ pub enum ControlPanelInputMsg { SetThumbByDate((DateTime, Option)), SelectionRewind, SelectionFastForward, + Disable, + Enable, } #[derive(Debug)] diff --git a/src/components/control_panel/thumbnail.rs b/src/components/control_panel/thumbnail.rs index 1c640db..9568b5f 100644 --- a/src/components/control_panel/thumbnail.rs +++ b/src/components/control_panel/thumbnail.rs @@ -63,9 +63,13 @@ impl RelmListItem for ImgItem { let ltex = self.img.clone(); let new_img = img.clone(); self.handle = Some(relm4::spawn_local(async move { - let tex = new_rx.await.unwrap(); - ltex.lock().unwrap().replace(tex); - new_img.set_paintable(ltex.lock().unwrap().as_ref()); + let tex = new_rx.await; + if let Ok(tex) = tex { + ltex.lock().unwrap().replace(tex); + new_img.set_paintable(ltex.lock().unwrap().as_ref()); + } else { + eprintln!("Failed to get texture"); + } })); } let ltex = self.img.lock().unwrap(); diff --git a/src/components/monitor/messages.rs b/src/components/monitor/messages.rs index 70a3dcb..7973b26 100644 --- a/src/components/monitor/messages.rs +++ b/src/components/monitor/messages.rs @@ -4,8 +4,8 @@ use crate::widgets::render::Layer; pub enum MonitorInputMsg { AddLayer(Layer), - RemoveLayer(usize), - UpdateLayer((usize, Box)), + RemoveLayer(String), + UpdateLayer((String, Box)), None, } @@ -25,6 +25,7 @@ pub enum MonitorOutputMsg { LayerAdded(usize), LayerRemoved(usize), LayerUpdated(usize), + LayerRenderFinished, } #[derive(Debug)] diff --git a/src/components/monitor/monitor.rs b/src/components/monitor/monitor.rs index f0ab70e..9f3bace 100644 --- a/src/components/monitor/monitor.rs +++ b/src/components/monitor/monitor.rs @@ -6,7 +6,10 @@ use crate::{ widgets::dynamic_col::DynamicCol, widgets::render::{Layer, Render}, }; -use glib::clone; +use glib::{clone, PropertyGet}; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; use std::sync::{Arc, Mutex}; use super::sidebar::{sidebar::SideBarModel, Msg, SideBarOutputMsg}; @@ -24,8 +27,9 @@ pub struct MonitorModel { render_range: (f64, f64, f64, f64), sidebar_open: bool, sidebar_width: i32, + new_layer: i8, #[no_eq] - layers: Vec, + layers: Rc>>, #[no_eq] sidebar: Controller, } @@ -65,12 +69,15 @@ impl Component for MonitorModel { gtk::Overlay{ #[wrap(Some)] set_child = &Render{ - #[watch] - set_interior_layers: model.layers.clone(), + #[track = "model.changed(MonitorModel::new_layer())"] + set_interior_layers: model.layers.borrow().values().cloned().collect(), #[track = "model.changed(MonitorModel::render_cfg())"] set_cfg: model.render_cfg, #[track = "model.changed(MonitorModel::render_range())"] set_view: model.render_range, + connect_render_status_notify[sender] => move |r| { + sender.output(MonitorOutputMsg::LayerRenderFinished); + } }, add_overlay=>k::Button{ set_label:"Add", @@ -92,55 +99,45 @@ impl Component for MonitorModel { fn update(&mut self, message: Self::Input, sender: ComponentSender, root: &Self::Root) { match message { MonitorInputMsg::AddLayer(layer) => { - // let mut canvas = OFFSCREEN.lock().unwrap(); - if layer.get_imp().is_some() { + let need_prepare = { layer.get_prepare().lock().unwrap().is_some() }; + if need_prepare { sender.oneshot_command(async move { let mut back = OffscreenRenderer::new(3000, 3000).unwrap(); let canvas = back.create_canvas(); - // let new_canvas = OffscreenRenderer::new().unwrap(); let f = { let p = layer.get_prepare(); let mut _p = p.lock().unwrap(); - _p.take() + _p.take().unwrap() }; - let target = if let Some(f) = f { - let imp = layer.get_imp().unwrap(); - - let map: Mapper = Mercator::default().into(); - let cms = CMS::new(map, (3000.0, 3000.0)); - let canvas = Arc::new(Mutex::new(canvas)); - let c = f(imp, canvas, cms); - Some(c) - } else { - None - }; - - if let Some(target) = target { - layer.set_render_target(target); - } + let imp = layer.get_imp().unwrap(); + let map: Mapper = Mercator::default().into(); + let cms = CMS::new(map, (3000.0, 3000.0)); + let canvas = Arc::new(Mutex::new(canvas)); + let target = f(imp, canvas, cms); + layer.set_render_target(target); MonitorCommand::NewLayer(layer) }); } else { - self.layers.push(layer); + self.layers + .borrow_mut() + .get_mut(layer.name.as_str()) + .map(|v| *v = layer); sender .output_sender() .send(MonitorOutputMsg::LayerAdded(0)) .unwrap(); - - self.sidebar - .sender() - .send(Msg::RefreshList(self.layers.clone())); + self.sidebar.sender().send(Msg::RefreshList); } } - MonitorInputMsg::RemoveLayer(index) => { - self.layers.remove(index); + MonitorInputMsg::RemoveLayer(k) => { + self.layers.borrow_mut().remove(&k); sender .output_sender() .send(MonitorOutputMsg::LayerRemoved(0)) .unwrap(); } - MonitorInputMsg::UpdateLayer((idx, f)) => { - f(&mut self.layers[idx]); + MonitorInputMsg::UpdateLayer((k, f)) => { + f(&mut (*self.layers.borrow_mut().get_mut(&k).unwrap())); sender .output_sender() .send(MonitorOutputMsg::LayerUpdated(0)) @@ -155,13 +152,13 @@ impl Component for MonitorModel { root: &Self::Root, sender: ComponentSender, ) -> ComponentParts { - let sidebar: Controller = - SideBarModel::builder() - .launch(()) - .forward(sender.input_sender(), |msg| match msg { - SideBarOutputMsg::NewLayer(layer) => MonitorInputMsg::AddLayer(layer), - _ => MonitorInputMsg::None, - }); + let layers = Rc::new(RefCell::new(HashMap::new())); + let sidebar: Controller = SideBarModel::builder() + .launch(layers.clone()) + .forward(sender.input_sender(), |msg| match msg { + SideBarOutputMsg::NewLayer(layer) => MonitorInputMsg::AddLayer(layer), + _ => MonitorInputMsg::None, + }); let render_cfg = RenderConfig { padding: [20.0, 40.0, 20.0, 40.0], @@ -169,10 +166,11 @@ impl Component for MonitorModel { let model = MonitorModel { render_range: (4.0, 53.3, 73.3, 135.0), + new_layer: 0, render_cfg, sidebar_open: true, sidebar_width: 400, - layers: vec![], + layers: layers, sidebar, tracker: 0, }; @@ -190,12 +188,12 @@ impl Component for MonitorModel { self.reset(); match msg { MonitorCommand::NewLayer(layer) => { - self.layers.push(layer); + // self.layers.push(layer); + self.layers.borrow_mut().insert(layer.name.clone(), layer); self.set_render_range((29.13, 30.16, 119.53, 121.13)); - self.sidebar - .sender() - .send(Msg::RefreshList(self.layers.clone())) - .unwrap(); + self.sidebar.sender().send(Msg::RefreshList).unwrap(); + let raw_id = self.get_new_layer(); + self.set_new_layer(*raw_id + 1); } _ => {} } diff --git a/src/components/monitor/sidebar/sidebar.rs b/src/components/monitor/sidebar/sidebar.rs index f8df41a..d4c36c4 100644 --- a/src/components/monitor/sidebar/sidebar.rs +++ b/src/components/monitor/sidebar/sidebar.rs @@ -1,3 +1,6 @@ +use std::{cell::RefCell, collections::HashMap, rc::Rc}; + +use abi_stable::type_level::trait_marker::Hash; use glib::clone; use gtk::prelude::WidgetExt; use gtk::prelude::*; @@ -17,6 +20,7 @@ use crate::{ use super::bottom_bar::BottomBarModel; pub struct SideBarModel { + layers: Rc>>, counter: u8, list_view_wrapper: TypedListView, bottom_bar_vec: FactoryVecDeque, @@ -24,7 +28,7 @@ pub struct SideBarModel { #[derive(Debug)] pub enum Msg { - RefreshList(Vec), + RefreshList, None, } @@ -35,7 +39,7 @@ pub enum SideBarOutputMsg { #[relm4::component(pub)] impl SimpleComponent for SideBarModel { - type Init = (); + type Init = Rc>>; type Output = SideBarOutputMsg; type Input = Msg; @@ -121,6 +125,7 @@ impl SimpleComponent for SideBarModel { } let model = SideBarModel { + layers: init, counter: 0, list_view_wrapper, bottom_bar_vec, @@ -149,13 +154,16 @@ impl SimpleComponent for SideBarModel { fn update(&mut self, message: Self::Input, sender: ComponentSender) { match message { - Msg::RefreshList(layers) => { - for layer in layers { - self.list_view_wrapper - .append(LayerItem::new(layer.name, true)); - } + Msg::RefreshList => { + let mut list = self + .layers + .borrow() + .iter() + .map(|(k, v)| LayerItem::new(k.clone(), v.visiable)) + .collect::>(); + self.list_view_wrapper.clear(); + self.list_view_wrapper.extend_from_iter(list); } - _ => {} } } @@ -181,12 +189,6 @@ struct Widgets { button: gtk::CheckButton, } -impl Drop for Widgets { - fn drop(&mut self) { - dbg!(self.label.label()); - } -} - impl RelmListItem for LayerItem { type Root = gtk::Box; type Widgets = Widgets; diff --git a/src/main.rs b/src/main.rs index 04539c4..fdc12cc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,7 @@ +#![feature(step_trait)] #![allow(unused)] #![allow(dead_code)] +#[macro_use] mod utils; use config::Config; use gtk::gio; @@ -17,6 +19,8 @@ mod plugin_system; use components::app::AppModel; use once_cell::sync::Lazy; use std::sync::Mutex; +use tracing::info; +use tracing_subscriber; mod widgets; const APP_ID: &str = "org.tsuki.radar_g"; @@ -30,6 +34,7 @@ static PLUGIN_MANAGER: Lazy = Lazy::new(|| PluginManager::new().u fn main() { // Load GL pointers from epoxy (GL context management library used by GTK). + tracing_subscriber::fmt::init(); { #[cfg(target_os = "macos")] let library = @@ -50,9 +55,9 @@ fn main() { .unwrap_or(ptr::null()) }); } - let relm = relm4::RelmApp::new(APP_ID); initialize_custom_css(); + info!("Init plugin system"); let pluginmanager = PluginManager::new(); relm.run::(()); } diff --git a/src/pipeline/offscreen_renderer.rs b/src/pipeline/offscreen_renderer.rs index e9e959d..9651f48 100644 --- a/src/pipeline/offscreen_renderer.rs +++ b/src/pipeline/offscreen_renderer.rs @@ -31,7 +31,9 @@ impl OffscreenRenderer { let descriptor = device.create_context_descriptor(&surfman::ContextAttributes { version: surfman::GLVersion::new(4, 1), - flags: ContextAttributeFlags::empty(), + flags: ContextAttributeFlags::ALPHA + .union(ContextAttributeFlags::DEPTH) + .union(ContextAttributeFlags::STENCIL), })?; let mut context = device.create_context(&descriptor, None)?; diff --git a/src/pipeline/utils.rs b/src/pipeline/utils.rs index afec865..dfdf995 100644 --- a/src/pipeline/utils.rs +++ b/src/pipeline/utils.rs @@ -1,10 +1,12 @@ use super::render_pipeline::RenderResult; use super::{offscreen_renderer::OffscreenRenderer, pool::DataPool}; +use crate::components::app::Buffer; use crate::coords::proj::Mercator; use crate::coords::Mapper; use crate::widgets::{Render, Target, TargetType, CMS}; use crate::{data::Radar2d, errors::RenderError, widgets::Layer, PLUGIN_MANAGER}; use chrono::{prelude::*, Duration}; +use euclid::approxord::max; use futures::future::*; use radarg_plugin_interface::*; use regex::Regex; @@ -32,10 +34,9 @@ pub fn ck() { type RenderR = Result; pub struct Pipeline { - pool: Option>>, - // switcher: Pool>, + pool: Vec>, results: SmallVec<[RenderResult; 20]>, - dispatcher: Option>>, + dispatcher: Option>>, handlers: Option>>, handler: Option>, sender: Option>, @@ -45,8 +46,7 @@ pub struct Pipeline { impl Pipeline { pub fn new(len: usize, key: String) -> Self { Self { - pool: Some(Vec::new()), - // switcher: Pool::new(len), + pool: Vec::new(), results: SmallVec::new(), dispatcher: None, handlers: None, @@ -56,7 +56,7 @@ impl Pipeline { } } - pub fn set_dispatcher(&mut self, dispatcher: Arc>) { + pub fn set_dispatcher(&mut self, dispatcher: Rc>) { self.dispatcher = Some(dispatcher); } @@ -68,10 +68,11 @@ impl Pipeline { &mut self, current_time: DateTime, check_existed: bool, + max_retry_time: usize, ) -> Option>> { let dispatcher = self.dispatcher.clone().unwrap(); - let dispatcher = dispatcher.lock().unwrap(); - let paths = dispatcher.get_path(&self.key, current_time, check_existed); + let dispatcher = dispatcher.borrow_mut(); + let paths = dispatcher.get_path(&self.key, current_time, check_existed, max_retry_time); if let Some(paths) = paths { let mut recvs = Vec::new(); @@ -92,7 +93,8 @@ impl Pipeline { } pub fn work_num(&self) -> usize { - self.pool.as_ref().unwrap().len() + // self.pool.as_ref().unwrap().len() + self.pool.len() } fn worker( @@ -149,14 +151,15 @@ impl Pipeline { tx.send(data).unwrap(); }; - self.pool.as_mut().unwrap().push(Box::pin(future)); + self.pool.push(Box::pin(future)); } - pub fn run(&mut self) -> BoxFuture<'static, ()> { - let pool = self.get_pool().unwrap(); + pub fn run(value: &mut Self) -> BoxFuture<'static, ()> { + let pool = value.get_pool(); Box::pin(async move { for f in pool.into_iter() { - f.await; + task::spawn(f); + // f.await; } }) } @@ -181,13 +184,17 @@ impl Pipeline { let mut handler = self.handlers.take().unwrap(); Box::pin(async move { for (h, f) in handler.into_iter().zip(f) { - f(h).await; + task::spawn(f(h)); + // f(h).await; } }) } - pub fn get_pool(&mut self) -> Option>> { - self.pool.take() + pub fn get_pool(&mut self) -> Vec> { + // self.pool.clone() + use std::mem::replace; + let pool = replace(&mut self.pool, Vec::new()); + pool } pub fn cancel_task(&mut self, timestamp: i64) {} @@ -199,7 +206,7 @@ pub struct Dispatcher { fore_len: usize, back_len: usize, step: Duration, - registered_buffer: Rc, Option)>>>>, + registered_buffer: Buffer, } impl Dispatcher { @@ -207,7 +214,7 @@ impl Dispatcher { fore_len: usize, back_len: usize, step: Duration, - registered_buffer: Rc, Option)>>>>, + registered_buffer: Buffer, ) -> Self { Self { datetime: Utc::now(), @@ -239,11 +246,47 @@ impl Dispatcher { self.back_len = back_len; } + pub fn get_single_path( + &self, + name: &str, + current_time: DateTime, + check_existed: bool, + ) -> Option { + let datetime_format: regex::Regex = + Regex::new(r"(?:%[YHMSmd](?:[-/:_]?%[YHMSmd])*)").unwrap(); + let c = self.path_format.get(name).map(|s| { + let path = s.clone(); + let need_formated = datetime_format.captures_iter(&path).collect::>(); + let mut result_path = path.clone(); + + for need_format in need_formated.iter() { + let fmt = need_format.get(0).unwrap().as_str(); + let t = current_time.format(fmt).to_string(); + result_path = result_path.replace(fmt, &t); + } + result_path + }); + if let Some(c) = c { + if check_existed { + if std::path::Path::new(&c).exists() { + Some(c) + } else { + None + } + } else { + Some(c) + } + } else { + None + } + } + pub fn get_path( &self, name: &str, current_time: DateTime, check_existed: bool, + mut max_retry_time: usize, ) -> Option)>> { let datetime_format: regex::Regex = Regex::new(r"(?:%[YHMSmd](?:[-/:_]?%[YHMSmd])*)").unwrap(); @@ -258,10 +301,11 @@ impl Dispatcher { while fore > 0 { let mut result_path = path.clone(); let t = current_time - self.step * fore as i32; - if buffer.iter().position(|(dt, _)| *dt == t).is_some() { + if buffer.get(&t).is_some() { fore = fore - 1; continue; } + for need_format in need_formated.iter() { let fmt = need_format.get(0).unwrap().as_str(); let t = t.format(fmt).to_string(); @@ -269,7 +313,11 @@ impl Dispatcher { } if check_existed { + if max_retry_time == 0 { + break; + } if !std::path::Path::new(&result_path).exists() { + max_retry_time = max_retry_time - 1; continue; } else { result_paths.push((result_path.clone(), t)); @@ -283,7 +331,8 @@ impl Dispatcher { while back < self.back_len { let mut result_path = path.clone(); let t = current_time + self.step * back as i32; - if buffer.iter().position(|(dt, _)| *dt == t).is_some() { + + if buffer.get(&t).is_some() { back = back + 1; continue; } @@ -294,7 +343,11 @@ impl Dispatcher { } if check_existed { + if max_retry_time == 0 { + break; + } if !std::path::Path::new(&result_path).exists() { + max_retry_time = max_retry_time - 1; continue; } else { result_paths.push((result_path.clone(), t)); @@ -313,12 +366,13 @@ impl Dispatcher { macro_rules! match_in_macro { ($block:ident,$name:literal, $(($branch:path, $t:ty, $color:expr)),+) => { { + let datetime = Utc.timestamp_opt($block.datetime, 0).unwrap(); match $block.data_type { $( $branch => { let data: $t = $block.into(); let layer = Layer::grid_render_layer(data, format!($name), $color); - Some((Utc::now() ,layer)) + Some(( datetime ,layer)) }, )+ _ => None @@ -328,7 +382,7 @@ macro_rules! match_in_macro { }; } -fn data_to_layer(block: Block) -> Option<(DateTime, Layer)> { +pub fn data_to_layer(block: Block) -> Option<(DateTime, Layer)> { use crate::utils::*; use radarg_plugin_interface::PluginResultType; match block.shape { diff --git a/src/render/imp.rs b/src/render/imp.rs deleted file mode 100644 index b25978e..0000000 --- a/src/render/imp.rs +++ /dev/null @@ -1,317 +0,0 @@ -use super::exterior::ExteriorWidget; -use super::interior::InteriorWidget; -use super::{Layer, WindowCoord}; -use crate::coords::proj::Mercator; -use crate::coords::Mapper; -use femtovg::{Canvas, Color, FontId, Renderer}; -use gtk::glib; -use gtk::subclass::prelude::*; -use gtk::traits::{GLAreaExt, WidgetExt}; -use std::cell::RefCell; -use std::num::NonZeroU32; - -#[derive(Debug, Default, Clone, Copy, PartialEq)] -pub struct RenderConfig { - pub padding: [f32; 4], -} - -#[derive(Debug, Clone)] -pub enum RenderMotion { - Translate, - Scale, - None, -} - -impl Default for RenderMotion { - fn default() -> Self { - Self::None - } -} - -#[derive(Default)] -pub struct RenderStatus { - pub(super) window_size: Option<(i32, i32)>, - pub(super) scale_rate: Option, - pub(super) pointer_location: WindowCoord, - pub(super) motion: RenderMotion, - pub(super) scale: f32, - pub(super) translate: Option<(f32, f32)>, - pub(super) view_range: Option<((f64, f64), (f64, f64))>, - translation: Option<(f64, f64)>, - init_translation: (f64, f64), -} - -pub struct Render { - pub(super) exterior: RefCell, - pub(super) interior: RefCell, - pub(super) canvas: RefCell>>, - pub config: RefCell, - pub status: RefCell, - pub mapper: RefCell, - pub(super) interior_layers: RefCell>, -} - -impl Default for Render { - fn default() -> Self { - Self { - exterior: RefCell::new(ExteriorWidget::default()), - interior: RefCell::new(InteriorWidget::default()), - interior_layers: RefCell::new(Vec::new()), - config: RefCell::new(RenderConfig::default()), - status: RefCell::new(RenderStatus::default()), - mapper: RefCell::new(Mercator::default().into()), - canvas: RefCell::new(None), - } - } -} - -#[glib::object_subclass] -impl ObjectSubclass for Render { - const NAME: &'static str = "Render"; - type Type = super::Render; - type ParentType = gtk::GLArea; -} - -impl ObjectImpl for Render { - fn constructed(&self) { - self.parent_constructed(); - let area = self.obj(); - area.set_has_stencil_buffer(true); - } -} - -impl WidgetImpl for Render {} - -impl GLAreaImpl for Render { - fn resize(&self, width: i32, height: i32) { - self.status.borrow_mut().window_size = Some((width, height)); - self.ensure_canvas(); - let mut canvas = self.canvas.borrow_mut(); - let canvas = canvas.as_mut().unwrap(); - canvas.set_size( - width as u32, - height as u32, - self.obj().scale_factor() as f32, - ); - - let translation = self.status.borrow().translation; - let scale_rate = self.status.borrow().scale_rate; - let mapper = self.mapper.borrow(); - - if let None = translation { - let mut status = self.status.borrow_mut(); - status.translation = Some((mapper.get_bounds().0, mapper.get_bounds().2)); - status.init_translation = (mapper.get_bounds().0, mapper.get_bounds().2); - } - - if let None = scale_rate { - let scale = (mapper.get_bounds().3 - mapper.get_bounds().2) / canvas.height() as f64; - self.status.borrow_mut().scale_rate = Some(scale); - } - } - - fn render(&self, context: >k::gdk::GLContext) -> bool { - self.ensure_canvas(); - let configs = self.config.borrow(); - let (w, h) = { - let mut canvas = self.canvas.borrow_mut(); - let canvas = canvas.as_mut().unwrap(); - - let dpi = self.obj().scale_factor(); - let w = canvas.width(); - let h = canvas.height(); - - canvas.clear_rect( - 0, - 0, - (w as i32 * dpi) as u32, - (h as i32 * dpi) as u32, - Color::rgba(0, 0, 0, 255), - ); - (w, h) - }; - - let render_range = self.status.borrow().view_range.clone(); - if let Some(((lat1, lat2), (lon1, lon2))) = render_range { - let mut status = self.status.borrow_mut(); - - let mapper = self.mapper.borrow(); - - let (tx, ty) = mapper.map((lon1, lat1)).unwrap(); - status.translation.replace((tx, ty)); - - let (lon1, lat1) = mapper.map((lon1, lat1)).unwrap(); - let (lon2, lat2) = mapper.map((lon2, lat2)).unwrap(); - - let scale = ((lat1 - lat2).abs() / h as f64).max((lon1 - lon2).abs() / w as f64); - status.scale_rate.replace(scale); - - status.view_range = None; - } else { - let mut status = self.status.borrow_mut(); - match status.motion { - RenderMotion::Translate => { - if let Some((x, y)) = status.translate { - let (ix, iy) = status.init_translation; - status.translation = Some(( - ix + x as f64 * status.scale_rate.unwrap(), - iy + y as f64 * status.scale_rate.unwrap(), - )); - } else { - status.init_translation = status.translation.unwrap(); - } - } - RenderMotion::Scale => { - let scale_rate = status.scale_rate.unwrap(); - let scale_flag = status.scale as f64; - let step = scale_rate * 0.1; - - let (tx, ty) = status.translation.unwrap(); - let (px, py) = status.pointer_location; - - let scaled = scale_rate + scale_flag * step; - status.scale_rate = Some(scaled); - let sx = scale_flag * step * px as f64; - let sy = scale_flag * step * py as f64; - status.translation = Some((tx - sx, ty - sy)); - status.init_translation = status.translation.unwrap(); - } - RenderMotion::None => {} - } - } - - let c = &(*self.interior_layers.borrow()); - self.interior - .borrow() - .draw(c, &self.obj(), self.status.borrow(), configs); - - { - let mut canvas = self.canvas.borrow_mut(); - let canvas = canvas.as_mut().unwrap(); - self.exterior.borrow().draw(canvas, &self.obj()); - canvas.flush(); - } - - true - } -} - -impl Render { - fn ensure_canvas(&self) { - use femtovg::{renderer, Canvas}; - use glow::HasContext; - - if self.canvas.borrow().is_some() { - return; - } - - let widget = self.obj(); - widget.attach_buffers(); - - static LOAD_FN: fn(&str) -> *const std::ffi::c_void = - |s| epoxy::get_proc_addr(s) as *const _; - // SAFETY: Need to get the framebuffer id that gtk expects us to draw into, so femtovg - // knows which framebuffer to bind. This is safe as long as we call attach_buffers - // beforehand. Also unbind it here just in case, since this can be called outside render. - let (mut renderer, fbo) = unsafe { - let renderer = - renderer::OpenGl::new_from_function(LOAD_FN).expect("Cannot create renderer"); - let ctx = glow::Context::from_loader_function(LOAD_FN); - let id = NonZeroU32::new(ctx.get_parameter_i32(glow::DRAW_FRAMEBUFFER_BINDING) as u32) - .expect("No GTK provided framebuffer binding"); - ctx.bind_framebuffer(glow::FRAMEBUFFER, None); - (renderer, glow::NativeFramebuffer(id)) - }; - renderer.set_screen_target(Some(fbo)); - let mut canvas = Canvas::new(renderer).expect("Cannot create canvas"); - canvas - .add_font_dir(std::path::Path::new("./src/assets")) - .unwrap(); - - self.canvas.replace(Some(canvas)); - } - - pub(super) fn window_size(&self) -> Option<(i32, i32)> { - self.status.borrow().window_size - } - - pub(super) fn view_range(&self) -> Option<((f64, f64), (f64, f64))> { - let padding = self.padding(); - let (w, h) = self.window_size().unwrap(); - let (w, h) = ( - w as f32 - padding[1] - padding[3], - h as f32 - padding[0] - padding[2], - ); - let (w, h) = (w as f64, h as f64); - - let mapper = self.mapper.borrow(); - - let status = self.status.borrow(); - status.translation.and_then(|(tx, ty)| { - status.scale_rate.and_then(|scale| { - let (x1, y1) = (tx + padding[3] as f64, ty + padding[2] as f64); - let (x2, y2) = ( - tx + w * scale + padding[3] as f64, - ty + h * scale + padding[2] as f64, - ); - let (x1, y1) = mapper.inverse_map((x1, y1)).unwrap(); - let (x2, y2) = mapper.inverse_map((x2, y2)).unwrap(); - Some(((x1, x2), (y1, y2))) - }) - }) - } - - pub(super) fn inverse_map(&self, loc: (f32, f32)) -> Option<(f64, f64)> { - let (x, y) = loc; - let status = self.status.borrow(); - status.translation.and_then(|(tx, ty)| { - status.scale_rate.and_then(|scale| { - let (x, y) = (x as f64, y as f64); - Some((tx + x * scale, (ty + y * scale))) - }) - }) - } - - fn padding(&self) -> [f32; 4] { - self.config.borrow().padding - } - - pub(super) fn map(&self, loc: (f64, f64)) -> Option<(f32, f32)> { - let (x, y) = loc; - let (_, h) = self.window_size().unwrap(); - let status = self.status.borrow(); - status.translation.and_then(|(tx, ty)| { - status.scale_rate.and_then(|scale| { - Some(( - (x - tx as f64) as f32 / scale as f32, - h as f32 - (y - ty as f64) as f32 / scale as f32, - )) - }) - }) - } - - pub(super) fn set_translation(&self, translation: (f64, f64)) { - let mut status = self.status.borrow_mut(); - status.translation = Some(translation); - } - - fn pointer_loc(&self) -> (f32, f32) { - let (x, y) = self.status.borrow().pointer_location.clone(); - let (_, h) = self.window_size().unwrap(); - (x, h as f32 - y) - } - - pub(super) fn set_view(&self, range: (f64, f64, f64, f64)) { - let (lat1, lat2, lon1, lon2) = range; - self.status - .borrow_mut() - .view_range - .replace(((lat1, lat2), (lon1, lon2))); - // if let Some((w, h)) = self.window_size() { - // println!("w:{}, h:{}", w, h); - // let scale = ((lat1 - lat2).abs() / h as f64).max((lon1 - lon2).abs() / w as f64); - // self.status.borrow_mut().scale_rate.replace(scale); - // self.set_translation((lat1, lon1)); - // } - } -} diff --git a/src/utils.rs b/src/utils.rs index c34cbbb..c993281 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -89,6 +89,7 @@ pub fn create_dbz_boundarynorm() -> BoundaryNorm { Color::rgb(139, 0, 255), ], true, + -125, ) } @@ -115,6 +116,7 @@ pub fn create_vel_boundarynorm() -> BoundaryNorm { Color::rgb(255, 0, 0), ], true, + -125.0, ) } @@ -142,6 +144,7 @@ pub fn create_phidp_boundarynorm() -> BoundaryNorm { Color::rgb(193, 0, 0), ], true, + -125.0, ) } @@ -166,6 +169,7 @@ pub fn create_zdr_boundarynorm() -> BoundaryNorm { Color::rgb(0, 192, 39), ], true, + -125.0, ) } @@ -192,6 +196,7 @@ pub fn create_cc_boundarynorm() -> BoundaryNorm { Color::rgb(193, 0, 0), ], true, + -125.0, ) } @@ -217,6 +222,7 @@ pub fn create_vil_boundarynorm() -> BoundaryNorm { Color::rgb(255, 255, 255), ], true, + -125.0, ) } @@ -242,6 +248,7 @@ pub fn create_hgt_boundarynorm() -> BoundaryNorm { Color::rgb(255, 255, 255), ], true, + -125.0, ) } @@ -272,6 +279,7 @@ pub fn create_et_boundarynorm() -> BoundaryNorm { Color::rgb(139, 0, 0), ], true, + -125.0, ) } @@ -292,6 +300,7 @@ pub fn create_cpc_boundarynorm() -> BoundaryNorm { Color::rgb(187, 165, 204), ], true, + -125, ) } @@ -319,5 +328,6 @@ pub fn create_kdp_boundarynorm() -> BoundaryNorm { Color::rgb(253, 6, 253), ], true, + -125.0, ) } diff --git a/src/widgets/render/imp.rs b/src/widgets/render/imp.rs index b25978e..044488d 100644 --- a/src/widgets/render/imp.rs +++ b/src/widgets/render/imp.rs @@ -4,10 +4,10 @@ use super::{Layer, WindowCoord}; use crate::coords::proj::Mercator; use crate::coords::Mapper; use femtovg::{Canvas, Color, FontId, Renderer}; -use gtk::glib; +use gtk::glib::{self, prelude::*, Properties}; use gtk::subclass::prelude::*; use gtk::traits::{GLAreaExt, WidgetExt}; -use std::cell::RefCell; +use std::cell::{Cell, RefCell}; use std::num::NonZeroU32; #[derive(Debug, Default, Clone, Copy, PartialEq)] @@ -41,7 +41,11 @@ pub struct RenderStatus { init_translation: (f64, f64), } +#[derive(Properties)] +#[properties(wrapper_type = super::Render)] pub struct Render { + #[property(get, set)] + render_status: Cell, pub(super) exterior: RefCell, pub(super) interior: RefCell, pub(super) canvas: RefCell>>, @@ -54,6 +58,7 @@ pub struct Render { impl Default for Render { fn default() -> Self { Self { + render_status: Cell::new(0), exterior: RefCell::new(ExteriorWidget::default()), interior: RefCell::new(InteriorWidget::default()), interior_layers: RefCell::new(Vec::new()), @@ -78,6 +83,18 @@ impl ObjectImpl for Render { let area = self.obj(); area.set_has_stencil_buffer(true); } + + fn properties() -> &'static [glib::ParamSpec] { + Self::derived_properties() + } + + fn set_property(&self, _id: usize, _value: &glib::Value, _pspec: &glib::ParamSpec) { + Self::derived_set_property(&self, _id, _value, _pspec) + } + + fn property(&self, _id: usize, _pspec: &glib::ParamSpec) -> glib::Value { + Self::derived_property(&self, _id, _pspec) + } } impl WidgetImpl for Render {} @@ -139,6 +156,7 @@ impl GLAreaImpl for Render { let (tx, ty) = mapper.map((lon1, lat1)).unwrap(); status.translation.replace((tx, ty)); + status.init_translation = (tx, ty); let (lon1, lat1) = mapper.map((lon1, lat1)).unwrap(); let (lon2, lat2) = mapper.map((lon2, lat2)).unwrap(); @@ -191,7 +209,6 @@ impl GLAreaImpl for Render { self.exterior.borrow().draw(canvas, &self.obj()); canvas.flush(); } - true } } @@ -307,11 +324,5 @@ impl Render { .borrow_mut() .view_range .replace(((lat1, lat2), (lon1, lon2))); - // if let Some((w, h)) = self.window_size() { - // println!("w:{}, h:{}", w, h); - // let scale = ((lat1 - lat2).abs() / h as f64).max((lon1 - lon2).abs() / w as f64); - // self.status.borrow_mut().scale_rate.replace(scale); - // self.set_translation((lat1, lon1)); - // } } } diff --git a/src/widgets/render/mod.rs b/src/widgets/render/mod.rs index b902436..db2ff22 100644 --- a/src/widgets/render/mod.rs +++ b/src/widgets/render/mod.rs @@ -4,6 +4,7 @@ mod imp; mod interior; pub mod predefined; mod renders; +pub mod widget; pub use self::cms::CMS; pub use self::imp::{RenderConfig, RenderMotion, RenderStatus}; use crate::coords::Mapper; @@ -126,6 +127,7 @@ impl Render { pub fn set_interior_layers(&self, layers: Vec) { self.imp().interior_layers.replace(layers); self.queue_render(); + self.set_render_status(0); } pub fn map(&self, loc: (f64, f64)) -> Option<(f32, f32)> { diff --git a/src/widgets/render/predefined/color_mapper.rs b/src/widgets/render/predefined/color_mapper.rs index 47a86ad..b1eee0b 100644 --- a/src/widgets/render/predefined/color_mapper.rs +++ b/src/widgets/render/predefined/color_mapper.rs @@ -2,8 +2,18 @@ use std::fmt::Debug; use femtovg::Color; use num_traits::NumOps; -pub trait ColorMapper : Debug + Send + Sync { +pub trait ColorMapper: Debug + Send + Sync { fn map_value_to_color(&self, value: T, invalid_value: T) -> Option; + // fn map_min_to_max(&self, min: T, max: T, invalid_value: T, step: usize) -> Vec { + // let mut colors = Vec::new(); + // for i in (min..=max).step_by(step) { + // colors.push(self.map_value_to_color(i, invalid_value).unwrap()); + // } + // colors + // } + + fn min_max(&self) -> (T, T); + fn invalid(&self) -> T; } #[derive(Debug)] @@ -11,6 +21,7 @@ pub struct BoundaryNorm { boundaries: Vec, extrand: bool, colors: Vec, + invalid_value: T, } impl Default for BoundaryNorm { @@ -35,17 +46,24 @@ impl Default for BoundaryNorm { Color::rgb(212, 142, 254), Color::rgb(170, 36, 250), ], + invalid_value: -125, } } } impl BoundaryNorm { - pub fn new(boundaries: Vec, colors: Vec, extrand: bool) -> Self { + pub fn new( + boundaries: Vec, + colors: Vec, + extrand: bool, + invalid_value: T, + ) -> Self { // assert_eq!(boundaries.len(), colors.len() + 1); BoundaryNorm { boundaries, extrand, colors, + invalid_value, } } @@ -69,4 +87,15 @@ impl ColorMapper for BoundaryNo fn map_value_to_color(&self, value: T, invalid_value: T) -> Option { self.map_value_to_color(value, invalid_value) } + + fn min_max(&self) -> (T, T) { + ( + *self.boundaries.first().unwrap(), + *self.boundaries.last().unwrap(), + ) + } + + fn invalid(&self) -> T { + self.invalid_value + } } diff --git a/src/widgets/render/predefined/mod.rs b/src/widgets/render/predefined/mod.rs index 850b62e..3fd98d9 100644 --- a/src/widgets/render/predefined/mod.rs +++ b/src/widgets/render/predefined/mod.rs @@ -1,4 +1,5 @@ pub mod color_mapper; -pub mod grid_field_renderer; pub mod gis; +pub mod grid_field_renderer; pub mod layers; +pub mod widgets; diff --git a/src/widgets/render/predefined/widgets.rs b/src/widgets/render/predefined/widgets.rs new file mode 100644 index 0000000..2fe55bd --- /dev/null +++ b/src/widgets/render/predefined/widgets.rs @@ -0,0 +1,51 @@ +use femtovg::{Color, Paint, Path}; +use std::iter::Step; + +use super::{super::widget::Widget as WidgetTrait, color_mapper::ColorMapper}; +pub struct ColorBar +where + V: num_traits::NumOps + PartialOrd + Step, + T: ColorMapper, +{ + color_mapper: T, + padding: [f32; 4], + width: Option, + height: Option, + origin: (f32, f32), + phantom: std::marker::PhantomData, +} + +impl WidgetTrait for ColorBar +where + V: num_traits::NumOps + PartialOrd + Step, + T: ColorMapper, +{ + fn render(&self, layer: crate::widgets::Layer, render: crate::widgets::Render) { + let (w, h) = render.window_size(); + let bar_width = self.width.unwrap_or(w as f32) - self.padding[1] - self.padding[3]; + let bar_height = self.height.unwrap_or(10.0); + let (x, y) = self.origin; + + let (l, ll) = self.color_mapper.min_max(); + let invalid = self.color_mapper.invalid(); + let colors = self.color_mapper.map_min_to_max(l, ll, invalid, 100); + + let b_w = w as f32 / 100.0; + + let mut canvas = render.get_canvas(); + + if let Some(canvas) = canvas.as_mut() { + for (i, color) in colors.into_iter().enumerate() { + let x = x + i as f32 * b_w; + let y = y; + let w = b_w; + let h = bar_height; + + let mut path = Path::new(); + path.rect(x, y, w, h); + let paint = Paint::color(color); + canvas.fill_path(&path, &paint); + } + } + } +} diff --git a/src/widgets/render/widget/mod.rs b/src/widgets/render/widget/mod.rs new file mode 100644 index 0000000..31a3368 --- /dev/null +++ b/src/widgets/render/widget/mod.rs @@ -0,0 +1,3 @@ +mod widget; + +pub use widget::Widget; diff --git a/src/widgets/render/widget/widget.rs b/src/widgets/render/widget/widget.rs new file mode 100644 index 0000000..b367029 --- /dev/null +++ b/src/widgets/render/widget/widget.rs @@ -0,0 +1,7 @@ +use crate::widgets::{Layer, Render, CMS}; +use femtovg::{renderer::OpenGl, Canvas}; +use std::rc::Rc; + +pub trait Widget { + fn render(&self, layer: Layer, render: Render); +}