mirror of
https://github.com/eliasstepanik/egui_node_graph.git
synced 2026-01-10 05:18:27 +00:00
commit
943cd4b94c
@ -83,7 +83,7 @@ pub struct GraphNodeWidget<'a, NodeData, DataType, ValueType> {
|
||||
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>
|
||||
where
|
||||
NodeData: NodeDataTrait<
|
||||
@ -100,8 +100,10 @@ where
|
||||
DataType = DataType,
|
||||
ValueType = ValueType,
|
||||
UserState = UserState,
|
||||
CategoryType = CategoryType,
|
||||
>,
|
||||
DataType: DataTypeTrait<UserState>,
|
||||
CategoryType: CategoryTrait,
|
||||
{
|
||||
#[must_use]
|
||||
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::*;
|
||||
|
||||
@ -14,9 +14,11 @@ pub struct NodeFinder<NodeTemplate> {
|
||||
_phantom: PhantomData<NodeTemplate>,
|
||||
}
|
||||
|
||||
impl<NodeTemplate, NodeData, UserState> NodeFinder<NodeTemplate>
|
||||
impl<NodeTemplate, NodeData, UserState, CategoryType> NodeFinder<NodeTemplate>
|
||||
where
|
||||
NodeTemplate: NodeTemplateTrait<NodeData = NodeData, UserState = UserState>,
|
||||
NodeTemplate:
|
||||
NodeTemplateTrait<NodeData = NodeData, UserState = UserState, CategoryType = CategoryType>,
|
||||
CategoryType: CategoryTrait,
|
||||
{
|
||||
pub fn new_at(pos: Pos2) -> Self {
|
||||
NodeFinder {
|
||||
@ -62,12 +64,29 @@ where
|
||||
resp.request_focus();
|
||||
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 max_height = ui.input(|i| i.screen_rect.height() * 0.5);
|
||||
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()
|
||||
.inner_margin(vec2(10.0, 10.0))
|
||||
.show(ui, |ui| {
|
||||
@ -75,18 +94,51 @@ where
|
||||
.max_height(max_height)
|
||||
.show(ui, |ui| {
|
||||
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();
|
||||
if kind_name
|
||||
.to_lowercase()
|
||||
.contains(self.query.to_lowercase().as_str())
|
||||
{
|
||||
if ui.selectable_label(false, kind_name).clicked() {
|
||||
submitted_archetype = Some(kind);
|
||||
} else if query_submit {
|
||||
submitted_archetype = Some(kind);
|
||||
query_submit = false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -194,6 +194,38 @@ pub trait NodeTemplateIter {
|
||||
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
|
||||
/// the [`GraphEditorState`]. It allows the customization of node templates. A
|
||||
/// node template is what describes what kinds of nodes can be added to the
|
||||
@ -207,6 +239,12 @@ pub trait NodeTemplateTrait: Clone {
|
||||
type ValueType;
|
||||
/// Must be set to the custom user `UserState` type
|
||||
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.
|
||||
///
|
||||
@ -215,6 +253,15 @@ pub trait NodeTemplateTrait: Clone {
|
||||
/// more information
|
||||
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.
|
||||
fn node_graph_label(&self, user_state: &mut Self::UserState) -> String;
|
||||
|
||||
|
||||
@ -71,13 +71,13 @@ impl MyValueType {
|
||||
#[derive(Clone, Copy)]
|
||||
#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum MyNodeTemplate {
|
||||
MakeVector,
|
||||
MakeScalar,
|
||||
AddScalar,
|
||||
SubtractScalar,
|
||||
VectorTimesScalar,
|
||||
MakeVector,
|
||||
AddVector,
|
||||
SubtractVector,
|
||||
VectorTimesScalar,
|
||||
}
|
||||
|
||||
/// 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 ValueType = MyValueType;
|
||||
type UserState = MyGraphState;
|
||||
type CategoryType = &'static str;
|
||||
|
||||
fn node_finder_label(&self, _user_state: &mut Self::UserState) -> Cow<'_, str> {
|
||||
Cow::Borrowed(match self {
|
||||
MyNodeTemplate::MakeVector => "New vector",
|
||||
MyNodeTemplate::MakeScalar => "New scalar",
|
||||
MyNodeTemplate::AddScalar => "Scalar add",
|
||||
MyNodeTemplate::SubtractScalar => "Scalar subtract",
|
||||
MyNodeTemplate::MakeVector => "New vector",
|
||||
MyNodeTemplate::AddVector => "Vector add",
|
||||
MyNodeTemplate::SubtractVector => "Vector subtract",
|
||||
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 {
|
||||
// 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.
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
#![cfg_attr(not(debug_assertions), deny(warnings))] // Forbid warnings in release builds
|
||||
#![warn(clippy::all, rust_2018_idioms)]
|
||||
|
||||
use egui_node_graph_example::NodeGraphExample;
|
||||
|
||||
// When compiling natively:
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn main() {
|
||||
@ -14,10 +16,10 @@ fn main() {
|
||||
cc.egui_ctx.set_visuals(Visuals::dark());
|
||||
#[cfg(feature = "persistence")]
|
||||
{
|
||||
Box::new(egui_node_graph_example::NodeGraphExample::new(cc))
|
||||
Box::new(NodeGraphExample::new(cc))
|
||||
}
|
||||
#[cfg(not(feature = "persistence"))]
|
||||
Box::new(egui_node_graph_example::NodeGraphExample::default())
|
||||
Box::<NodeGraphExample>::default()
|
||||
}),
|
||||
)
|
||||
.expect("Failed to run native example");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user