mirror of
https://github.com/eliasstepanik/egui_node_graph.git
synced 2026-01-11 05:48:27 +00:00
commit
943cd4b94c
@ -83,7 +83,7 @@ pub struct GraphNodeWidget<'a, NodeData, DataType, ValueType> {
|
|||||||
pub pan: egui::Vec2,
|
pub pan: egui::Vec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<NodeData, DataType, ValueType, NodeTemplate, UserResponse, UserState>
|
impl<NodeData, DataType, ValueType, NodeTemplate, UserResponse, UserState, CategoryType>
|
||||||
GraphEditorState<NodeData, DataType, ValueType, NodeTemplate, UserState>
|
GraphEditorState<NodeData, DataType, ValueType, NodeTemplate, UserState>
|
||||||
where
|
where
|
||||||
NodeData: NodeDataTrait<
|
NodeData: NodeDataTrait<
|
||||||
@ -100,8 +100,10 @@ where
|
|||||||
DataType = DataType,
|
DataType = DataType,
|
||||||
ValueType = ValueType,
|
ValueType = ValueType,
|
||||||
UserState = UserState,
|
UserState = UserState,
|
||||||
|
CategoryType = CategoryType,
|
||||||
>,
|
>,
|
||||||
DataType: DataTypeTrait<UserState>,
|
DataType: DataTypeTrait<UserState>,
|
||||||
|
CategoryType: CategoryTrait,
|
||||||
{
|
{
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn draw_graph_editor(
|
pub fn draw_graph_editor(
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use std::marker::PhantomData;
|
use std::{collections::BTreeMap, marker::PhantomData};
|
||||||
|
|
||||||
use crate::{color_hex_utils::*, NodeTemplateIter, NodeTemplateTrait};
|
use crate::{color_hex_utils::*, CategoryTrait, NodeTemplateIter, NodeTemplateTrait};
|
||||||
|
|
||||||
use egui::*;
|
use egui::*;
|
||||||
|
|
||||||
@ -14,9 +14,11 @@ pub struct NodeFinder<NodeTemplate> {
|
|||||||
_phantom: PhantomData<NodeTemplate>,
|
_phantom: PhantomData<NodeTemplate>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<NodeTemplate, NodeData, UserState> NodeFinder<NodeTemplate>
|
impl<NodeTemplate, NodeData, UserState, CategoryType> NodeFinder<NodeTemplate>
|
||||||
where
|
where
|
||||||
NodeTemplate: NodeTemplateTrait<NodeData = NodeData, UserState = UserState>,
|
NodeTemplate:
|
||||||
|
NodeTemplateTrait<NodeData = NodeData, UserState = UserState, CategoryType = CategoryType>,
|
||||||
|
CategoryType: CategoryTrait,
|
||||||
{
|
{
|
||||||
pub fn new_at(pos: Pos2) -> Self {
|
pub fn new_at(pos: Pos2) -> Self {
|
||||||
NodeFinder {
|
NodeFinder {
|
||||||
@ -62,12 +64,29 @@ where
|
|||||||
resp.request_focus();
|
resp.request_focus();
|
||||||
self.just_spawned = false;
|
self.just_spawned = false;
|
||||||
}
|
}
|
||||||
|
let update_open = resp.changed();
|
||||||
|
|
||||||
let mut query_submit = resp.lost_focus() && ui.input(|i| i.key_pressed(Key::Enter));
|
let mut query_submit = resp.lost_focus() && ui.input(|i| i.key_pressed(Key::Enter));
|
||||||
|
|
||||||
let max_height = ui.input(|i| i.screen_rect.height() * 0.5);
|
let max_height = ui.input(|i| i.screen_rect.height() * 0.5);
|
||||||
let scroll_area_width = resp.rect.width() - 30.0;
|
let scroll_area_width = resp.rect.width() - 30.0;
|
||||||
|
|
||||||
|
let all_kinds = all_kinds.all_kinds();
|
||||||
|
let mut categories: BTreeMap<String, Vec<&NodeTemplate>> = Default::default();
|
||||||
|
let mut orphan_kinds = Vec::new();
|
||||||
|
|
||||||
|
for kind in &all_kinds {
|
||||||
|
let kind_categories = kind.node_finder_categories(user_state);
|
||||||
|
|
||||||
|
if kind_categories.is_empty() {
|
||||||
|
orphan_kinds.push(kind);
|
||||||
|
} else {
|
||||||
|
for category in kind_categories {
|
||||||
|
categories.entry(category.name()).or_default().push(kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Frame::default()
|
Frame::default()
|
||||||
.inner_margin(vec2(10.0, 10.0))
|
.inner_margin(vec2(10.0, 10.0))
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
@ -75,18 +94,51 @@ where
|
|||||||
.max_height(max_height)
|
.max_height(max_height)
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
ui.set_width(scroll_area_width);
|
ui.set_width(scroll_area_width);
|
||||||
for kind in all_kinds.all_kinds() {
|
for (category, kinds) in categories {
|
||||||
|
let filtered_kinds: Vec<_> = kinds
|
||||||
|
.into_iter()
|
||||||
|
.map(|kind| {
|
||||||
|
let kind_name =
|
||||||
|
kind.node_finder_label(user_state).to_string();
|
||||||
|
(kind, kind_name)
|
||||||
|
})
|
||||||
|
.filter(|(_kind, kind_name)| {
|
||||||
|
kind_name
|
||||||
|
.to_lowercase()
|
||||||
|
.contains(self.query.to_lowercase().as_str())
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if !filtered_kinds.is_empty() {
|
||||||
|
let default_open = !self.query.is_empty();
|
||||||
|
|
||||||
|
CollapsingHeader::new(&category)
|
||||||
|
.default_open(default_open)
|
||||||
|
.open(update_open.then_some(default_open))
|
||||||
|
.show(ui, |ui| {
|
||||||
|
for (kind, kind_name) in filtered_kinds {
|
||||||
|
if ui
|
||||||
|
.selectable_label(false, kind_name)
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
submitted_archetype = Some(kind.clone());
|
||||||
|
} else if query_submit {
|
||||||
|
submitted_archetype = Some(kind.clone());
|
||||||
|
query_submit = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for kind in orphan_kinds {
|
||||||
let kind_name = kind.node_finder_label(user_state).to_string();
|
let kind_name = kind.node_finder_label(user_state).to_string();
|
||||||
if kind_name
|
|
||||||
.to_lowercase()
|
if ui.selectable_label(false, kind_name).clicked() {
|
||||||
.contains(self.query.to_lowercase().as_str())
|
submitted_archetype = Some(kind.clone());
|
||||||
{
|
} else if query_submit {
|
||||||
if ui.selectable_label(false, kind_name).clicked() {
|
submitted_archetype = Some(kind.clone());
|
||||||
submitted_archetype = Some(kind);
|
query_submit = false;
|
||||||
} else if query_submit {
|
|
||||||
submitted_archetype = Some(kind);
|
|
||||||
query_submit = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -194,6 +194,38 @@ pub trait NodeTemplateIter {
|
|||||||
fn all_kinds(&self) -> Vec<Self::Item>;
|
fn all_kinds(&self) -> Vec<Self::Item>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Describes a category of nodes.
|
||||||
|
///
|
||||||
|
/// Used by [`NodeTemplateTrait::node_finder_categories`] to categorize nodes
|
||||||
|
/// templates into groups.
|
||||||
|
///
|
||||||
|
/// If all nodes in a program are known beforehand, it's usefult to define
|
||||||
|
/// an enum containing all categories and implement [`CategoryTrait`] for it. This will
|
||||||
|
/// make it impossible to accidentally create a new category by mis-typing an existing
|
||||||
|
/// one, like in the case of using string types.
|
||||||
|
pub trait CategoryTrait {
|
||||||
|
/// Name of the category.
|
||||||
|
fn name(&self) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CategoryTrait for () {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> CategoryTrait for &'a str {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
self.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CategoryTrait for String {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
self.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// This trait must be implemented by the `NodeTemplate` generic parameter of
|
/// This trait must be implemented by the `NodeTemplate` generic parameter of
|
||||||
/// the [`GraphEditorState`]. It allows the customization of node templates. A
|
/// the [`GraphEditorState`]. It allows the customization of node templates. A
|
||||||
/// node template is what describes what kinds of nodes can be added to the
|
/// node template is what describes what kinds of nodes can be added to the
|
||||||
@ -207,6 +239,12 @@ pub trait NodeTemplateTrait: Clone {
|
|||||||
type ValueType;
|
type ValueType;
|
||||||
/// Must be set to the custom user `UserState` type
|
/// Must be set to the custom user `UserState` type
|
||||||
type UserState;
|
type UserState;
|
||||||
|
/// Must be a type that implements the [`CategoryTrait`] trait.
|
||||||
|
///
|
||||||
|
/// `&'static str` is a good default if you intend to simply type out
|
||||||
|
/// the categories of your node. Use `()` if you don't need categories
|
||||||
|
/// at all.
|
||||||
|
type CategoryType;
|
||||||
|
|
||||||
/// Returns a descriptive name for the node kind, used in the node finder.
|
/// Returns a descriptive name for the node kind, used in the node finder.
|
||||||
///
|
///
|
||||||
@ -215,6 +253,15 @@ pub trait NodeTemplateTrait: Clone {
|
|||||||
/// more information
|
/// more information
|
||||||
fn node_finder_label(&self, user_state: &mut Self::UserState) -> std::borrow::Cow<str>;
|
fn node_finder_label(&self, user_state: &mut Self::UserState) -> std::borrow::Cow<str>;
|
||||||
|
|
||||||
|
/// Vec of categories to which the node belongs.
|
||||||
|
///
|
||||||
|
/// It's often useful to organize similar nodes into categories, which will
|
||||||
|
/// then be used by the node finder to show a more manageable UI, especially
|
||||||
|
/// if the node template are numerous.
|
||||||
|
fn node_finder_categories(&self, _user_state: &mut Self::UserState) -> Vec<Self::CategoryType> {
|
||||||
|
Vec::default()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a descriptive name for the node kind, used in the graph.
|
/// Returns a descriptive name for the node kind, used in the graph.
|
||||||
fn node_graph_label(&self, user_state: &mut Self::UserState) -> String;
|
fn node_graph_label(&self, user_state: &mut Self::UserState) -> String;
|
||||||
|
|
||||||
|
|||||||
@ -71,13 +71,13 @@ impl MyValueType {
|
|||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub enum MyNodeTemplate {
|
pub enum MyNodeTemplate {
|
||||||
MakeVector,
|
|
||||||
MakeScalar,
|
MakeScalar,
|
||||||
AddScalar,
|
AddScalar,
|
||||||
SubtractScalar,
|
SubtractScalar,
|
||||||
VectorTimesScalar,
|
MakeVector,
|
||||||
AddVector,
|
AddVector,
|
||||||
SubtractVector,
|
SubtractVector,
|
||||||
|
VectorTimesScalar,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The response type is used to encode side-effects produced when drawing a
|
/// The response type is used to encode side-effects produced when drawing a
|
||||||
@ -125,19 +125,33 @@ impl NodeTemplateTrait for MyNodeTemplate {
|
|||||||
type DataType = MyDataType;
|
type DataType = MyDataType;
|
||||||
type ValueType = MyValueType;
|
type ValueType = MyValueType;
|
||||||
type UserState = MyGraphState;
|
type UserState = MyGraphState;
|
||||||
|
type CategoryType = &'static str;
|
||||||
|
|
||||||
fn node_finder_label(&self, _user_state: &mut Self::UserState) -> Cow<'_, str> {
|
fn node_finder_label(&self, _user_state: &mut Self::UserState) -> Cow<'_, str> {
|
||||||
Cow::Borrowed(match self {
|
Cow::Borrowed(match self {
|
||||||
MyNodeTemplate::MakeVector => "New vector",
|
|
||||||
MyNodeTemplate::MakeScalar => "New scalar",
|
MyNodeTemplate::MakeScalar => "New scalar",
|
||||||
MyNodeTemplate::AddScalar => "Scalar add",
|
MyNodeTemplate::AddScalar => "Scalar add",
|
||||||
MyNodeTemplate::SubtractScalar => "Scalar subtract",
|
MyNodeTemplate::SubtractScalar => "Scalar subtract",
|
||||||
|
MyNodeTemplate::MakeVector => "New vector",
|
||||||
MyNodeTemplate::AddVector => "Vector add",
|
MyNodeTemplate::AddVector => "Vector add",
|
||||||
MyNodeTemplate::SubtractVector => "Vector subtract",
|
MyNodeTemplate::SubtractVector => "Vector subtract",
|
||||||
MyNodeTemplate::VectorTimesScalar => "Vector times scalar",
|
MyNodeTemplate::VectorTimesScalar => "Vector times scalar",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this is what allows the library to show collapsible lists in the node finder.
|
||||||
|
fn node_finder_categories(&self, _user_state: &mut Self::UserState) -> Vec<&'static str> {
|
||||||
|
match self {
|
||||||
|
MyNodeTemplate::MakeScalar
|
||||||
|
| MyNodeTemplate::AddScalar
|
||||||
|
| MyNodeTemplate::SubtractScalar => vec!["Scalar"],
|
||||||
|
MyNodeTemplate::MakeVector
|
||||||
|
| MyNodeTemplate::AddVector
|
||||||
|
| MyNodeTemplate::SubtractVector => vec!["Vector"],
|
||||||
|
MyNodeTemplate::VectorTimesScalar => vec!["Vector", "Scalar"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn node_graph_label(&self, user_state: &mut Self::UserState) -> String {
|
fn node_graph_label(&self, user_state: &mut Self::UserState) -> String {
|
||||||
// It's okay to delegate this to node_finder_label if you don't want to
|
// It's okay to delegate this to node_finder_label if you don't want to
|
||||||
// show different names in the node finder and the node itself.
|
// show different names in the node finder and the node itself.
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
#![cfg_attr(not(debug_assertions), deny(warnings))] // Forbid warnings in release builds
|
#![cfg_attr(not(debug_assertions), deny(warnings))] // Forbid warnings in release builds
|
||||||
#![warn(clippy::all, rust_2018_idioms)]
|
#![warn(clippy::all, rust_2018_idioms)]
|
||||||
|
|
||||||
|
use egui_node_graph_example::NodeGraphExample;
|
||||||
|
|
||||||
// When compiling natively:
|
// When compiling natively:
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
fn main() {
|
fn main() {
|
||||||
@ -14,10 +16,10 @@ fn main() {
|
|||||||
cc.egui_ctx.set_visuals(Visuals::dark());
|
cc.egui_ctx.set_visuals(Visuals::dark());
|
||||||
#[cfg(feature = "persistence")]
|
#[cfg(feature = "persistence")]
|
||||||
{
|
{
|
||||||
Box::new(egui_node_graph_example::NodeGraphExample::new(cc))
|
Box::new(NodeGraphExample::new(cc))
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "persistence"))]
|
#[cfg(not(feature = "persistence"))]
|
||||||
Box::new(egui_node_graph_example::NodeGraphExample::default())
|
Box::<NodeGraphExample>::default()
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.expect("Failed to run native example");
|
.expect("Failed to run native example");
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user