Merge pull request #90 from kamirr/kek/categories

node categories
This commit is contained in:
setzer22 2023-05-15 11:23:11 +02:00 committed by GitHub
commit 943cd4b94c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 138 additions and 21 deletions

View File

@ -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(

View File

@ -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;
}
}
});

View File

@ -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;

View File

@ -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.

View File

@ -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");