Skip to content

univiseditor/UnivisEditor

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

UnivisEditor

A node-based visual editor for the Bevy Engine

Rust Bevy License


πŸ“– Overview

UnivisEditor is a node-based visual editor built on top of the Bevy Engine. It follows the philosophy that "everything is a node" - similar to Unreal Engine's Blueprint system or Blender's Node Editor.

The codebase is now organized as a Cargo workspace:

  • univis_editor_core
  • univis_editor_runtime
  • univis_editor_ui
  • univis_editor_persistence
  • univis_editor_nodes_builtin
  • univis_editor_game_editor (optional)
  • univis_editor_app (facade + prelude)

Key Features

  • 🎨 Visual Node Editor - Create complex logic by connecting nodes visually
  • πŸ”Œ Extensible Node System - Easy to create custom nodes with a clean trait-based API
  • πŸš€ Built on Bevy 0.17 - Leverages the latest Bevy ECS architecture
  • πŸ“¦ World-Space UI - Custom UI system (univis_ui) for 3D world-space interactions
  • πŸ” Search & Filter - Quickly find nodes with built-in search functionality
  • πŸ“œ Auto-scrolling Menu - Context menu with scrolling support for large node libraries

πŸš€ Getting Started

Prerequisites

  • Rust 1.75 or later
  • Bevy 0.17.3 compatible system

Installation

Add to your Cargo.toml:

[dependencies]
univis_editor_app = { path = "path/to/univis_editor/crates/univis_editor_app" }
bevy = "0.17.3"

Basic Usage

use bevy::prelude::*;
use univis_editor_app::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(NodeGraphPlugin)  // Add the editor plugin
        .run();
}

πŸ“š Creating Custom Nodes

UnivisEditor uses a trait-based system for defining nodes. Simply implement NodeDefinition:

use univis_editor_core::node_definition::{
    NodeDefinition, NodeId, NodeCategory, PortDefinition, 
    ProcessContext, ProcessResult
};
use univis_editor_core::value::NodeValue;
use bevy::prelude::*;

pub struct MyCustomNode;

impl NodeDefinition for MyCustomNode {
    // === Required Methods ===
    
    fn id(&self) -> NodeId {
        NodeId::new("custom/my_node")
    }
    
    fn display_name(&self) -> &str {
        "My Custom Node"
    }
    
    fn inputs(&self) -> Vec<PortDefinition> {
        vec![
            PortDefinition::input_float("Input A")
                .with_default(NodeValue::float(0.0)),
            PortDefinition::input_float("Input B")
                .with_default(NodeValue::float(0.0)),
        ]
    }
    
    fn outputs(&self) -> Vec<PortDefinition> {
        vec![PortDefinition::output_float("Result")]
    }
    
    fn process(&self, ctx: &mut ProcessContext) -> ProcessResult {
        let a = ctx.get_float_or(0, 0.0);
        let b = ctx.get_float_or(1, 0.0);
        ctx.set_float(0, a + b);  // Simple addition
        ProcessResult::Success
    }
    
    // === Optional Methods ===
    
    fn category(&self) -> NodeCategory {
        NodeCategory::new("Custom")
    }
    
    fn description(&self) -> Option<&str> {
        Some("Adds two numbers together")
    }
    
    fn color(&self) -> Color {
        Color::srgb(0.3, 0.5, 0.7)
    }
    
    fn icon(&self) -> Option<&str> {
        Some("+")
    }
    
    fn keywords(&self) -> Vec<&str> {
        vec!["add", "sum", "plus"]
    }
    
    fn menu_order(&self) -> i32 {
        1  // Display order in menu
    }
}

Registering Nodes

use univis_editor_core::node_registry::NodeRegistry;

fn register_my_nodes(registry: &mut NodeRegistry) {
    registry.register(MyCustomNode);
    // Add more nodes...
}

🎯 Node API Reference

ProcessContext Helpers

The ProcessContext provides convenient methods for reading and writing values:

// Reading values
let value = ctx.get_float(0);           // Option<f64>
let value = ctx.get_float_or(0, 0.0);   // f64 with default
let value = ctx.get_int(0);
let value = ctx.get_bool(0);
let value = ctx.get_vec2(0);
let value = ctx.get_vec3(0);
let value = ctx.get_string(0);

// Writing values
ctx.set_float(0, 42.0);
ctx.set_int(0, 42);
ctx.set_bool(0, true);
ctx.set_vec2(0, Vec2::new(1.0, 2.0));
ctx.set_vec3(0, Vec3::new(1.0, 2.0, 3.0));
ctx.set_string(0, "Hello");

