mirror of
https://github.com/eliasstepanik/egui_node_graph.git
synced 2026-01-22 11:08:27 +00:00
Merge branch 'main' of github.com:setzer22/egui_node_graph
This commit is contained in:
commit
c6c517644a
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@ -1,4 +1,8 @@
|
|||||||
on: [push]
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
|
||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user