Merge branch 'main' of github.com:setzer22/egui_node_graph

This commit is contained in:
Setzer22 2022-05-26 09:33:07 +02:00
commit c6c517644a
3 changed files with 147 additions and 118 deletions

View File

@ -1,4 +1,8 @@
on: [push] on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
name: CI name: CI

View File

@ -4,7 +4,7 @@ use crate::color_hex_utils::*;
use crate::utils::ColorUtils; use crate::utils::ColorUtils;
use super::*; use super::*;
use egui::epaint::RectShape; use egui::epaint::{CubicBezierShape, RectShape};
use egui::*; use egui::*;
pub type PortLocations = std::collections::HashMap<AnyParameterId, Pos2>; pub type PortLocations = std::collections::HashMap<AnyParameterId, Pos2>;
@ -150,22 +150,20 @@ where
} }
/* Draw connections */ /* Draw connections */
let connection_stroke = egui::Stroke {
width: 5.0,
color: color_from_hex("#efefef").unwrap(),
};
if let Some((_, ref locator)) = self.connection_in_progress { if let Some((_, ref locator)) = self.connection_in_progress {
let start_pos = port_locations[locator]; let start_pos = port_locations[locator];
ui.painter() let (src_pos, dst_pos) = match locator {
.line_segment([start_pos, cursor_pos], connection_stroke) AnyParameterId::Output(_) => (start_pos, cursor_pos),
AnyParameterId::Input(_) => (cursor_pos, start_pos),
};
draw_connection(ui.painter(), src_pos, dst_pos);
} }
for (input, output) in self.graph.iter_connections() { for (input, output) in self.graph.iter_connections() {
let src_pos = port_locations[&AnyParameterId::Output(output)]; let src_pos = port_locations[&AnyParameterId::Output(output)];
let dst_pos = port_locations[&AnyParameterId::Input(input)]; let dst_pos = port_locations[&AnyParameterId::Input(input)];
ui.painter() draw_connection(ui.painter(), src_pos, dst_pos);
.line_segment([src_pos, dst_pos], connection_stroke);
} }
/* Handle responses from drawing nodes */ /* Handle responses from drawing nodes */
@ -267,6 +265,26 @@ where
} }
} }
fn draw_connection(painter: &Painter, src_pos: Pos2, dst_pos: Pos2) {
let connection_stroke = egui::Stroke {
width: 5.0,
color: color_from_hex("#efefef").unwrap(),
};
let control_scale = ((dst_pos.x - src_pos.x) / 2.0).max(30.0);
let src_control = src_pos + Vec2::X * control_scale;
let dst_control = dst_pos - Vec2::X * control_scale;
let bezier = CubicBezierShape::from_points_stroke(
[src_pos, src_control, dst_control, dst_pos],
false,
Color32::TRANSPARENT,
connection_stroke,
);
painter.add(bezier);
}
impl<'a, NodeData, DataType, ValueType, UserResponse, UserState> impl<'a, NodeData, DataType, ValueType, UserResponse, UserState>
GraphNodeWidget<'a, NodeData, DataType, ValueType> GraphNodeWidget<'a, NodeData, DataType, ValueType>
where where

View File