PortDefinition Builders

PortDefinition::input_float("Value")
    .with_description("A float input")
    .with_default(NodeValue::float(1.0))
    .with_color(Color::srgb(1.0, 0.5, 0.0));

PortDefinition::output_float("Result");
PortDefinition::output_any("Pass Through");

NodeDefinition Trait Methods

Method Required Description
id() βœ… Unique identifier (e.g., "math/add")
display_name() βœ… Human-readable name
inputs() βœ… Input port definitions
outputs() βœ… Output port definitions
process() βœ… Processing logic
category() ❌ Node category for menu
description() ❌ Tooltip/description
color() ❌ Node header color
icon() ❌ Unicode icon
show_in_menu() ❌ Show in context menu (default: true)
menu_order() ❌ Sort order in menu
keywords() ❌ Search keywords
has_custom_body() ❌ Custom UI body
build_body() ❌ Build custom UI

πŸ“¦ Built-in Nodes

Math Nodes

Node Description
Add Addition of two numbers
Subtract Subtraction
Multiply Multiplication
Divide Division (with zero check)
Clamp Clamp value to range
Lerp Linear interpolation
Min / Max Minimum / Maximum
Abs Absolute value
Sin / Cos Trigonometric functions

Input Nodes

Node Description
Number Float value input
Integer Integer value input
Boolean True/false toggle
Text String input
Vector2 2D vector
Vector3 3D vector
Color RGBA color

Logic Nodes

Node Description
Compare Compare two values
Branch If/else conditional
And / Or / Not Boolean operations

Output Nodes

Node Description
View Display value in node
Watch Named value display
Debug Print to console

πŸ—οΈ Project Structure

src/
β”œβ”€β”€ core/
β”‚   β”œβ”€β”€ node_definition.rs   # Node trait and types
β”‚   β”œβ”€β”€ node_registry.rs     # Node registration system
β”‚   β”œβ”€β”€ value.rs             # Dynamic value types
β”‚   β”œβ”€β”€ menu.rs              # Context menu system
β”‚   β”œβ”€β”€ wire.rs              # Wire/connection logic
β”‚   └── interaction.rs       # User interaction
β”œβ”€β”€ nodes/
β”‚   β”œβ”€β”€ math.rs              # Math nodes
β”‚   β”œβ”€β”€ input.rs             # Input nodes
β”‚   β”œβ”€β”€ output.rs            # Output nodes
β”‚   └── logic.rs             # Logic nodes
β”œβ”€β”€ data/
β”‚   └── node_spawn.rs        # Node spawning system
β”œβ”€β”€ widgets/
β”‚   └── infinity_grid.rs     # Background grid
β”œβ”€β”€ editor/
β”‚   └── editor.rs            # Editor camera & controls
└── lib.rs                   # Main plugin

πŸ§ͺ Testing

Run the test suite:

# Run all tests
cargo test

# Run specific test file
cargo test --test math_node_tests

# Run with verbose output
cargo test -- --nocapture

Test Categories

  • value_tests.rs - NodeValue type tests
  • process_context_tests.rs - Context helper tests
  • port_definition_tests.rs - Port builder tests
  • math_node_tests.rs - Math node functionality
  • input_node_tests.rs - Input node tests
  • output_node_tests.rs - Output node tests
  • logic_node_tests.rs - Logic node tests
  • integration_tests.rs - End-to-end tests

πŸ”§ Architecture

Data Flow

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Input Node │────▢│  Math Node  │────▢│ Output Node β”‚
β”‚   (Value)   β”‚     β”‚  (Process)  β”‚     β”‚   (View)    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚                   β”‚                   β”‚
       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
                    Topological Sort
                    (Kahn's Algorithm)

Node Processing Pipeline

  1. Initialize - Set default values for new nodes
  2. Sort - Topological ordering for correct execution
  3. Propagate - Transfer values through connections
  4. Process - Execute each node's logic
  5. Output - Results available for next frame

🀝 Contributing

Contributions are welcome! Please feel free to submit issues and pull requests.

Development Setup

# Clone the repository
git clone https://github.com/univiseditor/univis_editor
cd univis_editor

# Run tests
cargo test

# Run the example
cargo run --example simple_editor

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.


πŸ™ Acknowledgments


Built with ❀️ using Bevy Engine

About

Visual editor built on the Bevy Engine core

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors