Display device node

This commit is contained in:
hodasemi 2022-01-30 19:56:14 +01:00
parent 1244041861
commit b4625849f1
13 changed files with 298 additions and 600 deletions

View file

@ -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" }

View file

@ -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<EventType, Vec<InputEventKind>>,
}
@ -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<Vec<(String, Descriptor)>> = 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::<Vec<Vec<(String, Descriptor)>>>(),
);
inputs.insert(0, (format!("Path: {}", self.path), Descriptor::Info));
NodeDescriptor {
op_name: self.name().to_string(),
inputs,
outputs,
}
}
fn flatten<T>(vv: Vec<Vec<T>>) -> Vec<T> {
vv.into_iter().flatten().collect()
}
}
impl std::fmt::Debug for Device {

View file

@ -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<NodeId>,

View file

@ -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));
}
}
}

View file

@ -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<OutputId> {
pub fn remove_connection(&mut self, input_id: ID) -> Option<ID> {
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<Item = (InputId, OutputId)> + '_ {
pub fn iter_connections(&self) -> impl Iterator<Item = (ID, ID)> + '_ {
self.connections.iter().map(|(o, i)| (*o, *i))
}
pub fn connection(&self, input: InputId) -> Option<OutputId> {
pub fn connection(&self, input: ID) -> Option<ID> {
self.connections.get(&input).copied()
}
pub fn any_param_type(&self, param: AnyParameterId) -> Result<DataType> {
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<Item = &InputParam> + 'a {
self.input_ids().map(|id| graph.get_input(id))
pub fn inputs<'a>(&'a self, graph: &'a Graph) -> impl Iterator<Item = &Param> + 'a {
self.input_ids().map(|id| graph.input(id))
}
pub fn outputs<'a>(&'a self, graph: &'a Graph) -> impl Iterator<Item = &OutputParam> + 'a {
self.output_ids().map(|id| graph.get_output(id))
pub fn outputs<'a>(&'a self, graph: &'a Graph) -> impl Iterator<Item = &Param> + 'a {
self.output_ids().map(|id| graph.output(id))
}
pub fn input_ids(&self) -> impl Iterator<Item = InputId> + '_ {
pub fn input_ids(&self) -> impl Iterator<Item = ID> + '_ {
self.inputs.iter().map(|(_name, id)| *id)
}
pub fn output_ids(&self) -> impl Iterator<Item = OutputId> + '_ {
pub fn output_ids(&self) -> impl Iterator<Item = ID> + '_ {
self.outputs.iter().map(|(_name, id)| *id)
}
pub fn get_input(&self, name: &str) -> Result<InputId> {
pub fn get_input(&self, name: &str) -> Result<ID> {
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<OutputId> {
pub fn get_output(&self, name: &str) -> Result<ID> {
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)
// }
}

View file

@ -5,16 +5,16 @@ use crate::color_hex_utils::*;
use crate::graph_types::*;
use crate::id_types::*;
pub type PortLocations = std::collections::HashMap<AnyParameterId, Pos2>;
pub type PortLocations = std::collections::HashMap<ID, Pos2>;
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<DrawGraphNodeResponse> {
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(&param_name, ui);
graph.input(param).value_widget(&param_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<DrawGraphNodeResponse>,
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
}
}
}

View file

@ -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<f32>),
Scalar(f32),
Selection {
text: String,
selection: Option<Vec<u32>>,
},
/// Used for parameters that can't have a value because they only accept
/// connections.
None,
Enum {
values: Vec<String>,
selection: Option<u32>,
},
NewFile {
path: Option<std::path::PathBuf>,
},
}
/// 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<InputParamMetadata>,
/// 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<NodeId, Node>,
pub inputs: SlotMap<InputId, InputParam>,
pub outputs: SlotMap<OutputId, OutputParam>,
pub inputs: SlotMap<ID, Param>,
pub outputs: SlotMap<ID, Param>,
// Connects the input of a node, to the output of its predecessor that
// produces it
pub connections: HashMap<InputId, OutputId>,
pub connections: HashMap<ID, ID>,
}
pub enum InputDescriptor {
Vector { default: Vec3<f32> },
Mesh,
Selection,
Scalar { default: f32, min: f32, max: f32 },
Enum { values: Vec<String> },
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)>,
}

View file

@ -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; }

View file

@ -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);

View file

@ -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<T> = 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);
});
}

View file

@ -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<GraphNodeType> {
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
}
}

View file

@ -1,210 +0,0 @@
use nalgebra::Vector3 as Vec3;
use strum::IntoEnumIterator;
use crate::graph_types::*;
const ONE: Vec3<f32> = Vec3::new(1.0, 1.0, 1.0);
const ZERO: Vec3<f32> = Vec3::new(0.0, 0.0, 0.0);
const X: Vec3<f32> = Vec3::new(1.0, 0.0, 0.0);
const Y: Vec3<f32> = 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<Item = GraphNodeType> {
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",
}
}
}

View file

@ -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::<u32>()
.map_err(|_| anyhow::anyhow!("Cannot parse number"))
})
.collect::<Result<Vec<_>>>()
.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(|_| "<Invalid string>".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));
});
}
}