diff --git a/.gitignore b/.gitignore index e3b39c6..0f30a05 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.idea/ + debug/ target/ dist/ @@ -6,4 +8,4 @@ Cargo.lock **/*.rs.bk -*.pdb \ No newline at end of file +*.pdb diff --git a/Cargo.toml b/Cargo.toml index a23f596..122e7d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,8 +12,10 @@ eframe = { version = "0.29.1", features = [ "persistence",]} egui = { version = "0.29.1", features = ["callstack", "default", "log"] } +egui-snarl = {version = "0.5.0", features = ["serde"]} glam = "0.29.2" rand = "0.8.5" serde = { version = "1.0.215", features = ["derive"] } serde_json = "1.0.133" +syn = "2.0.89" typetag = "0.2.18" diff --git a/pane_layout.json b/pane_layout.json index b6379d6..266bdc7 100644 --- a/pane_layout.json +++ b/pane_layout.json @@ -1,14 +1,80 @@ -{ - "Properties": { - "mode": "Hidden", - "position": null, - "size": null, - "order": 1 +[ + { + "pane": { + "type": "BluePane" + }, + "id": "BLUE", + "mode": "Left" }, - "Console": { - "mode": "Tiled", - "position": null, - "size": null, - "order": 0 + { + "pane": { + "type": "GreenPane" + }, + "id": "Green", + "mode": "Bottom" + }, + { + "pane": { + "type": "PointRendererPane" + }, + "id": "Point Cloud", + "mode": "Center" + }, + { + "pane": { + "type": "PipelinePane", + "snarl": { + "nodes": { + "0": { + "value": { + "type": "Node1" + }, + "pos": { + "x": -56.0, + "y": -52.5 + }, + "open": true + }, + "1": { + "value": { + "type": "Node1" + }, + "pos": { + "x": -61.21814, + "y": 155.28181 + }, + "open": true + } + }, + "wires": [ + { + "out_pin": { + "node": 1, + "output": 0 + }, + "in_pin": { + "node": 0, + "input": 0 + } + }, + { + "out_pin": { + "node": 0, + "output": 0 + }, + "in_pin": { + "node": 1, + "input": 0 + } + } + ] + }, + "style": { + "select_rect_contained": null + }, + "snarl_ui_id": 12028535709621507733 + }, + "id": "Pipeline Pane", + "mode": "Right" } -} \ No newline at end of file +] \ No newline at end of file diff --git a/src/app.rs b/src/app.rs index fd8becf..66ff803 100644 --- a/src/app.rs +++ b/src/app.rs @@ -4,7 +4,7 @@ // use egui::{accesskit::TextAlign, mutex::Mutex, Align2, Color32, FontId, Pos2, Stroke}; // use egui_glow::glow; -use crate::panes::PaneManager; +use crate::pane_manager::PaneManager; /// We derive Deserialize/Serialize so we can persist app state on shutdown. // #[derive(serde::Deserialize, serde::Serialize)] diff --git a/src/lib.rs b/src/lib.rs index 4d3c4aa..5c470f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,8 @@ #![warn(clippy::all, rust_2018_idioms)] mod app; -mod point_cloud_renderer; +mod pane_manager; mod panes; +mod nodes; pub use app::App; -pub use panes::PaneManager; -// pub use point_cloud_renderer::PointCloudApp; +pub use pane_manager::PaneManager; \ No newline at end of file diff --git a/src/nodes/constants.rs b/src/nodes/constants.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/nodes/mod.rs b/src/nodes/mod.rs new file mode 100644 index 0000000..987b09f --- /dev/null +++ b/src/nodes/mod.rs @@ -0,0 +1 @@ +pub mod constants; \ No newline at end of file diff --git a/src/panes.rs b/src/pane_manager.rs similarity index 80% rename from src/panes.rs rename to src/pane_manager.rs index 5868257..d140c71 100644 --- a/src/panes.rs +++ b/src/pane_manager.rs @@ -1,6 +1,7 @@ use egui::{Ui, Color32, Stroke}; use eframe::egui_glow::glow; use std::sync::Arc; +use crate::panes::*; // use erased_serde::serialize_trait_object; #[derive(serde::Deserialize, serde::Serialize, PartialEq)] @@ -75,47 +76,6 @@ impl PaneState { // #[derive(serde::Deserialize, serde::Serialize)] // #[derive(serde::Deserialize, serde::Serialize)] -#[derive(serde::Serialize, serde::Deserialize)] -pub struct BluePane {} -#[typetag::serde] -impl Pane for BluePane { - fn new() -> PaneState where Self: Sized { - let mut s = Self {}; - PaneState { - id: s.name().to_string(), - mode: PaneMode::Left, - pane: Box::new(s), - } - } - fn init(&mut self, _pcc: &PsudoCreationContext){} - fn name(&mut self) -> &str {"BLUE"} - fn render(&mut self, ui: &mut Ui){ - ui.painter().rect(ui.max_rect(), 0., Color32::BLUE, Stroke::NONE); - } - fn context_menu(&mut self, _ui: &mut Ui) {} -} - - -#[derive(serde::Serialize, serde::Deserialize)] -pub struct GreenPane {} -#[typetag::serde] -impl Pane for GreenPane { - fn new() -> PaneState where Self: Sized { - let mut s = Self {}; - PaneState { - id: s.name().to_string(), - mode: PaneMode::Bottom, - pane: Box::new(s), - } - } - fn init(&mut self, _cc: &PsudoCreationContext){} - fn name(&mut self) -> &str {"Green"} - fn render(&mut self, ui: &mut Ui){ - ui.painter().rect(ui.max_rect(), 0., Color32::GREEN, Stroke::NONE); - } - fn context_menu(&mut self, _ui: &mut Ui) {} -} - pub struct PsudoCreationContext { pub gl: Option>, } @@ -131,9 +91,8 @@ impl PaneManager { // if let Some(cc) = cc { let mut panes = vec![ - BluePane::new(), - GreenPane::new(), - crate::point_cloud_renderer::PointRendererPane::new(), + point_cloud_renderer::PointRendererPane::new(), + pipeline_editor::PipelinePane::new(), ]; let pcc = PsudoCreationContext { @@ -172,21 +131,6 @@ impl PaneManager { for i in 0..len { ui.menu_button(self.panes[i].id.clone(), |ui| { - if ui.button("Hidden").clicked() { - self.panes[i].mode = PaneMode::Hidden; - } - if ui.button("Windowed").clicked() { - self.panes[i].mode = PaneMode::Windowed; - } - if ui.button("Left").clicked() { - self.panes[i].mode = PaneMode::Left; - } - if ui.button("Right").clicked() { - self.panes[i].mode = PaneMode::Right; - } - if ui.button("Bottom").clicked() { - self.panes[i].mode = PaneMode::Bottom; - } if ui.button("Center").clicked() { for a in 0..len { let pane2: &mut PaneState = &mut self.panes[a]; @@ -195,6 +139,27 @@ impl PaneManager { } } self.panes[i].mode = PaneMode::Center; + ui.close_menu(); + } + if ui.button("Windowed").clicked() { + self.panes[i].mode = PaneMode::Windowed; + ui.close_menu(); + } + if ui.button("Left").clicked() { + self.panes[i].mode = PaneMode::Left; + ui.close_menu(); + } + if ui.button("Right").clicked() { + self.panes[i].mode = PaneMode::Right; + ui.close_menu(); + } + if ui.button("Bottom").clicked() { + self.panes[i].mode = PaneMode::Bottom; + ui.close_menu(); + } + if ui.button("Hidden").clicked() { + self.panes[i].mode = PaneMode::Hidden; + ui.close_menu(); } }); } @@ -203,12 +168,11 @@ impl PaneManager { ui.separator(); for i in 0..len { - ui.menu_button(self.panes[i].id.clone(), |ui| { - let pane: &mut PaneState = &mut self.panes[i]; - if pane.mode != PaneMode::Hidden { - pane.pane.context_menu(ui); - } - }); + if self.panes[i].mode != PaneMode::Hidden { + ui.menu_button(self.panes[i].id.clone(), |ui| { + let _ = &mut self.panes[i].pane.context_menu(ui); + }); + } } }); }); @@ -269,7 +233,7 @@ impl PaneManager { self.panes = panes; - for (mut pane) in &mut self.panes { + for pane in &mut self.panes { pane.pane.init(&self.pcc); } } diff --git a/src/panes/mod.rs b/src/panes/mod.rs new file mode 100644 index 0000000..789ff96 --- /dev/null +++ b/src/panes/mod.rs @@ -0,0 +1,2 @@ +pub mod pipeline_editor; +pub mod point_cloud_renderer; \ No newline at end of file diff --git a/src/panes/pipeline_editor.rs b/src/panes/pipeline_editor.rs new file mode 100644 index 0000000..440647e --- /dev/null +++ b/src/panes/pipeline_editor.rs @@ -0,0 +1,257 @@ +use crate::pane_manager::{Pane, PaneMode, PaneState, PsudoCreationContext}; + + +use egui::{Color32, Id, Pos2, Ui}; +use egui_snarl::{ + ui::{PinInfo, SnarlStyle, SnarlViewer}, + InPin, NodeId, OutPin, Snarl, +}; +use egui_snarl::ui::{PinShape, WireStyle}; + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct PipelinePane { + snarl: Option>>, + style: Option, + snarl_ui_id: Option, +} +#[typetag::serde] +impl Pane for PipelinePane { + fn new() -> PaneState + where + Self: Sized, + { + let mut s = Self { + snarl: Some(Snarl::new()), + style: Some(SnarlStyle::new()), + snarl_ui_id: None, + }; + PaneState { + id: s.name().to_string(), + mode: PaneMode::Center, + pane: Box::new(s), + } + } + fn init(&mut self, _cc: &PsudoCreationContext) {} + fn name(&mut self) -> &str { + "Pipeline Pane" + } + fn render(&mut self, ui: &mut Ui) { + self.snarl_ui_id = Some(ui.id()); + + if let Some(snarl) = &mut self.snarl { + if let Some(style) = &self.style { + snarl.show(&mut NodeViewer, style, "snarl", ui); + } + } + + + } + fn context_menu(&mut self, ui: &mut Ui) { + + ui.menu_button("Add Node", |ui| { + if let Some(snarl) = &mut self.snarl { + NodeViewer::add_node_menu(Pos2 { x: 0., y: 0. }, ui, snarl); + } + }); + if ui.button("Run").clicked() { + ui.close_menu(); + self.run(); + } + // if !self.snarl.is_none() { + // self.snarl.unwrap().add_node_menu(ui, ui.clip_rect().min.clone(), ) + // } + } +} + +impl PipelinePane { + pub fn run(&mut self) { + // Todo: + } +} + +fn format_float(v: f64) -> String { + let v = (v * 1000.0).round() / 1000.0; + format!("{}", v) +} + +#[typetag::serde(tag = "type")] +trait Node { + fn new() -> Self + where + Self: Sized; + fn get_name(&self) -> &str; + fn get_description(&self) -> &str; + fn duplicate(&self) -> Box; + fn inputs(&self) -> usize; + fn outputs(&self) -> usize; + fn show_input(&self, pin: &InPin, ui: &mut Ui, scale: f32) -> PinInfo; + fn show_output(&self, pin: &OutPin, ui: &mut Ui, scale: f32) -> PinInfo; + fn can_rx(&self, other: &Box) -> bool; + fn can_tx(&self, other: &Box) -> bool; + fn context_menu(&self, ui: &mut Ui); +} + +#[derive(Clone, serde::Serialize, serde::Deserialize)] +struct Node1; +#[typetag::serde] +impl Node for Node1 { + fn new() -> Self { + Self + } + + fn get_name(&self) -> &str { + "Test" + } + fn get_description(&self) -> &str {"Test Node"} + + fn duplicate(&self) -> Box { + Box::new(Self::new()) + } + + fn inputs(&self) -> usize { + 1 + } + fn outputs(&self) -> usize { + 1 + } + fn show_input(&self, _pin: &InPin, _ui: &mut Ui, _scale: f32) -> PinInfo { + PinInfo::square() + } + fn show_output(&self, _pin: &OutPin, _ui: &mut Ui, _scale: f32) -> PinInfo { + PinInfo::square().with_fill(Color32::RED).with_wire_style(WireStyle::Bezier3) + } + fn can_rx(&self, _other: &Box) -> bool { + true + } + fn can_tx(&self, _other: &Box) -> bool { + true + } + fn context_menu(&self, ui: &mut Ui) { + ui.label("Test!"); + } +} + +#[derive(Clone, serde::Serialize, serde::Deserialize)] +struct NodeViewer; + +impl SnarlViewer> for NodeViewer { + fn connect(&mut self, from: &OutPin, to: &InPin, snarl: &mut Snarl>) { + // Validate connection + + let rx = snarl.get_node(to.id.node).unwrap(); + let tx = snarl.get_node(from.id.node).unwrap(); + + if rx.can_rx(tx) && tx.can_tx(rx) { + for &remote in &to.remotes { + snarl.disconnect(remote, to.id); + } + + snarl.connect(from.id, to.id); + } + } + + fn title(&mut self, node: &Box) -> String { + node.get_name().to_string() + } + + fn outputs(&mut self, node: &Box) -> usize { + node.outputs() + } + + fn inputs(&mut self, node: &Box) -> usize { + node.inputs() + } + + fn show_input( + &mut self, + pin: &InPin, + ui: &mut Ui, + scale: f32, + snarl: &mut Snarl>, + ) -> PinInfo { + snarl + .get_node(pin.id.node) + .unwrap() + .show_input(pin, ui, scale) + } + + fn show_output( + &mut self, + pin: &OutPin, + ui: &mut Ui, + scale: f32, + snarl: &mut Snarl>, + ) -> PinInfo { + snarl + .get_node(pin.id.node) + .unwrap() + .show_output(pin, ui, scale) + } + + + fn has_graph_menu(&mut self, _pos: Pos2, _snarl: &mut Snarl>) -> bool { + true + } + + fn show_graph_menu( + &mut self, + pos: egui::Pos2, + ui: &mut Ui, + _scale: f32, + snarl: &mut Snarl>, + ) { + NodeViewer::add_node_menu(pos, ui, snarl); + } + + fn has_on_hover_popup(&mut self, _: &Box) -> bool { + true + } + + fn show_on_hover_popup( + &mut self, + node: NodeId, + _inputs: &[InPin], + _outputs: &[OutPin], + ui: &mut Ui, + _scale: f32, + snarl: &mut Snarl>, + ) { + ui.label(snarl.get_node(node).unwrap().get_description()); + } + + fn has_node_menu(&mut self, _node: &Box) -> bool { + true + } + + fn show_node_menu( + &mut self, + nodeid: NodeId, + _inputs: &[InPin], + _outputs: &[OutPin], + ui: &mut Ui, + _scale: f32, + snarl: &mut Snarl>, + ) { + ui.label("Node menu"); + if ui.button("Remove").clicked() { + snarl.remove_node(nodeid); + ui.close_menu(); + } else if ui.button("Duplicate").clicked() { + snarl.insert_node(Pos2 {x:0.,y:0.}, snarl.get_node(nodeid).unwrap().duplicate()); + ui.close_menu(); + } else { + snarl.get_node(nodeid).unwrap().context_menu(ui); + } + } +} + +impl NodeViewer { + pub fn add_node_menu(pos: Pos2, ui: &mut Ui, snarl: &mut Snarl>) { + ui.label("Add node"); + let button = ui.button("Test1"); + if button.clicked() { + snarl.insert_node(pos, Box::new(Node1::new())); + ui.close_menu(); + } + } +} \ No newline at end of file diff --git a/src/point_cloud_renderer.rs b/src/panes/point_cloud_renderer.rs similarity index 99% rename from src/point_cloud_renderer.rs rename to src/panes/point_cloud_renderer.rs index 2171e8e..1a67a5e 100644 --- a/src/point_cloud_renderer.rs +++ b/src/panes/point_cloud_renderer.rs @@ -6,7 +6,7 @@ use glam::{Vec3, Mat4, Quat}; use std::fs::File; // use std::path::Path; use std::io::{BufReader, BufRead}; -use crate::panes::{Pane, PaneMode, PaneState}; +use crate::pane_manager::{Pane, PaneMode, PaneState, PsudoCreationContext}; use std::sync::Mutex; use egui::FontId; use egui::Align2; @@ -14,7 +14,6 @@ use egui::Align2; use std::time::Instant; use egui::Stroke; use egui::Ui; -use crate::panes; // Shader sources updated for 3D rendering with fixed-point positions const VERTEX_SHADER: &str = r#" @@ -532,7 +531,7 @@ pub struct PointRendererPane { #[typetag::serde] impl Pane for PointRendererPane { fn new() -> PaneState where Self: Sized { - let mut renderer = PointRenderer::default(); + let renderer = PointRenderer::default(); let mut s = Self { renderer: Arc::new(Mutex::new(renderer)), points: Vec::new(), @@ -541,11 +540,11 @@ impl Pane for PointRendererPane { }; PaneState { id: s.name().to_string(), - mode: PaneMode::Center, + mode: PaneMode::Hidden, pane: Box::new(s), } } - fn init(&mut self, pcc: &panes::PsudoCreationContext){ + fn init(&mut self, pcc: &PsudoCreationContext){ self.renderer.lock().expect("Renderer Not Initialized").init(pcc.gl.clone(), 1_000_000); } fn name(&mut self) -> &str {"Point Cloud"}