Skip to content
This repository has been archived by the owner on Apr 9, 2024. It is now read-only.

Commit

Permalink
Merge branch 'main' into kek/many-to-many
Browse files Browse the repository at this point in the history
  • Loading branch information
kamirr committed May 16, 2023
2 parents 15dbdcd + 943cd4b commit 7b0d018
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 25 deletions.
27 changes: 23 additions & 4 deletions egui_node_graph/src/editor_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,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<
Expand All @@ -104,8 +104,10 @@ where
DataType = DataType,
ValueType = ValueType,
UserState = UserState,
CategoryType = CategoryType,
>,
DataType: DataTypeTrait<UserState>,
CategoryType: CategoryTrait,
{
#[must_use]
pub fn draw_graph_editor(
Expand All @@ -118,12 +120,12 @@ where
// (so for windows it will use up to the resizeably set limit
// and for a Panel it will fill it completely)
let editor_rect = ui.max_rect();
ui.allocate_rect(editor_rect, Sense::hover());
let resp = ui.allocate_rect(editor_rect, Sense::hover());

let cursor_pos = ui
.ctx()
.input(|i| i.pointer.hover_pos().unwrap_or(Pos2::ZERO));
let mut cursor_in_editor = editor_rect.contains(cursor_pos);
let mut cursor_in_editor = resp.hovered();
let mut cursor_in_finder = false;

// Gets filled with the node metrics as they are drawn
Expand Down Expand Up @@ -631,6 +633,14 @@ where
ui.label(param_name);
}

self.graph[self.node_id].user_data.separator(
ui,
self.node_id,
AnyParameterId::Input(param_id),
self.graph,
user_state,
);

let height_intermediate = ui.min_rect().bottom();

let max_connections = self.graph[param_id]
Expand All @@ -657,14 +667,23 @@ where
}

let outputs = self.graph[self.node_id].outputs.clone();
for (param_name, _param) in outputs {
for (param_name, param_id) in outputs {
let height_before = ui.min_rect().bottom();
responses.extend(
self.graph[self.node_id]
.user_data
.output_ui(ui, self.node_id, self.graph, user_state, &param_name)
.into_iter(),
);

self.graph[self.node_id].user_data.separator(
ui,
self.node_id,
AnyParameterId::Output(param_id),
self.graph,
user_state,
);

let height_after = ui.min_rect().bottom();
output_port_heights.push((height_before + height_after) / 2.0);
}
Expand Down
82 changes: 67 additions & 15 deletions egui_node_graph/src/node_finder.rs
Original file line number Diff line number Diff line change
@@ -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::*;

Expand All @@ -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 {
Expand Down Expand Up @@ -62,31 +64,81 @@ 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| {
ScrollArea::vertical()
.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;
}
}
});
Expand Down
67 changes: 66 additions & 1 deletion egui_node_graph/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ where
where
Self::Response: UserResponseTrait;

// UI to draw on the top bar of the node.
/// UI to draw on the top bar of the node.
fn top_bar_ui(
&self,
_ui: &mut egui::Ui,
Expand Down Expand Up @@ -158,6 +158,24 @@ where
None
}

/// Separator to put between elements in the node.
///
/// Invoked between inputs, outputs and bottom UI. Useful for
/// complicated UIs that start to lose structure without explicit
/// separators. The `param_id` argument is the id of input or output
/// *preceeding* the separator.
///
/// Default implementation does nothing.
fn separator(
&self,
_ui: &mut egui::Ui,
_node_id: NodeId,
_param_id: AnyParameterId,
_graph: &Graph<Self, Self::DataType, Self::ValueType>,
_user_state: &mut Self::UserState,
) {
}

fn can_delete(
&self,
_node_id: NodeId,
Expand All @@ -176,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
Expand All @@ -189,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.
///
Expand All @@ -197,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;

Expand Down
20 changes: 17 additions & 3 deletions egui_node_graph_example/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,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
Expand Down Expand Up @@ -136,19 +136,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.
Expand Down
6 changes: 4 additions & 2 deletions egui_node_graph_example/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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");
Expand Down

0 comments on commit 7b0d018

Please sign in to comment.