@ -142,45 +142,41 @@ impl NodeTemplateTrait for MyNodeTemplate {
// The nodes are created empty by default. This function needs to take // The nodes are created empty by default. This function needs to take
// care of creating the desired inputs and outputs based on the template // care of creating the desired inputs and outputs based on the template
// We define some macros here to avoid boilerplate. Note that this is // We define some closures here to avoid boilerplate. Note that this is
// entirely optional. // entirely optional.
macro_rules! input { let input_scalar = |graph: &mut MyGraph, name: &str| {
(scalar $name:expr) => { graph.add_input_param(
graph.add_input_param( node_id,
node_id, name.to_string(),
$name.to_string(), MyDataType::Scalar,
MyDataType::Scalar, MyValueType::Scalar { value: 0.0 },
MyValueType::Scalar { value: 0.0 }, InputParamKind::ConnectionOrConstant,
InputParamKind::ConnectionOrConstant, true,
true, );
); };
}; let input_vector = |graph: &mut MyGraph, name: &str| {
(vector $name:expr) => { graph.add_input_param(
graph.add_input_param( node_id,
node_id, name.to_string(),
$name.to_string(), MyDataType::Vec2,
MyDataType::Vec2, MyValueType::Vec2 {
MyValueType::Vec2 { value: egui::vec2(0.0, 0.0),
value: egui::vec2(0.0, 0.0), },
}, InputParamKind::ConnectionOrConstant,
InputParamKind::ConnectionOrConstant, true,
true, );
); };
};
}
macro_rules! output { let output_scalar = |graph: &mut MyGraph, name: &str| {
(scalar $name:expr) => { graph.add_output_param(node_id, name.to_string(), MyDataType::Scalar);
graph.add_output_param(node_id, $name.to_string(), MyDataType::Scalar); };
}; let output_vector = |graph: &mut MyGraph, name: &str| {
(vector $name:expr) => { graph.add_output_param(node_id, name.to_string(), MyDataType::Vec2);
graph.add_output_param(node_id, $name.to_string(), MyDataType::Vec2); };
};
}
match self { match self {
MyNodeTemplate::AddScalar => { MyNodeTemplate::AddScalar => {
// The first input param doesn't use the macro so we can comment // The first input param doesn't use the closure so we can comment
// it in more detail. // it in more detail.
graph.add_input_param( graph.add_input_param(
node_id, node_id,
@ -197,37 +193,37 @@ impl NodeTemplateTrait for MyNodeTemplate {
InputParamKind::ConnectionOrConstant, InputParamKind::ConnectionOrConstant,
true, true,
); );
input!(scalar "B"); input_scalar(graph, "B");
output!(scalar "out"); output_scalar(graph, "out");
} }
MyNodeTemplate::SubtractScalar => { MyNodeTemplate::SubtractScalar => {
input!(scalar "A"); input_scalar(graph, "A");
input!(scalar "B"); input_scalar(graph, "B");
output!(scalar "out"); output_scalar(graph, "out");
} }
MyNodeTemplate::VectorTimesScalar => { MyNodeTemplate::VectorTimesScalar => {
input!(scalar "scalar"); input_scalar(graph, "scalar");
input!(vector "vector"); input_vector(graph, "vector");
output!(vector "out"); output_vector(graph, "out");
} }
MyNodeTemplate::AddVector => { MyNodeTemplate::AddVector => {
input!(vector "v1"); input_vector(graph, "v1");
input!(vector "v2"); input_vector(graph, "v2");
output!(vector "out"); output_vector(graph, "out");
} }
MyNodeTemplate::SubtractVector => { MyNodeTemplate::SubtractVector => {
input!(vector "v1"); input_vector(graph, "v1");
input!(vector "v2"); input_vector(graph, "v2");
output!(vector "out"); output_vector(graph, "out");
} }
MyNodeTemplate::MakeVector => { MyNodeTemplate::MakeVector => {
input!(scalar "x"); input_scalar(graph, "x");
input!(scalar "y"); input_scalar(graph, "y");
output!(vector "out"); output_vector(graph, "out");
} }
MyNodeTemplate::MakeScalar => { MyNodeTemplate::MakeScalar => {
input!(scalar "value"); input_scalar(graph, "value");
output!(scalar "out"); output_scalar(graph, "out");
} }
} }
} }
@ -400,42 +396,34 @@ pub fn evaluate_node(
node_id: NodeId, node_id: NodeId,
outputs_cache: &mut OutputsCache, outputs_cache: &mut OutputsCache,
) -> anyhow::Result<MyValueType> { ) -> anyhow::Result<MyValueType> {
// Similar to when creating node types above, we define two macros for // To solve a similar problem as creating node types above, we define an
// convenience. They may be overkill for this small example, but something // Evaluator as a convenience. It may be overkill for this small example,
// like this makes the code much more readable when the number of nodes // but something like this makes the code much more readable when the
// starts growing. // number of nodes starts growing.
macro_rules! input {
(Vec2 $name:expr) => {
evaluate_input(graph, node_id, $name, outputs_cache)?.try_to_vec2()?
};
(Scalar $name:expr) => {
evaluate_input(graph, node_id, $name, outputs_cache)?.try_to_scalar()?
};
}
macro_rules! output { struct Evaluator<'a> {
(Vec2 $name:expr => $value:expr) => {{ graph: &'a MyGraph,
let out = MyValueType::Vec2 { value: $value }; outputs_cache: &'a mut OutputsCache,
populate_output(graph, outputs_cache, node_id, $name, out)?; node_id: NodeId,
Ok(out)
}};
(Scalar $name:expr => $value:expr) => {{
let out = MyValueType::Scalar { value: $value };
populate_output(graph, outputs_cache, node_id, $name, out)?;
Ok(out)
}};
} }
impl<'a> Evaluator<'a> {
let node = &graph[node_id]; fn new(graph: &'a MyGraph, outputs_cache: &'a mut OutputsCache, node_id: NodeId) -> Self {
match node.user_data.template { Self {
MyNodeTemplate::AddScalar => { graph,
outputs_cache,
node_id,
}
}
fn evaluate_input(&mut self, name: &str) -> anyhow::Result<MyValueType> {
// Calling `evaluate_input` recursively evaluates other nodes in the // Calling `evaluate_input` recursively evaluates other nodes in the
// graph until the input value for a paramater has been computed. // graph until the input value for a paramater has been computed.
// This first call doesn't use the `input!` macro to illustrate what evaluate_input(self.graph, self.node_id, name, self.outputs_cache)
// is going on underneath. }
let a = evaluate_input(graph, node_id, "A", outputs_cache)?.try_to_scalar()?; fn populate_output(
let b = evaluate_input(graph, node_id, "B", outputs_cache)?.try_to_scalar()?; &mut self,
name: &str,
value: MyValueType,
) -> anyhow::Result<MyValueType> {
// After computing an output, we don't just return it, but we also // After computing an output, we don't just return it, but we also
// populate the outputs cache with it. This ensures the evaluation // populate the outputs cache with it. This ensures the evaluation
// only ever computes an output once. // only ever computes an output once.
@ -449,39 +437,58 @@ pub fn evaluate_node(
// //
// Note that this is just one possible semantic interpretation of // Note that this is just one possible semantic interpretation of
// the graphs, you can come up with your own evaluation semantics! // the graphs, you can come up with your own evaluation semantics!
let out = MyValueType::Scalar { value: a + b }; populate_output(self.graph, self.outputs_cache, self.node_id, name, value)
populate_output(graph, outputs_cache, node_id, "out", out)?; }
Ok(out) fn input_vector(&mut self, name: &str) -> anyhow::Result<egui::Vec2> {
self.evaluate_input(name)?.try_to_vec2()
}
fn input_scalar(&mut self, name: &str) -> anyhow::Result<f32> {
self.evaluate_input(name)?.try_to_scalar()
}
fn output_vector(&mut self, name: &str, value: egui::Vec2) -> anyhow::Result<MyValueType> {
self.populate_output(name, MyValueType::Vec2 { value })
}
fn output_scalar(&mut self, name: &str, value: f32) -> anyhow::Result<MyValueType> {
self.populate_output(name, MyValueType::Scalar { value })
}
}
let node = &graph[node_id];
let mut evaluator = Evaluator::new(graph, outputs_cache, node_id);
match node.user_data.template {
MyNodeTemplate::AddScalar => {
let a = evaluator.input_scalar("A")?;
let b = evaluator.input_scalar("B")?;
evaluator.output_scalar("out", a + b)
} }
MyNodeTemplate::SubtractScalar => { MyNodeTemplate::SubtractScalar => {
// Using the macros, the code gets as succint as it gets let a = evaluator.input_scalar("A")?;
let a = input!(Scalar "A"); let b = evaluator.input_scalar("B")?;
let b = input!(Scalar "B"); evaluator.output_scalar("out", a - b)
output!(Scalar "out" => a - b)
} }
MyNodeTemplate::VectorTimesScalar => { MyNodeTemplate::VectorTimesScalar => {
let scalar = input!(Scalar "scalar"); let scalar = evaluator.input_scalar("scalar")?;
let vector = input!(Vec2 "vector"); let vector = evaluator.input_vector("vector")?;
output!(Vec2 "out" => vector * scalar) evaluator.output_vector("out", vector * scalar)
} }
MyNodeTemplate::AddVector => { MyNodeTemplate::AddVector => {
let v1 = input!(Vec2 "v1"); let v1 = evaluator.input_vector("v1")?;
let v2 = input!(Vec2 "v2"); let v2 = evaluator.input_vector("v2")?;
output!(Vec2 "out" => v1 + v2) evaluator.output_vector("out", v1 + v2)
} }
MyNodeTemplate::SubtractVector => { MyNodeTemplate::SubtractVector => {
let v1 = input!(Vec2 "v1"); let v1 = evaluator.input_vector("v1")?;
let v2 = input!(Vec2 "v2"); let v2 = evaluator.input_vector("v2")?;
output!(Vec2 "out" => v1 - v2) evaluator.output_vector("out", v1 - v2)
} }
MyNodeTemplate::MakeVector => { MyNodeTemplate::MakeVector => {
let x = input!(Scalar "x"); let x = evaluator.input_scalar("x")?;
let y = input!(Scalar "y"); let y = evaluator.input_scalar("y")?;
output!(Vec2 "out" => egui::vec2(x, y)) evaluator.output_vector("out", egui::vec2(x, y))
} }
MyNodeTemplate::MakeScalar => { MyNodeTemplate::MakeScalar => {
let value = input!(Scalar "value"); let value = evaluator.input_scalar("value")?;
output!(Scalar "out" => value) evaluator.output_scalar("out", value)
} }
} }
} }
@ -492,10 +499,10 @@ fn populate_output(
node_id: NodeId, node_id: NodeId,
param_name: &str, param_name: &str,
value: MyValueType, value: MyValueType,
) -> anyhow::Result<()> { ) -> anyhow::Result<MyValueType> {
let output_id = graph[node_id].get_output(param_name)?; let output_id = graph[node_id].get_output(param_name)?;
outputs_cache.insert(output_id, value); outputs_cache.insert(output_id, value);
Ok(()) Ok(value)
} }
// Evaluates the input value of // Evaluates the input value of