use crate::{ graphics::{ collections::agg_fast_path::{AggFastPath, AggFastPathConfig, Path}, font::{Anchor, FontConfig, LineStyle, PositionText, Text, TextLine}, ppi::{PPIConfig, PPI}, transforms::plane::PlaneTrans, AttaWithBuffer, Graphics, }, pg::SideBarInputMsg, GL, }; use radarg_core::config::Setting; use relm4::{ adw::{self, prelude::*}, gtk::{self, prelude::*}, ComponentParts, SimpleComponent, }; use core::f32; use glow::HasContext; use std::{ cell::{RefCell, RefMut}, rc::Rc, }; use tracker::track; use crate::font_manager::{FontSize, FontStyle}; use crate::pg::layout_type::ViewPort; use crate::ui::operation::{self, Operation}; use crate::{errors::*, font_manager::FontManager, ui::typ, utils::resources::ManagedResource}; use radarg_core::radarg_data::RadarGridData; use super::{Attach, Module, ModuleCursor}; pub struct PPIModule<'b, 'gl: 'b> { gl: &'gl GL, ppi_program: &'b mut PPI, line_program: &'b mut AggFastPath, text_program: &'b mut Text, } impl<'b, 'a: 'b> PPIModule<'b, 'a> { pub fn new( gl: &'a GL, ppi: &'b mut PPI, text: &'b mut Text, line: &'b mut AggFastPath, ) -> Self { let config = PPIConfig::default(); Self { gl, ppi_program: ppi, text_program: text, line_program: line, } } fn bind_ppi_pg( &self, attach: &mut Attach, data: &RadarGridData, config: &PPIModuleConfig, ) -> Result<()> { let (vbo, ebo, len) = self .ppi_program .bake(&self.gl, data, &config.to_ppi_config())?; attach.bind_data(&vbo, ebo.as_ref(), len, glow::DYNAMIC_DRAW); Ok(()) } fn bind_line_pg( &self, attach: &mut Attach, data: &RadarGridData, config: &PPIModuleConfig, ) -> Result<()> { let raw_config = config.to_line_config(); // Will be changed in the future let outskirt = 10.0; let mut paths = vec![]; let range_line_num = config.range_line_num; let r = outskirt / range_line_num as f32; let seg_num = 200; let angle = 2f32 * f32::consts::PI / seg_num as f32; for range in 1..=range_line_num { // Create the path let mut path = Path::new(true); let r = r * range as f32; // Draw the circle for seg in 0..seg_num { let angle = angle * seg as f32; let x = (angle.cos() * r) as f32; let y = (angle.sin() * r) as f32; path.push([x, y, 0.01]); } path.finish(); paths.push(path); } let ath_lin_num = config.ath_line_num; let a = 2.0 * f32::consts::PI / ath_lin_num as f32; for _a in 0..=ath_lin_num { let mut path = Path::new(false); let x = (a * _a as f32).cos() * outskirt; let y = (a * _a as f32).sin() * outskirt; path.push([0.0, 0.0, 0.01]); path.push([x, y, 0.01]); path.finish(); paths.push(path); } if config.vertical_axis { let mut path = Path::new(false); path.push([0.0, 0.0, 0.0]); path.push([0.0, 0.0, outskirt]); path.finish(); paths.push(path); } let (vbo, ebo, len) = self.line_program.bake(&self.gl, &paths, &raw_config)?; attach.bind_data(&vbo, ebo.as_ref(), len, glow::STATIC_DRAW); Ok(()) } fn bind_tick( &self, attach: &mut Attach, data: &RadarGridData, config: &PPIModuleConfig, ) -> Result<()> { let font_style = config.to_font_config(); match font_style { FontConfig::Textline(line_style, font_style) => { let new_text = TextLine::new("Hello,World", Some(font_style), None); let position_text = PositionText::new(new_text, [0.0, 0.0, 0.0], Anchor::BottomCenter); let text_pg_config = config.to_font_config(); let (vbo, ebo, len) = self.text_program .bake(&self.gl, &position_text, &text_pg_config)?; attach.bind_data(&vbo, ebo.as_ref(), len, glow::STATIC_DRAW); } } Ok(()) } } impl<'b, 'a: 'b> Module for PPIModule<'b, 'a> { type Cursor = PPIPackage; type Data = RadarGridData; type Operation = PlaneTrans; fn render<'dt>( &mut self, cursor: &mut Self::Cursor, operation: &Operation, viewport: &ViewPort, ) -> Result<()> { // PPI Program let data = &cursor.data; let mut config = &mut cursor.ppi_config.borrow_mut(); // Mount PPI Program self.ppi_program.mount(&self.gl)?; // Deal with the operation operation.attach_with_program(&self.gl, self.ppi_program.program()); let ppi_attach = &mut cursor.ppi_attach; // Update the config self.ppi_program .set_config(&self.gl, &config.to_ppi_config())?; // if the layer is changed, we need to rebind the data if config.changed_layer() { self.bind_ppi_pg(ppi_attach, data, config); } // PPI Draw ppi_attach.bind_self(); self.ppi_program.draw(&self.gl, ppi_attach.len())?; ppi_attach.unbind_self(); // Unmount PPI Program self.ppi_program.unmount(&self.gl)?; if config.changed_range_line_num() || config.changed_ath_line_num() || config.changed_vertical_axis() { self.bind_line_pg(&mut cursor.line_attach, data, config)?; } // Mount Line Program self.line_program.mount(&self.gl)?; // Deal with the operation operation.attach_with_program(&self.gl, self.line_program.program()); // Set the viewport, this is important self.line_program.set_viewport(&self.gl, viewport.size()); // Update the config self.line_program .set_config(&self.gl, &config.to_line_config()); // PPI Tick Draw let attach = &mut cursor.line_attach; attach.bind_self(); self.line_program.draw(&self.gl, attach.len())?; attach.unbind_self(); self.line_program.unmount(&self.gl)?; self.text_program.mount(&self.gl); let tick_attach = &mut cursor.tick_attach; // Deal with the operation operation.attach_with_program(&self.gl, self.text_program.program_mut()); self.text_program.set_viewport(&self.gl, viewport.size()); self.text_program .set_config(&self.gl, &config.to_font_config()); tick_attach.bind_self(); self.text_program.draw(&self.gl, tick_attach.len())?; tick_attach.unbind_self(); config.reset(); Ok(()) } fn load_data<'dt>(&self, data: &Rc, setting: &Setting) -> Result { // Init the memory let (vao, vbo, ebo) = self.ppi_program.init(&self.gl); let mut ppi_attach = Attach::new(self.gl.clone(), vao, vbo, ebo, None); // Get the data info let (r, a, t, max_layer, unvalid) = self.ppi_program.data_info(&data)?; // Find the color map let cmap = setting.find(&t); // Check if the color map is valid if cmap.is_none() { return Err(Error::InvalidDataType); } let cmap = cmap.unwrap(); // Init the memory for the line program let (vao, vbo, ebo) = self.line_program.init(&self.gl); let mut line_attach = Attach::new(self.gl.clone(), vao, vbo, ebo, None); // Tick Label let (vao, vbo, ebo) = self.text_program.init(&self.gl); let mut tick_attach = Attach::new(self.gl.clone(), vao, vbo, ebo, None); let mut config = PPIModuleConfig::default(); config.rdpi = r; config.adpi = a; config.max_layer = max_layer; config.unvalid_value = unvalid; config.colors = cmap.color().unwrap(); config.color_range = cmap.value_range(); // Bind the data self.bind_ppi_pg(&mut ppi_attach, &data, &config); self.bind_line_pg(&mut line_attach, &data, &config); // self.bind_tick(&mut tick_attach, &data.borrow(), &config); let data = data.clone(); Ok(PPIPackage::new( config, ppi_attach, line_attach, tick_attach, data, )) } } #[derive(Debug)] pub struct PPIPackage { draw_helper: bool, ppi_config: Rc>, ppi_attach: Attach, line_attach: Attach, tick_attach: Attach, data: Rc, } impl PPIPackage { fn new( ppi_config: PPIModuleConfig, ppi_attach: Attach, line_attach: Attach, tick_attach: Attach, data: Rc, ) -> Self { Self { draw_helper: true, ppi_config: Rc::new(RefCell::new(ppi_config)), ppi_attach, line_attach, tick_attach, data, } } } #[track] #[derive(PartialEq, Debug, Clone)] pub struct PPIModuleConfig { pub ticks: bool, pub line_color: [f32; 4], pub line_width: f32, pub line_antialias: f32, pub range_line_num: usize, pub ath_line_num: usize, pub vertical_axis: bool, pub layer: usize, pub colors: Vec<[u8; 4]>, pub color_range: [f32; 2], pub is_three_d: bool, pub tick_label_color: [f32; 4], pub tick_label_size: f32, #[do_not_track] pub unvalid_value: f32, #[do_not_track] pub max_layer: usize, #[do_not_track] pub rdpi: f32, #[do_not_track] pub adpi: f32, } impl Default for PPIModuleConfig { fn default() -> Self { Self { ticks: true, line_color: [1.0, 1.0, 1.0, 1.0], line_width: 1.5, line_antialias: 0.5, layer: 0, vertical_axis: false, range_line_num: 5, ath_line_num: 6, colors: vec![], color_range: [0.0, 0.0], is_three_d: true, unvalid_value: 0.0, tick_label_color: [1.0, 1.0, 1.0, 1.0], tick_label_size: 24.0, max_layer: 0, rdpi: 0.0, adpi: 0.0, tracker: 0, } } } impl PPIModuleConfig { fn to_ppi_config(&self) -> PPIConfig { PPIConfig { unvalid_value: self.unvalid_value, color_range: self.color_range, colors: self.colors.clone(), layer: self.layer, rdpi: self.rdpi, adpi: self.adpi, three_d: self.is_three_d, } } fn to_line_config(&self) -> AggFastPathConfig { AggFastPathConfig { color: self.line_color, linewidth: self.line_width, antialias: self.line_antialias, } } fn to_font_config(&self) -> FontConfig { let line_style = LineStyle::default(); let mut font_style = FontStyle::default(); font_style.size = FontSize::Absolute(self.tick_label_size); font_style.color = self.tick_label_color; FontConfig::Textline(line_style, font_style) } } pub struct PPIModuleConfigComponent { config: Rc>, } #[derive(Debug)] pub enum OutputMsg { Refresh, } #[relm4::component(pub)] impl SimpleComponent for PPIModuleConfigComponent { type Widgets = PPIModuleConfigComponentWidgets; type Init = Rc>; type Input = (); type Output = OutputMsg; view! { adw::PreferencesGroup { set_title:"PPI Config", set_hexpand:true, set_vexpand:true, adw::SwitchRow { set_title:"Ticks", set_active: init_config.ticks, connect_active_notify[sender, config_ref] => move |this| { let active = this.is_active(); config_ref.borrow_mut().set_ticks(active); } }, adw::SpinRow { set_title: "Layer", set_value: init_config.layer as f64, set_range: (0.0, init_config.max_layer as f64), set_digits: 0, set_numeric: true, set_climb_rate: 1.0, connect_value_notify[sender, config_ref] => move |this| { let layer = this.value() as usize; config_ref.borrow_mut().set_layer(layer); } }, gtk::Button { set_label: "Refresh", connect_clicked[sender] => move |_| { sender.output(OutputMsg::Refresh); } } } } fn init( init: Self::Init, root: Self::Root, sender: relm4::ComponentSender, ) -> relm4::ComponentParts { let model = PPIModuleConfigComponent { config: init.clone(), }; let init_config = init.borrow().clone(); let config_ref = model.config.clone(); let widgets = view_output!(); ComponentParts { model, widgets } } fn update(&mut self, message: Self::Input, sender: relm4::ComponentSender) {} } impl ModuleCursor for PPIPackage { type Module<'rf, 'gl: 'rf> = PPIModule<'rf, 'gl>; type Config = PPIModuleConfig; type Data = RadarGridData; type Component = PPIModuleConfigComponent; type ComponentOutput = OutputMsg; fn set_config(&mut self, f: F) where F: FnOnce(&mut Self::Config), { let mut config = self.ppi_config.borrow_mut(); f(&mut config); } fn component_config(&self) -> Rc> { self.ppi_config.clone() } fn component_sender(&self) -> Box crate::pg::SideBarInputMsg> { Box::new(|c| match c { OutputMsg::Refresh => SideBarInputMsg::Refresh, }) } }