mirror of
https://github.com/eliasstepanik/egui_node_graph.git
synced 2026-01-11 22:08:28 +00:00
Documentation, readme
This commit is contained in:
parent
db4c4f0f9f
commit
e16968256a
64
README.md
64
README.md
@ -1,2 +1,62 @@
|
|||||||
# egui_node_graph
|
# Egui Node Graph
|
||||||
Build your node graph applications in Rust, using egui
|
> There you have it! Now go build your next awesome node graph thing in Rust 🦀
|
||||||
|
|
||||||
|
[](https://crates.io/crates/egui_node_graph)
|
||||||
|
[](https://docs.rs/egui_node_graph)
|
||||||
|

|
||||||
|
[](https://github.com/rust-secure-code/safety-dance/)
|
||||||
|
|
||||||
|
**Egui node graph** is a featureful, customizable library to create node graph
|
||||||
|
applications using [egui](https://github.com/emilk/egui). The library takes care
|
||||||
|
of presenting a node graph to your users, and allows customizing many aspects of
|
||||||
|
the interaction, creating the semantics you want for your specific application.
|
||||||
|
|
||||||
|
## Features and goals
|
||||||
|
This crate is meant to be a solid base for anyone wanting to expose a node graph
|
||||||
|
interface to their users. Its main design goal is to be completely agnostic to
|
||||||
|
the semantics of the graph, be it game logic, audio production, dialog trees,
|
||||||
|
shader generators... we have you covered!
|
||||||
|
|
||||||
|
The purpose of this library is to draw your graphs and handle the common user
|
||||||
|
interaction, like adding new nodes, moving nodes or creating connections. All
|
||||||
|
the additional functionality is provided by the user by means of custom user
|
||||||
|
types implementing several traits.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
To see a node graph in action, simply clone this repository and launch the
|
||||||
|
example using `cargo run`. This should open a window with an empty canvas. Right
|
||||||
|
clicking anywhere on the screen will bring up the *node finder* menu.
|
||||||
|
|
||||||
|
The [application code in the example](https://github.com/setzer22/egui_node_graph/blob/main/egui_node_graph_example/src/app.rs)
|
||||||
|
is thoroughly commented and serves as a good introduction to embedding this
|
||||||
|
library in your egui project.
|
||||||
|
|
||||||
|
## A note on API visibility
|
||||||
|
Contrary to the general tendency in the Rust ecosytem, this library exposes all
|
||||||
|
types and fields that may be remotely relevant to a user as public. This is done
|
||||||
|
with the intent to be as flexible as possible, so no implementation details are
|
||||||
|
hidden from users who wish to tinker with the internals. Note that this crate
|
||||||
|
forbids use of `unsafe` so there is no risk of introducing UB by breaking any of
|
||||||
|
its invariants.
|
||||||
|
|
||||||
|
That being said, for the most typical use cases, you will want to stick to the
|
||||||
|
customization options this crate provides for you: The generic types in the
|
||||||
|
`GraphEditorState` object and their associated traits are the main API, all of
|
||||||
|
the other types and fields in this crate should be considered an implementation
|
||||||
|
detail. The example project contains a detailed explanation of all the
|
||||||
|
customization options and how are users supposed to interact with this crate.
|
||||||
|
|
||||||
|
Finally, this does not change the fact that this crate follows semantic
|
||||||
|
versioning, as is usual in the Rust ecosystem. Any change to a public field is
|
||||||
|
still considered breaking.
|
||||||
|
|
||||||
|
## Use cases
|
||||||
|
|
||||||
|
**Egui node graph** is the library powering the graph user interface of
|
||||||
|
[Blackjack](https://github.com/setzer22/blackjack), a 3d procedural modelling
|
||||||
|
software built in Rust using egui, rend3 and wgpu.
|
||||||
|

|
||||||
|
Are you using this crate for something cool? Add yourself to this section by sending a PR!
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
Contributions are welcome! Before writing a PR, please get in touch by filing an issue 😄
|
||||||
@ -9,8 +9,6 @@ use egui::*;
|
|||||||
|
|
||||||
pub type PortLocations = std::collections::HashMap<AnyParameterId, Pos2>;
|
pub type PortLocations = std::collections::HashMap<AnyParameterId, Pos2>;
|
||||||
|
|
||||||
pub trait UserResponseTrait: Clone + Copy + std::fmt::Debug + PartialEq + Eq {}
|
|
||||||
|
|
||||||
/// Nodes communicate certain events to the parent graph when drawn. There is
|
/// Nodes communicate certain events to the parent graph when drawn. There is
|
||||||
/// one special `User` variant which can be used by users as the return value
|
/// one special `User` variant which can be used by users as the return value
|
||||||
/// when executing some custom actions in the UI of the node.
|
/// when executing some custom actions in the UI of the node.
|
||||||
@ -43,52 +41,20 @@ pub struct GraphNodeWidget<'a, NodeData, DataType, ValueType> {
|
|||||||
pub pan: egui::Vec2,
|
pub pan: egui::Vec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait InputParamWidget {
|
impl<NodeData, DataType, ValueType, NodeTemplate, UserResponse, UserState>
|
||||||
fn value_widget(&mut self, param_name: &str, ui: &mut Ui);
|
GraphEditorState<NodeData, DataType, ValueType, NodeTemplate, UserState>
|
||||||
}
|
|
||||||
|
|
||||||
pub trait DataTypeTrait: PartialEq + Eq {
|
|
||||||
// The associated port color of this datatype
|
|
||||||
fn data_type_color(&self) -> egui::Color32;
|
|
||||||
|
|
||||||
// The name of this datatype
|
|
||||||
fn name(&self) -> &str;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait NodeDataTrait
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
type Response;
|
|
||||||
type UserState;
|
|
||||||
|
|
||||||
/// Additional UI elements to draw in the nodes, after the parameters.
|
|
||||||
fn bottom_ui<DataType, ValueType>(
|
|
||||||
&self,
|
|
||||||
ui: &mut Ui,
|
|
||||||
node_id: NodeId,
|
|
||||||
graph: &Graph<Self, DataType, ValueType>,
|
|
||||||
user_state: &Self::UserState,
|
|
||||||
|
|
||||||
) -> Vec<NodeResponse<Self::Response>>
|
|
||||||
where
|
|
||||||
Self::Response: UserResponseTrait;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<NodeData, DataType, ValueType, NodeKind, UserResponse, UserState>
|
|
||||||
GraphEditorState<NodeData, DataType, ValueType, NodeKind, UserState>
|
|
||||||
where
|
where
|
||||||
NodeData: NodeDataTrait<Response = UserResponse, UserState = UserState>,
|
NodeData: NodeDataTrait<Response = UserResponse, UserState = UserState>,
|
||||||
UserResponse: UserResponseTrait,
|
UserResponse: UserResponseTrait,
|
||||||
ValueType: InputParamWidget,
|
ValueType: WidgetValueTrait,
|
||||||
NodeKind: NodeKindTrait<NodeData = NodeData, DataType = DataType, ValueType = ValueType>,
|
NodeTemplate: NodeTemplateTrait<NodeData = NodeData, DataType = DataType, ValueType = ValueType>,
|
||||||
DataType: DataTypeTrait,
|
DataType: DataTypeTrait,
|
||||||
{
|
{
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn draw_graph_editor(
|
pub fn draw_graph_editor(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &CtxRef,
|
ctx: &CtxRef,
|
||||||
all_kinds: impl NodeKindIter<Item = NodeKind>,
|
all_kinds: impl NodeTemplateIter<Item = NodeTemplate>,
|
||||||
) -> GraphResponse<UserResponse> {
|
) -> GraphResponse<UserResponse> {
|
||||||
let mouse = &ctx.input().pointer;
|
let mouse = &ctx.input().pointer;
|
||||||
let cursor_pos = mouse.hover_pos().unwrap_or(Pos2::ZERO);
|
let cursor_pos = mouse.hover_pos().unwrap_or(Pos2::ZERO);
|
||||||
@ -277,7 +243,7 @@ impl<'a, NodeData, DataType, ValueType, UserResponse, UserState>
|
|||||||
where
|
where
|
||||||
NodeData: NodeDataTrait<Response = UserResponse, UserState = UserState>,
|
NodeData: NodeDataTrait<Response = UserResponse, UserState = UserState>,
|
||||||
UserResponse: UserResponseTrait,
|
UserResponse: UserResponseTrait,
|
||||||
ValueType: InputParamWidget,
|
ValueType: WidgetValueTrait,
|
||||||
DataType: DataTypeTrait,
|
DataType: DataTypeTrait,
|
||||||
{
|
{
|
||||||
pub const MAX_NODE_SIZE: [f32; 2] = [200.0, 200.0];
|
pub const MAX_NODE_SIZE: [f32; 2] = [200.0, 200.0];
|
||||||
|
|||||||
91
egui_node_graph/src/graph.rs
Normal file
91
egui_node_graph/src/graph.rs
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "persistence")]
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// A node inside the [`Graph`]. Nodes have input and output parameters, stored
|
||||||
|
/// as ids. They also contain a custom `NodeData` struct with whatever data the
|
||||||
|
/// user wants to store per-node.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(Serialize, Deserialize))]
|
||||||
|
pub struct Node<NodeData> {
|
||||||
|
pub id: NodeId,
|
||||||
|
pub label: String,
|
||||||
|
pub inputs: Vec<(String, InputId)>,
|
||||||
|
pub outputs: Vec<(String, OutputId)>,
|
||||||
|
pub user_data: NodeData,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The three kinds of input params. These describe how the graph must behave
|
||||||
|
/// with respect to inline widgets and connections for this parameter.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(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,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "persistence")]
|
||||||
|
fn shown_inline_default() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An input parameter. Input parameters are inside a node, and represent data
|
||||||
|
/// that this node receives. Unlike their [`OutputParam`] counterparts, input
|
||||||
|
/// parameters also display an inline widget which allows setting its "value".
|
||||||
|
/// The `DataType` generic parameter is used to restrict the range of input
|
||||||
|
/// connections for this parameter, and the `ValueType` is use to represent the
|
||||||
|
/// data for the inline widget (i.e. constant) value.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(Serialize, Deserialize))]
|
||||||
|
pub struct InputParam<DataType, ValueType> {
|
||||||
|
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: ValueType,
|
||||||
|
/// The input kind. See [`InputParamKind`]
|
||||||
|
pub kind: InputParamKind,
|
||||||
|
/// Back-reference to the node containing this parameter.
|
||||||
|
pub node: NodeId,
|
||||||
|
/// When true, the node is shown inline inside the node graph.
|
||||||
|
#[cfg_attr(feature = "persistence", serde(default = "shown_inline_default"))]
|
||||||
|
pub shown_inline: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An output parameter. Output parameters are inside a node, and represent the
|
||||||
|
/// data that the node produces. Output parameters can be linked to the input
|
||||||
|
/// parameters of other nodes. Unlike an [`InputParam`], output parameters
|
||||||
|
/// cannot have a constant inline value.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(Serialize, Deserialize))]
|
||||||
|
pub struct OutputParam<DataType> {
|
||||||
|
pub id: OutputId,
|
||||||
|
/// Back-reference to the node containing this parameter.
|
||||||
|
pub node: NodeId,
|
||||||
|
pub typ: DataType,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The graph, containing nodes, input parameters and output parameters. Because
|
||||||
|
/// graphs are full of self-referential structures, this type uses the `slotmap`
|
||||||
|
/// crate to represent all the inner references in the data.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(Serialize, Deserialize))]
|
||||||
|
pub struct Graph<NodeData, DataType, ValueType> {
|
||||||
|
/// The [`Node`]s of the graph
|
||||||
|
pub nodes: SlotMap<NodeId, Node<NodeData>>,
|
||||||
|
/// The [`InputParam`]s of the graph
|
||||||
|
pub inputs: SlotMap<InputId, InputParam<DataType, ValueType>>,
|
||||||
|
/// The [`OutputParam`]s of the graph
|
||||||
|
pub outputs: SlotMap<OutputId, OutputParam<DataType>>,
|
||||||
|
// Connects the input of a node, to the output of its predecessor that
|
||||||
|
// produces it
|
||||||
|
pub connections: SecondaryMap<InputId, OutputId>,
|
||||||
|
}
|
||||||
@ -1,96 +1,46 @@
|
|||||||
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
use slotmap::{SecondaryMap, SlotMap};
|
use slotmap::{SecondaryMap, SlotMap};
|
||||||
|
|
||||||
|
pub type SVec<T> = smallvec::SmallVec<[T; 4]>;
|
||||||
|
|
||||||
|
/// Contains the main definitions for the node graph model.
|
||||||
|
pub mod graph;
|
||||||
|
pub use graph::*;
|
||||||
|
|
||||||
|
/// Type declarations for the different id types (node, input, output)
|
||||||
pub mod id_type;
|
pub mod id_type;
|
||||||
pub use id_type::*;
|
pub use id_type::*;
|
||||||
|
|
||||||
|
/// Implements the index trait for the Graph type, allowing indexing by all
|
||||||
|
/// three id types
|
||||||
pub mod index_impls;
|
pub mod index_impls;
|
||||||
|
|
||||||
|
/// Implementing the main methods for the `Graph`
|
||||||
pub mod graph_impls;
|
pub mod graph_impls;
|
||||||
|
|
||||||
|
/// Custom error types, crate-wide
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub use error::*;
|
pub use error::*;
|
||||||
|
|
||||||
|
/// The main struct in the library, contains all the necessary state to draw the
|
||||||
|
/// UI graph
|
||||||
pub mod ui_state;
|
pub mod ui_state;
|
||||||
pub use ui_state::*;
|
pub use ui_state::*;
|
||||||
|
|
||||||
|
/// The node finder is a tiny widget allowing to create new node types
|
||||||
pub mod node_finder;
|
pub mod node_finder;
|
||||||
pub use node_finder::*;
|
pub use node_finder::*;
|
||||||
|
|
||||||
|
/// The inner details of the egui implementation. Most egui code lives here.
|
||||||
pub mod editor_ui;
|
pub mod editor_ui;
|
||||||
pub use editor_ui::*;
|
pub use editor_ui::*;
|
||||||
|
|
||||||
|
/// Several traits that must be implemented by the user to customize the
|
||||||
|
/// behavior of this library.
|
||||||
|
pub mod traits;
|
||||||
|
pub use traits::*;
|
||||||
|
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
mod color_hex_utils;
|
mod color_hex_utils;
|
||||||
|
|
||||||
#[cfg(feature = "persistence")]
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
pub type SVec<T> = smallvec::SmallVec<[T; 4]>;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
#[cfg_attr(feature = "persistence", derive(Serialize, Deserialize))]
|
|
||||||
pub struct Node<NodeData> {
|
|
||||||
pub id: NodeId,
|
|
||||||
pub label: String,
|
|
||||||
pub inputs: Vec<(String, InputId)>,
|
|
||||||
pub outputs: Vec<(String, OutputId)>,
|
|
||||||
pub user_data: NodeData,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// There are three kinds of input params
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
#[cfg_attr(feature = "persistence", derive(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,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "persistence")]
|
|
||||||
fn shown_inline_default() -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
#[cfg_attr(feature = "persistence", derive(Serialize, Deserialize))]
|
|
||||||
pub struct InputParam<DataType, ValueType> {
|
|
||||||
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: ValueType,
|
|
||||||
/// The input kind. See [`InputParamKind`]
|
|
||||||
pub kind: InputParamKind,
|
|
||||||
/// Back-reference to the node containing this parameter.
|
|
||||||
pub node: NodeId,
|
|
||||||
/// When true, the node is shown inline inside the node graph.
|
|
||||||
#[cfg_attr(feature = "persistence", serde(default = "shown_inline_default"))]
|
|
||||||
pub shown_inline: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
#[cfg_attr(feature = "persistence", derive(Serialize, Deserialize))]
|
|
||||||
pub struct OutputParam<DataType> {
|
|
||||||
pub id: OutputId,
|
|
||||||
/// Back-reference to the node containing this parameter.
|
|
||||||
pub node: NodeId,
|
|
||||||
pub typ: DataType,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
#[cfg_attr(feature = "persistence", derive(Serialize, Deserialize))]
|
|
||||||
pub struct Graph<NodeData, DataType, ValueType> {
|
|
||||||
pub nodes: SlotMap<NodeId, Node<NodeData>>,
|
|
||||||
pub inputs: SlotMap<InputId, InputParam<DataType, ValueType>>,
|
|
||||||
pub outputs: SlotMap<OutputId, OutputParam<DataType>>,
|
|
||||||
// Connects the input of a node, to the output of its predecessor that
|
|
||||||
// produces it
|
|
||||||
connections: SecondaryMap<InputId, OutputId>,
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,49 +1,20 @@
|
|||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use crate::{color_hex_utils::*, Graph, NodeId};
|
use crate::{color_hex_utils::*, NodeTemplateIter, NodeTemplateTrait};
|
||||||
|
|
||||||
use egui::*;
|
use egui::*;
|
||||||
|
|
||||||
pub struct NodeFinder<NodeKind> {
|
pub struct NodeFinder<NodeTemplate> {
|
||||||
query: String,
|
pub query: String,
|
||||||
/// Reset every frame. When set, the node finder will be moved at that position
|
/// Reset every frame. When set, the node finder will be moved at that position
|
||||||
pub position: Option<Pos2>,
|
pub position: Option<Pos2>,
|
||||||
pub just_spawned: bool,
|
pub just_spawned: bool,
|
||||||
_phantom: PhantomData<NodeKind>,
|
_phantom: PhantomData<NodeTemplate>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait NodeKindIter {
|
impl<NodeTemplate, NodeData> NodeFinder<NodeTemplate>
|
||||||
type Item;
|
|
||||||
fn all_kinds(&self) -> Box<dyn Iterator<Item = &Self::Item> + '_>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait NodeKindTrait: Clone {
|
|
||||||
type NodeData;
|
|
||||||
type DataType;
|
|
||||||
type ValueType;
|
|
||||||
|
|
||||||
/// Returns a descriptive name for the node kind, used in the node finder.
|
|
||||||
fn node_finder_label(&self) -> &str;
|
|
||||||
|
|
||||||
/// Returns a descriptive name for the node kind, used in the graph.
|
|
||||||
fn node_graph_label(&self) -> String;
|
|
||||||
|
|
||||||
/// Returns the user data for this node kind.
|
|
||||||
fn user_data(&self) -> Self::NodeData;
|
|
||||||
|
|
||||||
/// This function is run when this node kind gets added to the graph. The
|
|
||||||
/// node will be empty by default, and this function can be used to fill its
|
|
||||||
/// parameters.
|
|
||||||
fn build_node(
|
|
||||||
&self,
|
|
||||||
graph: &mut Graph<Self::NodeData, Self::DataType, Self::ValueType>,
|
|
||||||
node_id: NodeId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<NodeKind, NodeData> NodeFinder<NodeKind>
|
|
||||||
where
|
where
|
||||||
NodeKind: NodeKindTrait<NodeData = NodeData>,
|
NodeTemplate: NodeTemplateTrait<NodeData = NodeData>,
|
||||||
{
|
{
|
||||||
pub fn new_at(pos: Pos2) -> Self {
|
pub fn new_at(pos: Pos2) -> Self {
|
||||||
NodeFinder {
|
NodeFinder {
|
||||||
@ -60,8 +31,8 @@ where
|
|||||||
pub fn show(
|
pub fn show(
|
||||||
&mut self,
|
&mut self,
|
||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
all_kinds: impl NodeKindIter<Item = NodeKind>,
|
all_kinds: impl NodeTemplateIter<Item = NodeTemplate>,
|
||||||
) -> Option<NodeKind> {
|
) -> Option<NodeTemplate> {
|
||||||
let background_color = color_from_hex("#3f3f3f").unwrap();
|
let background_color = color_from_hex("#3f3f3f").unwrap();
|
||||||
let text_color = color_from_hex("#fefefe").unwrap();
|
let text_color = color_from_hex("#fefefe").unwrap();
|
||||||
|
|
||||||
|
|||||||
85
egui_node_graph/src/traits.rs
Normal file
85
egui_node_graph/src/traits.rs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// This trait must be implemented by the `ValueType` generic parameter of the
|
||||||
|
/// [`Graph`]. The trait allows drawing custom inline widgets for the different
|
||||||
|
/// types of the node graph.
|
||||||
|
pub trait WidgetValueTrait {
|
||||||
|
fn value_widget(&mut self, param_name: &str, ui: &mut egui::Ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This trait must be implemented by the `DataType` generic parameter of the
|
||||||
|
/// [`Graph`]. This trait tells the library how to visually expose data types
|
||||||
|
/// to the user.
|
||||||
|
pub trait DataTypeTrait: PartialEq + Eq {
|
||||||
|
// The associated port color of this datatype
|
||||||
|
fn data_type_color(&self) -> egui::Color32;
|
||||||
|
|
||||||
|
// The name of this datatype
|
||||||
|
fn name(&self) -> &str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This trait must be implemented for the `NodeData` generic parameter of the
|
||||||
|
/// [`Graph`]. This trait allows customizing some aspects of the node drawing.
|
||||||
|
pub trait NodeDataTrait
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
/// Must be set to the custom user `NodeResponse` type
|
||||||
|
type Response;
|
||||||
|
/// Must be set to the custom user `UserState` type
|
||||||
|
type UserState;
|
||||||
|
|
||||||
|
/// Additional UI elements to draw in the nodes, after the parameters.
|
||||||
|
fn bottom_ui<DataType, ValueType>(
|
||||||
|
&self,
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
node_id: NodeId,
|
||||||
|
graph: &Graph<Self, DataType, ValueType>,
|
||||||
|
user_state: &Self::UserState,
|
||||||
|
) -> Vec<NodeResponse<Self::Response>>
|
||||||
|
where
|
||||||
|
Self::Response: UserResponseTrait;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This trait can be implemented by any user type. The trait tells the library
|
||||||
|
/// how to enumerate the node templates it will present to the user as part of
|
||||||
|
/// the node finder.
|
||||||
|
pub trait NodeTemplateIter {
|
||||||
|
type Item;
|
||||||
|
fn all_kinds(&self) -> Box<dyn Iterator<Item = &Self::Item> + '_>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This trait must be implemented by the `NodeTemplate` generic parameter of
|
||||||
|
/// the [`GraphEditorState`]. It allows the customization of node templates. A
|
||||||
|
/// node template is what describes what kinds of nodes can be added to the
|
||||||
|
/// graph, what is their name, and what are their input / output parameters.
|
||||||
|
pub trait NodeTemplateTrait: Clone {
|
||||||
|
/// Must be set to the custom user `NodeData` type
|
||||||
|
type NodeData;
|
||||||
|
/// Must be set to the custom user `DataType` type
|
||||||
|
type DataType;
|
||||||
|
/// Must be set to the custom user `ValueType` type
|
||||||
|
type ValueType;
|
||||||
|
|
||||||
|
/// Returns a descriptive name for the node kind, used in the node finder.
|
||||||
|
fn node_finder_label(&self) -> &str;
|
||||||
|
|
||||||
|
/// Returns a descriptive name for the node kind, used in the graph.
|
||||||
|
fn node_graph_label(&self) -> String;
|
||||||
|
|
||||||
|
/// Returns the user data for this node kind.
|
||||||
|
fn user_data(&self) -> Self::NodeData;
|
||||||
|
|
||||||
|
/// This function is run when this node kind gets added to the graph. The
|
||||||
|
/// node will be empty by default, and this function can be used to fill its
|
||||||
|
/// parameters.
|
||||||
|
fn build_node(
|
||||||
|
&self,
|
||||||
|
graph: &mut Graph<Self::NodeData, Self::DataType, Self::ValueType>,
|
||||||
|
node_id: NodeId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The custom user response types when drawing nodes in the graph must
|
||||||
|
/// implement this trait.
|
||||||
|
pub trait UserResponseTrait: Clone + Copy + std::fmt::Debug + PartialEq + Eq {}
|
||||||
@ -10,7 +10,7 @@ pub struct PanZoom {
|
|||||||
pub zoom: f32,
|
pub zoom: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GraphEditorState<NodeData, DataType, ValueType, NodeKind, UserState> {
|
pub struct GraphEditorState<NodeData, DataType, ValueType, NodeTemplate, UserState> {
|
||||||
pub graph: Graph<NodeData, DataType, ValueType>,
|
pub graph: Graph<NodeData, DataType, ValueType>,
|
||||||
/// Nodes are drawn in this order. Draw order is important because nodes
|
/// Nodes are drawn in this order. Draw order is important because nodes
|
||||||
/// that are drawn last are on top.
|
/// that are drawn last are on top.
|
||||||
@ -24,7 +24,7 @@ pub struct GraphEditorState<NodeData, DataType, ValueType, NodeKind, UserState>
|
|||||||
/// The position of each node.
|
/// The position of each node.
|
||||||
pub node_positions: SecondaryMap<NodeId, egui::Pos2>,
|
pub node_positions: SecondaryMap<NodeId, egui::Pos2>,
|
||||||
/// The node finder is used to create new nodes.
|
/// The node finder is used to create new nodes.
|
||||||
pub node_finder: Option<NodeFinder<NodeKind>>,
|
pub node_finder: Option<NodeFinder<NodeTemplate>>,
|
||||||
/// The panning of the graph viewport.
|
/// The panning of the graph viewport.
|
||||||
pub pan_zoom: PanZoom,
|
pub pan_zoom: PanZoom,
|
||||||
pub user_state: UserState,
|
pub user_state: UserState,
|
||||||
|
|||||||
@ -32,11 +32,11 @@ pub enum MyValueType {
|
|||||||
Scalar { value: f32 },
|
Scalar { value: f32 },
|
||||||
}
|
}
|
||||||
|
|
||||||
/// NodeKind is a mechanism to define node "templates". It's what the graph will
|
/// NodeTemplate is a mechanism to define node templates. It's what the graph
|
||||||
/// display in the "new node" popup. The user code needs to tell the library how
|
/// will display in the "new node" popup. The user code needs to tell the
|
||||||
/// to convert a NodeKind into a Node.
|
/// library how to convert a NodeTemplate into a Node.
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub enum MyNodeKind {
|
pub enum MyNodeTemplate {
|
||||||
AddScalar,
|
AddScalar,
|
||||||
SubtractScalar,
|
SubtractScalar,
|
||||||
VectorTimesScalar,
|
VectorTimesScalar,
|
||||||
@ -82,17 +82,17 @@ impl DataTypeTrait for MyDataType {
|
|||||||
|
|
||||||
// A trait for the node kinds, which tells the library how to build new nodes
|
// A trait for the node kinds, which tells the library how to build new nodes
|
||||||
// from the templates in the node finder
|
// from the templates in the node finder
|
||||||
impl NodeKindTrait for MyNodeKind {
|
impl NodeTemplateTrait for MyNodeTemplate {
|
||||||
type NodeData = MyNodeData;
|
type NodeData = MyNodeData;
|
||||||
type DataType = MyDataType;
|
type DataType = MyDataType;
|
||||||
type ValueType = MyValueType;
|
type ValueType = MyValueType;
|
||||||
|
|
||||||
fn node_finder_label(&self) -> &str {
|
fn node_finder_label(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
MyNodeKind::AddScalar => "Scalar add",
|
MyNodeTemplate::AddScalar => "Scalar add",
|
||||||
MyNodeKind::SubtractScalar => "Scalar subtract",
|
MyNodeTemplate::SubtractScalar => "Scalar subtract",
|
||||||
MyNodeKind::VectorTimesScalar => "Vector times scalar",
|
MyNodeTemplate::VectorTimesScalar => "Vector times scalar",
|
||||||
MyNodeKind::AddVector => "Vector subtract",
|
MyNodeTemplate::AddVector => "Vector subtract",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,7 +151,7 @@ impl NodeKindTrait for MyNodeKind {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
MyNodeKind::AddScalar => {
|
MyNodeTemplate::AddScalar => {
|
||||||
// The first input param doesn't use the macro so we can comment
|
// The first input param doesn't use the macro so we can comment
|
||||||
// it in more detail.
|
// it in more detail.
|
||||||
graph.add_input_param(
|
graph.add_input_param(
|
||||||
@ -172,17 +172,17 @@ impl NodeKindTrait for MyNodeKind {
|
|||||||
input!(scalar "B");
|
input!(scalar "B");
|
||||||
output!(scalar "out");
|
output!(scalar "out");
|
||||||
}
|
}
|
||||||
MyNodeKind::SubtractScalar => {
|
MyNodeTemplate::SubtractScalar => {
|
||||||
input!(scalar "A");
|
input!(scalar "A");
|
||||||
input!(scalar "B");
|
input!(scalar "B");
|
||||||
output!(scalar "out");
|
output!(scalar "out");
|
||||||
}
|
}
|
||||||
MyNodeKind::VectorTimesScalar => {
|
MyNodeTemplate::VectorTimesScalar => {
|
||||||
input!(scalar "scalar");
|
input!(scalar "scalar");
|
||||||
input!(vector "vector");
|
input!(vector "vector");
|
||||||
output!(vector "out");
|
output!(vector "out");
|
||||||
}
|
}
|
||||||
MyNodeKind::AddVector => {
|
MyNodeTemplate::AddVector => {
|
||||||
input!(vector "v1");
|
input!(vector "v1");
|
||||||
input!(vector "v2");
|
input!(vector "v2");
|
||||||
output!(vector "out");
|
output!(vector "out");
|
||||||
@ -191,9 +191,9 @@ impl NodeKindTrait for MyNodeKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AllMyNodeKinds;
|
pub struct AllMyNodeTemplates;
|
||||||
impl NodeKindIter for AllMyNodeKinds {
|
impl NodeTemplateIter for AllMyNodeTemplates {
|
||||||
type Item = MyNodeKind;
|
type Item = MyNodeTemplate;
|
||||||
|
|
||||||
fn all_kinds(&self) -> Box<dyn Iterator<Item = &Self::Item> + '_> {
|
fn all_kinds(&self) -> Box<dyn Iterator<Item = &Self::Item> + '_> {
|
||||||
// This function must return a list of node kinds, which the node finder
|
// This function must return a list of node kinds, which the node finder
|
||||||
@ -204,17 +204,17 @@ impl NodeKindIter for AllMyNodeKinds {
|
|||||||
// over return parameters, so you can't return an iterator.
|
// over return parameters, so you can't return an iterator.
|
||||||
Box::new(
|
Box::new(
|
||||||
[
|
[
|
||||||
MyNodeKind::AddScalar,
|
MyNodeTemplate::AddScalar,
|
||||||
MyNodeKind::SubtractScalar,
|
MyNodeTemplate::SubtractScalar,
|
||||||
MyNodeKind::VectorTimesScalar,
|
MyNodeTemplate::VectorTimesScalar,
|
||||||
MyNodeKind::AddVector,
|
MyNodeTemplate::AddVector,
|
||||||
]
|
]
|
||||||
.iter(),
|
.iter(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputParamWidget for MyValueType {
|
impl WidgetValueTrait for MyValueType {
|
||||||
fn value_widget(&mut self, param_name: &str, ui: &mut egui::Ui) {
|
fn value_widget(&mut self, param_name: &str, ui: &mut egui::Ui) {
|
||||||
// This trait is used to tell the library which UI to display for the
|
// This trait is used to tell the library which UI to display for the
|
||||||
// inline parameter widgets.
|
// inline parameter widgets.
|
||||||
@ -293,7 +293,7 @@ impl NodeDataTrait for MyNodeData {
|
|||||||
pub struct NodeGraphExample {
|
pub struct NodeGraphExample {
|
||||||
// The `GraphEditorState` is the top-level object. You "register" all your
|
// The `GraphEditorState` is the top-level object. You "register" all your
|
||||||
// custom types by specifying it as its generic parameters.
|
// custom types by specifying it as its generic parameters.
|
||||||
state: GraphEditorState<MyNodeData, MyDataType, MyValueType, MyNodeKind, MyGraphState>,
|
state: GraphEditorState<MyNodeData, MyDataType, MyValueType, MyNodeTemplate, MyGraphState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for NodeGraphExample {
|
impl Default for NodeGraphExample {
|
||||||
@ -312,7 +312,7 @@ impl epi::App for NodeGraphExample {
|
|||||||
/// Called each time the UI needs repainting, which may be many times per second.
|
/// Called each time the UI needs repainting, which may be many times per second.
|
||||||
/// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`.
|
/// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`.
|
||||||
fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) {
|
fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) {
|
||||||
let graph_response = self.state.draw_graph_editor(ctx, AllMyNodeKinds);
|
let graph_response = self.state.draw_graph_editor(ctx, AllMyNodeTemplates);
|
||||||
for node_response in graph_response.node_responses {
|
for node_response in graph_response.node_responses {
|
||||||
// Here, we ignore all other graph events. But you may find
|
// Here, we ignore all other graph events. But you may find
|
||||||
// some use for them. For example, by playing a sound when a new
|
// some use for them. For example, by playing a sound when a new
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user