diff --git a/Cargo.toml b/Cargo.toml index 45ee8a8..b58325b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,11 +10,9 @@ egui = { version = "*", features = ["persistence"] } eframe = "*" epi = "*" slotmap = { version = "*", features = ["serde"] } -smallvec = { version = "*", features = ["serde"] } strum = "*" strum_macros = "*" serde = { version = "*", features = ["derive"] } anyhow = "*" -nalgebra = { version = "*", features = ["serde-serialize"] } rfd = "*" evdev = { git = "http://www.gavania.de/hodasemi/evdev-rs" } \ No newline at end of file diff --git a/src/device.rs b/src/device.rs index 6724c30..1769dce 100644 --- a/src/device.rs +++ b/src/device.rs @@ -1,9 +1,11 @@ use evdev::{Device as EvDevice, EventType, InputEventKind}; -use std::{collections::HashMap, path::PathBuf}; +use std::collections::HashMap; + +use crate::graph_types::{Descriptor, NodeDescriptor}; pub struct Device { device: EvDevice, - path: PathBuf, + path: String, supported_events: HashMap>, } @@ -84,13 +86,119 @@ impl Device { Device { device, - path, + path: path.as_path().to_str().unwrap().to_string(), supported_events, } }) .collect() } + + pub fn name(&self) -> &str { + self.device.name().unwrap_or("Unnamed Device") + } + + pub fn to_descriptor(&self) -> NodeDescriptor { + let mut inputs = match self.supported_events.get(&EventType::FORCEFEEDBACK) { + Some(ff) => ff + .iter() + .map(|kind| { + ( + format!( + "{:?}", + match kind { + InputEventKind::ForceFeedback(ff) => ff, + _ => panic!("wrong kind"), + } + ), + // TODO: find min and max values + Descriptor::Slider { min: 0, max: 255 }, + ) + }) + .collect(), + None => Vec::new(), + }; + + let outputs = Self::flatten( + self.supported_events + .iter() + .map(|(event_type, kinds)| { + let t: Option> = match *event_type { + EventType::ABSOLUTE => Some( + kinds + .iter() + .map(|kind| { + ( + format!( + "{:?}", + match kind { + InputEventKind::AbsAxis(axis) => axis, + _ => panic!("wrong kind"), + } + ), + // TODO: find min and max values + Descriptor::Slider { min: 0, max: 255 }, + ) + }) + .collect(), + ), + EventType::RELATIVE => Some( + kinds + .iter() + .map(|kind| { + ( + format!( + "{:?}", + match kind { + InputEventKind::RelAxis(axis) => axis, + _ => panic!("wrong kind"), + } + ), + // TODO: find min and max values + Descriptor::Slider { min: 0, max: 255 }, + ) + }) + .collect(), + ), + EventType::KEY => Some( + kinds + .iter() + .map(|kind| { + ( + format!( + "{:?}", + match kind { + InputEventKind::Key(key) => key, + _ => panic!("wrong kind"), + } + ), + Descriptor::Button, + ) + }) + .collect(), + ), + + _ => None, + }; + + t + }) + .filter_map(|f| f) + .collect::>>(), + ); + + inputs.insert(0, (format!("Path: {}", self.path), Descriptor::Info)); + + NodeDescriptor { + op_name: self.name().to_string(), + inputs, + outputs, + } + } + + fn flatten(vv: Vec>) -> Vec { + vv.into_iter().flatten().collect() + } } impl std::fmt::Debug for Device { diff --git a/src/editor_state.rs b/src/editor_state.rs index efb340a..577dcea 100644 --- a/src/editor_state.rs +++ b/src/editor_state.rs @@ -14,7 +14,7 @@ pub struct GraphEditorState { pub graph: Graph, /// An ongoing connection interaction: The mouse has dragged away from a /// port and the user is holding the click - pub connection_in_progress: Option<(NodeId, AnyParameterId)>, + pub connection_in_progress: Option<(NodeId, ID)>, /// The currently active node. A program will be compiled to compute the /// result of this node and constantly updated in real-time. pub active_node: Option, diff --git a/src/graph_editor_egui.rs b/src/graph_editor_egui.rs index ecbc102..a4f8551 100644 --- a/src/graph_editor_egui.rs +++ b/src/graph_editor_egui.rs @@ -1,12 +1,12 @@ use egui::*; use crate::color_hex_utils::*; +use crate::device::Device; use crate::editor_state::GraphEditorState; use crate::graph_node_ui::*; -use crate::id_types::*; use crate::node_finder::NodeFinder; -pub fn draw_graph_editor(ctx: &CtxRef, state: &mut GraphEditorState) { +pub fn draw_graph_editor(ctx: &CtxRef, state: &mut GraphEditorState, devices: &[Device]) { let mouse = &ctx.input().pointer; let cursor_pos = mouse.hover_pos().unwrap_or(Pos2::ZERO); @@ -49,8 +49,8 @@ pub fn draw_graph_editor(ctx: &CtxRef, state: &mut GraphEditorState) { node_finder_area = node_finder_area.current_pos(pos); } node_finder_area.show(ctx, |ui| { - if let Some(node_archetype) = node_finder.show(ui) { - let new_node = state.graph.add_node(node_archetype.to_descriptor()); + if let Some(node_device) = node_finder.show(devices, ui) { + let new_node = state.graph.add_node(node_device.to_descriptor()); state .node_positions .insert(new_node, cursor_pos - state.pan_zoom.pan); @@ -76,8 +76,8 @@ pub fn draw_graph_editor(ctx: &CtxRef, state: &mut GraphEditorState) { for (input, output) in state.graph.iter_connections() { let painter = ctx.layer_painter(LayerId::background()); - let src_pos = port_locations[&AnyParameterId::Output(output)]; - let dst_pos = port_locations[&AnyParameterId::Input(input)]; + let src_pos = port_locations[&output]; + let dst_pos = port_locations[&input]; painter.line_segment([src_pos, dst_pos], connection_stroke); } @@ -97,10 +97,7 @@ pub fn draw_graph_editor(ctx: &CtxRef, state: &mut GraphEditorState) { .expect("Cannot end drag without in-progress connection."), locator, ) { - (AnyParameterId::Input(input), AnyParameterId::Output(output)) - | (AnyParameterId::Output(output), AnyParameterId::Input(input)) => { - Some((input, output)) - } + (input, output) | (output, input) => Some((input, output)), _ => None, }; @@ -133,10 +130,9 @@ pub fn draw_graph_editor(ctx: &CtxRef, state: &mut GraphEditorState) { .graph .connection(input_id) .expect("Connection data should be valid"); - let other_node = state.graph.get_input(input_id).node(); + let other_node = state.graph.input(input_id).node; state.graph.remove_connection(input_id); - state.connection_in_progress = - Some((other_node, AnyParameterId::Output(corresp_output))); + state.connection_in_progress = Some((other_node, corresp_output)); } } } diff --git a/src/graph_impls.rs b/src/graph_impls.rs index c326161..bc5cd85 100644 --- a/src/graph_impls.rs +++ b/src/graph_impls.rs @@ -1,9 +1,8 @@ +use anyhow::Error; use anyhow::{anyhow, Result}; -use smallvec::smallvec; use crate::graph_types::*; use crate::id_types::*; -use crate::SVec; impl Graph { pub fn new() -> Self { @@ -14,88 +13,65 @@ impl Graph { let node_id = self.nodes.insert_with_key(|node_id| { Node { id: node_id, - label: d.label, op_name: d.op_name, // These will get filled in later inputs: Vec::default(), outputs: Vec::default(), - is_executable: d.is_executable, } }); - use InputParamKind::*; - let inputs: Vec<(String, InputId)> = d + let inputs: Vec<(String, ID)> = d .inputs .into_iter() .map(|(input_name, input)| { let input_id = self.inputs.insert_with_key(|id| match input { - InputDescriptor::Vector { default } => InputParam { + Descriptor::Button => Param { id, - typ: DataType::Vector, - value: InputParamValue::Vector(default), - metadata: smallvec![], - kind: ConnectionOrConstant, + kind: input, node: node_id, + draw_connector: true, }, - InputDescriptor::Mesh => InputParam { + Descriptor::Slider { .. } => Param { id, - typ: DataType::Mesh, - value: InputParamValue::None, - metadata: smallvec![], - kind: ConnectionOnly, + kind: input, node: node_id, + draw_connector: true, }, - InputDescriptor::Selection => InputParam { + Descriptor::Info => Param { id, - typ: DataType::Selection, - value: InputParamValue::Selection { - text: "".into(), - selection: Some(vec![]), - }, - metadata: smallvec![], - kind: ConnectionOrConstant, - node: node_id, - }, - InputDescriptor::Scalar { default, min, max } => InputParam { - id, - typ: DataType::Scalar, - value: InputParamValue::Scalar(default), - metadata: smallvec![InputParamMetadata::MinMaxScalar { min, max }], - kind: ConnectionOrConstant, - node: node_id, - }, - InputDescriptor::Enum { values } => InputParam { - id, - typ: DataType::Enum, - value: InputParamValue::Enum { - values, - selection: None, - }, - metadata: smallvec![], - kind: ConstantOnly, - node: node_id, - }, - InputDescriptor::NewFile => InputParam { - id, - typ: DataType::NewFile, - value: InputParamValue::NewFile { path: None }, - metadata: smallvec![], - kind: ConstantOnly, + kind: input, node: node_id, + draw_connector: false, }, }); + (input_name, input_id) }) .collect(); - let outputs: Vec<(String, OutputId)> = d + let outputs: Vec<(String, ID)> = d .outputs .into_iter() .map(|(output_name, output)| { - let output_id = self.outputs.insert_with_key(|id| OutputParam { - node: node_id, - id, - typ: output.0, + let output_id = self.outputs.insert_with_key(|id| match output { + Descriptor::Button => Param { + id, + kind: output, + node: node_id, + draw_connector: true, + }, + Descriptor::Slider { .. } => Param { + id, + kind: output, + node: node_id, + draw_connector: true, + }, + Descriptor::Info => Param { + id, + kind: output, + node: node_id, + draw_connector: false, + }, }); (output_name, output_id) }) @@ -109,18 +85,18 @@ impl Graph { pub fn remove_node(&mut self, node_id: NodeId) { self.connections .retain(|i, o| !(self.outputs[*o].node == node_id || self.inputs[*i].node == node_id)); - let inputs: SVec<_> = self[node_id].input_ids().collect(); + let inputs: Vec<_> = self[node_id].input_ids().collect(); for input in inputs { self.inputs.remove(input); } - let outputs: SVec<_> = self[node_id].output_ids().collect(); + let outputs: Vec<_> = self[node_id].output_ids().collect(); for output in outputs { self.outputs.remove(output); } self.nodes.remove(node_id); } - pub fn remove_connection(&mut self, input_id: InputId) -> Option { + pub fn remove_connection(&mut self, input_id: ID) -> Option { self.connections.remove(&input_id) } @@ -128,53 +104,47 @@ impl Graph { self.nodes.iter().map(|(id, _)| id) } - pub fn add_connection(&mut self, output: OutputId, input: InputId) { + pub fn add_connection(&mut self, output: ID, input: ID) { self.connections.insert(input, output); } - pub fn iter_connections(&self) -> impl Iterator + '_ { + pub fn iter_connections(&self) -> impl Iterator + '_ { self.connections.iter().map(|(o, i)| (*o, *i)) } - pub fn connection(&self, input: InputId) -> Option { + pub fn connection(&self, input: ID) -> Option { self.connections.get(&input).copied() } - pub fn any_param_type(&self, param: AnyParameterId) -> Result { - match param { - AnyParameterId::Input(input) => self.inputs.get(input).map(|x| x.typ), - AnyParameterId::Output(output) => self.outputs.get(output).map(|x| x.typ), + pub fn any_type(&self, id: ID) -> Result<&Param> { + match self.inputs.get(id) { + Some(param) => Ok(param), + None => match self.outputs.get(id) { + Some(param) => Ok(param), + None => Err(Error::msg(format!("Invalid parameter id: {:?}", id))), + }, } - .ok_or_else(|| anyhow!("Invalid parameter id: {:?}", param)) - } - - pub fn get_input(&self, input: InputId) -> &InputParam { - &self.inputs[input] - } - - pub fn get_output(&self, output: OutputId) -> &OutputParam { - &self.outputs[output] } } impl Node { - pub fn inputs<'a>(&'a self, graph: &'a Graph) -> impl Iterator + 'a { - self.input_ids().map(|id| graph.get_input(id)) + pub fn inputs<'a>(&'a self, graph: &'a Graph) -> impl Iterator + 'a { + self.input_ids().map(|id| graph.input(id)) } - pub fn outputs<'a>(&'a self, graph: &'a Graph) -> impl Iterator + 'a { - self.output_ids().map(|id| graph.get_output(id)) + pub fn outputs<'a>(&'a self, graph: &'a Graph) -> impl Iterator + 'a { + self.output_ids().map(|id| graph.output(id)) } - pub fn input_ids(&self) -> impl Iterator + '_ { + pub fn input_ids(&self) -> impl Iterator + '_ { self.inputs.iter().map(|(_name, id)| *id) } - pub fn output_ids(&self) -> impl Iterator + '_ { + pub fn output_ids(&self) -> impl Iterator + '_ { self.outputs.iter().map(|(_name, id)| *id) } - pub fn get_input(&self, name: &str) -> Result { + pub fn get_input(&self, name: &str) -> Result { self.inputs .iter() .find(|(param_name, _id)| param_name == name) @@ -182,7 +152,7 @@ impl Node { .ok_or_else(|| anyhow!("Node {:?} has no parameter named {}", self.id, name)) } - pub fn get_output(&self, name: &str) -> Result { + pub fn get_output(&self, name: &str) -> Result { self.outputs .iter() .find(|(param_name, _id)| param_name == name) @@ -190,14 +160,9 @@ impl Node { .ok_or_else(|| anyhow!("Node {:?} has no parameter named {}", self.id, name)) } - /// Can this node be enabled on the UI? I.e. does it output a mesh? - pub fn can_be_enabled(&self, graph: &Graph) -> bool { - self.outputs(graph) - .any(|output| output.typ == DataType::Mesh) - } - - /// Executable nodes are used to produce side effects, like exporting files. - pub fn is_executable(&self) -> bool { - self.is_executable - } + // Can this node be enabled on the UI? I.e. does it output a mesh? + // pub fn can_be_enabled(&self, graph: &Graph) -> bool { + // self.outputs(graph) + // .any(|output| output.typ == DataType::Mesh) + // } } diff --git a/src/graph_node_ui.rs b/src/graph_node_ui.rs index 4d7f31e..43006db 100644 --- a/src/graph_node_ui.rs +++ b/src/graph_node_ui.rs @@ -5,16 +5,16 @@ use crate::color_hex_utils::*; use crate::graph_types::*; use crate::id_types::*; -pub type PortLocations = std::collections::HashMap; +pub type PortLocations = std::collections::HashMap; pub enum DrawGraphNodeResponse { - ConnectEventStarted(NodeId, AnyParameterId), - ConnectEventEnded(AnyParameterId), + ConnectEventStarted(NodeId, ID), + ConnectEventEnded(ID), SetActiveNode(NodeId), RunNodeSideEffect(NodeId), ClearActiveNode, DeleteNode(NodeId), - DisconnectEvent(InputId), + DisconnectEvent(ID), } pub struct GraphNodeWidget<'a> { @@ -22,7 +22,7 @@ pub struct GraphNodeWidget<'a> { pub graph: &'a mut Graph, pub port_locations: &'a mut PortLocations, pub node_id: NodeId, - pub ongoing_drag: Option<(NodeId, AnyParameterId)>, + pub ongoing_drag: Option<(NodeId, ID)>, pub active: bool, pub pan: egui::Vec2, } @@ -60,7 +60,7 @@ impl<'a> GraphNodeWidget<'a> { node_id: NodeId, ui: &mut Ui, port_locations: &mut PortLocations, - ongoing_drag: Option<(NodeId, AnyParameterId)>, + ongoing_drag: Option<(NodeId, ID)>, active: bool, ) -> Option { let margin = egui::vec2(15.0, 5.0); @@ -92,7 +92,7 @@ impl<'a> GraphNodeWidget<'a> { child_ui.vertical(|ui| { ui.horizontal(|ui| { ui.add(Label::new( - RichText::new(&graph[node_id].label) + RichText::new(&graph[node_id].op_name) .text_style(TextStyle::Button) .color(color_from_hex("#fefefe").unwrap()), )); @@ -107,7 +107,7 @@ impl<'a> GraphNodeWidget<'a> { if graph.connection(param).is_some() { ui.label(param_name); } else { - graph[param].value_widget(¶m_name, ui); + graph.input(param).value_widget(¶m_name, ui); } let height_after = ui.min_rect().bottom(); input_port_heights.push((height_before + height_after) / 2.0); @@ -121,31 +121,27 @@ impl<'a> GraphNodeWidget<'a> { output_port_heights.push((height_before + height_after) / 2.0); } - // Button row - ui.horizontal(|ui| { - // Show 'Enable' button for nodes that output a mesh - if graph[node_id].can_be_enabled(graph) { - ui.horizontal(|ui| { - if !active { - if ui.button("👁 Set active").clicked() { - response = Some(DrawGraphNodeResponse::SetActiveNode(node_id)); - } - } else { - let button = egui::Button::new( - RichText::new("👁 Active").color(egui::Color32::BLACK), - ) - .fill(egui::Color32::GOLD); - if ui.add(button).clicked() { - response = Some(DrawGraphNodeResponse::ClearActiveNode); - } - } - }); - } - // Show 'Run' button for executable nodes - if graph[node_id].is_executable() && ui.button("⛭ Run").clicked() { - response = Some(DrawGraphNodeResponse::RunNodeSideEffect(node_id)); - } - }) + // // Button row + // ui.horizontal(|ui| { + // // Show 'Enable' button for nodes that output a mesh + // if graph[node_id].can_be_enabled(graph) { + // ui.horizontal(|ui| { + // if !active { + // if ui.button("👁 Set active").clicked() { + // response = Some(DrawGraphNodeResponse::SetActiveNode(node_id)); + // } + // } else { + // let button = egui::Button::new( + // RichText::new("👁 Active").color(egui::Color32::BLACK), + // ) + // .fill(egui::Color32::GOLD); + // if ui.add(button).clicked() { + // response = Some(DrawGraphNodeResponse::ClearActiveNode); + // } + // } + // }); + // } + // }) }); // Second pass, iterate again to draw the ports. This happens outside @@ -162,13 +158,11 @@ impl<'a> GraphNodeWidget<'a> { node_id: NodeId, port_pos: Pos2, response: &mut Option, - param_id: AnyParameterId, + param_id: ID, port_locations: &mut PortLocations, - ongoing_drag: Option<(NodeId, AnyParameterId)>, + ongoing_drag: Option<(NodeId, ID)>, is_connected_input: bool, ) { - let port_type = graph.any_param_type(param_id).unwrap(); - let port_rect = Rect::from_center_size(port_pos, egui::vec2(10.0, 10.0)); let sense = if ongoing_drag.is_some() { @@ -181,16 +175,14 @@ impl<'a> GraphNodeWidget<'a> { let port_color = if resp.hovered() { Color32::WHITE } else { - GraphNodeWidget::data_type_color(port_type) + color_from_hex("#4b7f52").unwrap() }; ui.painter() .circle(port_rect.center(), 5.0, port_color, Stroke::none()); if resp.drag_started() { if is_connected_input { - *response = Some(DrawGraphNodeResponse::DisconnectEvent( - param_id.assume_input(), - )); + *response = Some(DrawGraphNodeResponse::DisconnectEvent(param_id)); } else { *response = Some(DrawGraphNodeResponse::ConnectEventStarted( node_id, param_id, @@ -201,10 +193,7 @@ impl<'a> GraphNodeWidget<'a> { if let Some((origin_node, origin_param)) = ongoing_drag { if origin_node != node_id { // Don't allow self-loops - if graph.any_param_type(origin_param).unwrap() == port_type - && resp.hovered() - && ui.input().pointer.any_released() - { + if resp.hovered() && ui.input().pointer.any_released() { *response = Some(DrawGraphNodeResponse::ConnectEventEnded(param_id)); } } @@ -219,11 +208,7 @@ impl<'a> GraphNodeWidget<'a> { .iter() .zip(input_port_heights.into_iter()) { - let should_draw = match graph[*param].kind() { - InputParamKind::ConnectionOnly => true, - InputParamKind::ConstantOnly => false, - InputParamKind::ConnectionOrConstant => true, - }; + let should_draw = graph.input(*param).draw_connector; if should_draw { let pos_left = pos2(port_left, port_height); @@ -233,7 +218,7 @@ impl<'a> GraphNodeWidget<'a> { node_id, pos_left, &mut response, - AnyParameterId::Input(*param), + *param, port_locations, ongoing_drag, graph.connection(*param).is_some(), @@ -254,7 +239,7 @@ impl<'a> GraphNodeWidget<'a> { node_id, pos_right, &mut response, - AnyParameterId::Output(*param), + *param, port_locations, ongoing_drag, false, @@ -344,16 +329,4 @@ impl<'a> GraphNodeWidget<'a> { resp } - - /// The port colors for all the data types - fn data_type_color(data_type: DataType) -> egui::Color32 { - match data_type { - DataType::Mesh => color_from_hex("#266dd3").unwrap(), - DataType::Vector => color_from_hex("#eecf6d").unwrap(), - DataType::Scalar => color_from_hex("#eb9fef").unwrap(), - DataType::Selection => color_from_hex("#4b7f52").unwrap(), - DataType::Enum => color_from_hex("#ff0000").unwrap(), // Should never be in a port, so highlight in red - DataType::NewFile => color_from_hex("#ff0000").unwrap(), // Should never be in a port, so highlight in red - } - } } diff --git a/src/graph_types.rs b/src/graph_types.rs index 6da37c9..cad6bd5 100644 --- a/src/graph_types.rs +++ b/src/graph_types.rs @@ -1,10 +1,8 @@ -use nalgebra::Vector3 as Vec3; use serde::{Deserialize, Serialize}; use slotmap::SlotMap; use std::collections::HashMap; use crate::id_types::*; -use crate::SVec; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum DataType { @@ -23,119 +21,40 @@ pub enum InputParamMetadata { } #[derive(Debug, Clone, Serialize, Deserialize)] -pub enum InputParamValue { - Vector(Vec3), - Scalar(f32), - Selection { - text: String, - selection: Option>, - }, - /// Used for parameters that can't have a value because they only accept - /// connections. - None, - Enum { - values: Vec, - selection: Option, - }, - NewFile { - path: Option, - }, -} - -/// There are three kinds of input params -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub enum InputParamKind { - /// No constant value can be set. Only incoming connections can produce it - ConnectionOnly, - /// Only a constant value can be set. No incoming connections accepted. - ConstantOnly, - /// Both incoming connections and constants are accepted. Connections take - /// precedence over the constant values. - ConnectionOrConstant, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct InputParam { - pub id: InputId, - /// The data type of this node. Used to determine incoming connections. This - /// should always match the type of the InputParamValue, but the property is - /// not actually enforced. - pub typ: DataType, - /// The constant value stored in this parameter. - pub value: InputParamValue, - /// A list of metadata fields, specifying things like bounds or limits. - /// Metadata values that don't make sense for a type are ignored. - pub metadata: SVec, - /// The input kind. See [InputParamKind] - pub kind: InputParamKind, - /// Back-reference to the node containing this parameter. +pub struct Param { + pub id: ID, + pub kind: Descriptor, pub node: NodeId, -} - -impl InputParam { - pub fn value(&self) -> InputParamValue { - self.value.clone() - } - - pub fn kind(&self) -> InputParamKind { - self.kind - } - - pub fn node(&self) -> NodeId { - self.node - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct OutputParam { - pub id: OutputId, - /// Back-reference to the node containing this parameter. - pub node: NodeId, - pub typ: DataType, -} - -impl OutputParam { - pub fn node(&self) -> NodeId { - self.node - } + pub draw_connector: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Node { pub id: NodeId, - pub label: String, pub op_name: String, - pub inputs: Vec<(String, InputId)>, - pub outputs: Vec<(String, OutputId)>, - /// Executable nodes will run some code when their "Run" button is clicked - pub is_executable: bool, + pub inputs: Vec<(String, ID)>, + pub outputs: Vec<(String, ID)>, } #[derive(Default, Debug, Clone, Serialize, Deserialize)] pub struct Graph { pub nodes: SlotMap, - pub inputs: SlotMap, - pub outputs: SlotMap, + pub inputs: SlotMap, + pub outputs: SlotMap, // Connects the input of a node, to the output of its predecessor that // produces it - pub connections: HashMap, + pub connections: HashMap, } -pub enum InputDescriptor { - Vector { default: Vec3 }, - Mesh, - Selection, - Scalar { default: f32, min: f32, max: f32 }, - Enum { values: Vec }, - NewFile, +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Descriptor { + Button, + Slider { min: i32, max: i32 }, + Info, } -pub struct OutputDescriptor(pub DataType); - pub struct NodeDescriptor { pub op_name: String, - pub label: String, - pub inputs: Vec<(String, InputDescriptor)>, - pub outputs: Vec<(String, OutputDescriptor)>, - pub is_executable: bool, + pub inputs: Vec<(String, Descriptor)>, + pub outputs: Vec<(String, Descriptor)>, } diff --git a/src/id_types.rs b/src/id_types.rs index 3ed325e..99d3a88 100644 --- a/src/id_types.rs +++ b/src/id_types.rs @@ -1,24 +1,2 @@ slotmap::new_key_type! { pub struct NodeId; } -slotmap::new_key_type! { pub struct InputId; } -slotmap::new_key_type! { pub struct OutputId; } - -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] -pub enum AnyParameterId { - Input(InputId), - Output(OutputId), -} - -impl AnyParameterId { - pub fn assume_input(&self) -> InputId { - match self { - AnyParameterId::Input(input) => *input, - AnyParameterId::Output(output) => panic!("{:?} is not an InputId", output), - } - } - pub fn assume_output(&self) -> OutputId { - match self { - AnyParameterId::Output(output) => *output, - AnyParameterId::Input(input) => panic!("{:?} is not an OutputId", input), - } - } -} +slotmap::new_key_type! { pub struct ID; } diff --git a/src/index_impls.rs b/src/index_impls.rs index ca2a181..138f651 100644 --- a/src/index_impls.rs +++ b/src/index_impls.rs @@ -31,6 +31,42 @@ macro_rules! impl_index_traits { }; } +impl Graph { + pub fn input(&self, index: ID) -> &Param { + self.inputs.get(index).unwrap_or_else(|| { + panic!( + "ID index error for {:?}. Has the value been deleted?", + index + ) + }) + } + + pub fn input_mut(&mut self, index: ID) -> &mut Param { + self.inputs.get_mut(index).unwrap_or_else(|| { + panic!( + "ID index error for {:?}. Has the value been deleted?", + index + ) + }) + } + + pub fn output(&self, index: ID) -> &Param { + self.outputs.get(index).unwrap_or_else(|| { + panic!( + "ID index error for {:?}. Has the value been deleted?", + index + ) + }) + } + + pub fn output_mut(&mut self, index: ID) -> &mut Param { + self.outputs.get_mut(index).unwrap_or_else(|| { + panic!( + "ID index error for {:?}. Has the value been deleted?", + index + ) + }) + } +} + impl_index_traits!(NodeId, Node, nodes); -impl_index_traits!(InputId, InputParam, inputs); -impl_index_traits!(OutputId, OutputParam, outputs); diff --git a/src/main.rs b/src/main.rs index 7a3def6..bc923bc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,13 +11,10 @@ mod graph_types; mod id_types; mod index_impls; mod node_finder; -mod node_types; mod param_ui; use device::Device; -pub type SVec = smallvec::SmallVec<[T; 4]>; - #[derive(Default)] struct App { pub state: GraphEditorState, @@ -34,9 +31,9 @@ impl App { } impl epi::App for App { - fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) { - egui::CentralPanel::default().show(ctx, |ui| { - graph_editor_egui::draw_graph_editor(&ctx, &mut self.state); + fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) { + egui::CentralPanel::default().show(ctx, |_| { + graph_editor_egui::draw_graph_editor(&ctx, &mut self.state, &self.devices); }); } diff --git a/src/node_finder.rs b/src/node_finder.rs index 28e3c07..2ce1cea 100644 --- a/src/node_finder.rs +++ b/src/node_finder.rs @@ -1,7 +1,7 @@ use egui::*; use crate::color_hex_utils::*; -use crate::node_types::GraphNodeType; +use crate::device::Device; #[derive(Default)] pub struct NodeFinder { @@ -23,7 +23,7 @@ impl NodeFinder { /// Shows the node selector panel with a search bar. Returns whether a node /// archetype was selected and, in that case, the finder should be hidden on /// the next frame. - pub fn show(&mut self, ui: &mut Ui) -> Option { + pub fn show<'a>(&mut self, devices: &'a [Device], ui: &mut Ui) -> Option<&'a Device> { let background_color = color_from_hex("#3f3f3f").unwrap(); let _titlebar_color = background_color.linear_multiply(0.8); let text_color = color_from_hex("#fefefe").unwrap(); @@ -35,7 +35,7 @@ impl NodeFinder { .margin(vec2(5.0, 5.0)); // The archetype that will be returned. - let mut submitted_archetype = None; + let mut submitted_device = None; frame.show(ui, |ui| { ui.vertical(|ui| { let resp = ui.text_edit_singleline(&mut self.query); @@ -47,15 +47,15 @@ impl NodeFinder { let mut query_submit = resp.lost_focus() && ui.input().key_down(Key::Enter); Frame::default().margin(vec2(10.0, 10.0)).show(ui, |ui| { - for archetype in GraphNodeType::all_types() { - let archetype_name = archetype.type_label(); - if archetype_name.contains(self.query.as_str()) { + for device in devices { + let device_name = device.name(); + if device_name.contains(self.query.as_str()) { if query_submit { - submitted_archetype = Some(archetype); + submitted_device = Some(device); query_submit = false; } - if ui.selectable_label(false, archetype_name).clicked() { - submitted_archetype = Some(archetype); + if ui.selectable_label(false, device_name).clicked() { + submitted_device = Some(device); } } } @@ -63,6 +63,6 @@ impl NodeFinder { }); }); - submitted_archetype + submitted_device } } diff --git a/src/node_types.rs b/src/node_types.rs deleted file mode 100644 index d0d0bb2..0000000 --- a/src/node_types.rs +++ /dev/null @@ -1,210 +0,0 @@ -use nalgebra::Vector3 as Vec3; -use strum::IntoEnumIterator; - -use crate::graph_types::*; - -const ONE: Vec3 = Vec3::new(1.0, 1.0, 1.0); -const ZERO: Vec3 = Vec3::new(0.0, 0.0, 0.0); -const X: Vec3 = Vec3::new(1.0, 0.0, 0.0); -const Y: Vec3 = Vec3::new(0.0, 1.0, 0.0); - -#[derive(Clone, Copy, strum_macros::EnumIter)] -pub enum GraphNodeType { - MakeBox, - MakeQuad, - BevelEdges, - ExtrudeFaces, - ChamferVertices, - MakeVector, - VectorMath, - MergeMeshes, - ExportObj, -} - -macro_rules! in_vector { - ($name:expr, $default:expr) => { - ( - $name.to_owned(), - InputDescriptor::Vector { default: $default }, - ) - }; -} - -macro_rules! in_scalar { - ($name:expr, $default:expr, $min:expr, $max:expr) => { - ( - $name.to_owned(), - InputDescriptor::Scalar { - default: $default, - max: $max, - min: $min, - }, - ) - }; - ($name:expr) => { - in_scalar!($name, 0.0, -1.0, 2.0) - }; -} - -macro_rules! in_mesh { - ($name:expr) => { - ($name.to_owned(), InputDescriptor::Mesh) - }; -} - -macro_rules! out_mesh { - ($name:expr) => { - ($name.to_owned(), OutputDescriptor(DataType::Mesh)) - }; -} - -macro_rules! out_vector { - ($name:expr) => { - ($name.to_owned(), OutputDescriptor(DataType::Vector)) - }; -} - -macro_rules! in_selection { - ($name:expr) => { - ($name.to_owned(), InputDescriptor::Selection) - }; -} - -macro_rules! in_file { - ($name:expr) => { - ($name.to_owned(), InputDescriptor::NewFile) - }; -} - -macro_rules! in_enum { - ($name:expr, $( $values:expr ),+) => { - ($name.to_owned(), InputDescriptor::Enum { values: vec![$( $values.to_owned() ),+] }) - }; -} - -impl GraphNodeType { - pub fn to_descriptor(&self) -> NodeDescriptor { - let label = self.type_label().into(); - let op_name = self.op_name().into(); - match self { - GraphNodeType::MakeBox => NodeDescriptor { - op_name, - label, - inputs: vec![in_vector!("origin", ZERO), in_vector!("size", ONE)], - outputs: vec![out_mesh!("out_mesh")], - is_executable: false, - }, - GraphNodeType::MakeQuad => NodeDescriptor { - op_name, - label, - inputs: vec![ - in_vector!("center", ZERO), - in_vector!("normal", Y), - in_vector!("right", X), - in_vector!("size", ONE), - ], - outputs: vec![out_mesh!("out_mesh")], - is_executable: false, - }, - GraphNodeType::BevelEdges => NodeDescriptor { - op_name, - label, - inputs: vec![ - in_mesh!("in_mesh"), - in_selection!("edges"), - in_scalar!("amount", 0.0, 0.0, 1.0), - ], - outputs: vec![out_mesh!("out_mesh")], - is_executable: false, - }, - GraphNodeType::ExtrudeFaces => NodeDescriptor { - op_name, - label, - inputs: vec![ - in_mesh!("in_mesh"), - in_selection!("faces"), - in_scalar!("amount", 0.0, 0.0, 1.0), - ], - outputs: vec![out_mesh!("out_mesh")], - is_executable: false, - }, - GraphNodeType::ChamferVertices => NodeDescriptor { - op_name, - label, - inputs: vec![ - in_mesh!("in_mesh"), - in_selection!("vertices"), - in_scalar!("amount", 0.0, 0.0, 1.0), - ], - outputs: vec![out_mesh!("out_mesh")], - is_executable: false, - }, - GraphNodeType::MakeVector => NodeDescriptor { - op_name, - label, - inputs: vec![in_scalar!("x"), in_scalar!("y"), in_scalar!("z")], - outputs: vec![out_vector!("out_vec")], - is_executable: false, - }, - GraphNodeType::VectorMath => NodeDescriptor { - op_name, - label, - inputs: vec![ - in_enum!("vec_op", "ADD", "SUB"), - in_vector!("A", ZERO), - in_vector!("B", ZERO), - ], - outputs: vec![out_vector!("out_vec")], - is_executable: false, - }, - GraphNodeType::MergeMeshes => NodeDescriptor { - op_name, - label, - inputs: vec![in_mesh!("A"), in_mesh!("B")], - outputs: vec![out_mesh!("out_mesh")], - is_executable: false, - }, - GraphNodeType::ExportObj => NodeDescriptor { - op_name, - label, - inputs: vec![in_mesh!("mesh"), in_file!("export_path")], - outputs: vec![], - is_executable: true, - }, - } - } - - pub fn all_types() -> impl Iterator { - GraphNodeType::iter() - } - - pub fn type_label(&self) -> &'static str { - match self { - GraphNodeType::MakeBox => "Box", - GraphNodeType::MakeQuad => "Quad", - GraphNodeType::BevelEdges => "Bevel edges", - GraphNodeType::ExtrudeFaces => "Extrude faces", - GraphNodeType::ChamferVertices => "Chamfer vertices", - GraphNodeType::MakeVector => "Vector", - GraphNodeType::VectorMath => "Vector math", - GraphNodeType::MergeMeshes => "Merge meshes", - GraphNodeType::ExportObj => "OBJ Export", - } - } - - /// The op_name is used by the graph compiler in graph_compiler.rs to select - /// which PolyASM instructions to emit. - pub fn op_name(&self) -> &'static str { - match self { - GraphNodeType::MakeBox => "MakeBox", - GraphNodeType::MakeQuad => "MakeQuad", - GraphNodeType::BevelEdges => "BevelEdges", - GraphNodeType::ExtrudeFaces => "ExtrudeFaces", - GraphNodeType::ChamferVertices => "ChamferVertices", - GraphNodeType::MakeVector => "MakeVector", - GraphNodeType::VectorMath => "VectorMath", - GraphNodeType::MergeMeshes => "MergeMeshes", - GraphNodeType::ExportObj => "ExportObj", - } - } -} diff --git a/src/param_ui.rs b/src/param_ui.rs index afa6ea1..34c0e01 100644 --- a/src/param_ui.rs +++ b/src/param_ui.rs @@ -1,87 +1,25 @@ -use anyhow::Result; use egui::*; use crate::graph_types::*; -impl InputParam { - pub fn value_widget(&mut self, name: &str, ui: &mut Ui) { - match &mut self.value { - InputParamValue::Vector(vector) => { - ui.label(name); - - ui.horizontal(|ui| { - ui.label("x"); - ui.add(egui::DragValue::new(&mut vector.x).speed(0.1)); - ui.label("y"); - ui.add(egui::DragValue::new(&mut vector.y).speed(0.1)); - ui.label("z"); - ui.add(egui::DragValue::new(&mut vector.z).speed(0.1)); - }); - } - InputParamValue::Scalar(scalar) => { - let mut min = f32::NEG_INFINITY; - let mut max = f32::INFINITY; - for metadata in &self.metadata { - match metadata { - InputParamMetadata::MinMaxScalar { - min: min_val, - max: max_val, - } => { - min = *min_val; - max = *max_val; - } - } - } - ui.horizontal(|ui| { - ui.label(name); - ui.add(Slider::new(scalar, min..=max)); - }); - } - InputParamValue::Selection { text, selection } => { - if ui.text_edit_singleline(text).changed() { - *selection = text - .split(',') - .map(|x| { - x.parse::() - .map_err(|_| anyhow::anyhow!("Cannot parse number")) - }) - .collect::>>() - .ok(); - } - } - InputParamValue::None => { +impl Param { + pub fn value_widget(&self, name: &str, ui: &mut Ui) { + match &self.kind { + Descriptor::Info => { ui.label(name); } - InputParamValue::Enum { values, selection } => { - let selected = if let Some(selection) = selection { - values[*selection as usize].clone() - } else { - "".to_owned() - }; - ComboBox::from_label(name) - .selected_text(selected) - .show_ui(ui, |ui| { - for (idx, value) in values.iter().enumerate() { - ui.selectable_value(selection, Some(idx as u32), value); - } - }); - } - InputParamValue::NewFile { path } => { + Descriptor::Button => { ui.label(name); + } + Descriptor::Slider { min, max } => { ui.horizontal(|ui| { - if ui.button("Select").clicked() { - *path = rfd::FileDialog::new().save_file(); - } - if let Some(ref path) = path { - ui.label( - path.clone() - .into_os_string() - .into_string() - .unwrap_or_else(|_| "".to_owned()), - ); - } else { - ui.label("No file selected"); - } + ui.label(format!("{}", min)); + ui.add( + ProgressBar::new(0.0) + .desired_width(160.0) + .text(format!("{}", 0)), + ); + ui.label(format!("{}", max)); }); } }