Skip to content

Commit 7af60e0

Browse files
authored
Add the auto-generated node catalog to the website's user manual (#3662)
* Generate the MVP node catalog in the manual (with some placeholders) * Implement nearly the rest of everything * Move to the tools directory and make it generate nicer default values * Add category descriptions * Organize file structure and improve type naming * Improve book table of contents code * Add collapsing chapter navigation to the book template * Add to build workflow * Clean up site structure
1 parent 5543afd commit 7af60e0

61 files changed

Lines changed: 1136 additions & 389 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/website.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ jobs:
8080
cd website
8181
npm run generate-editor-structure
8282
83+
- name: Generate node catalog documentation
84+
run: |
85+
cd tools/node-docs
86+
cargo run
87+
8388
- name: 🌐 Build Graphite website with Zola
8489
env:
8590
MODE: prod

.vscode/settings.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
// Configured in `.prettierrc`
2727
"editor.defaultFormatter": "esbenp.prettier-vscode"
2828
},
29+
// Website: don't format Zola/Tera-templated HTML on save
30+
"[html]": {
31+
"editor.formatOnSave": false
32+
},
2933
// Handlebars: don't save on format
3034
// (`about.hbs` is used by Cargo About to encode license information)
3135
"[handlebars]": {

Cargo.lock

Lines changed: 17 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ members = [
3939
"node-graph/node-macro",
4040
"node-graph/preprocessor",
4141
"proc-macros",
42-
"tools/crate-hierarchy-viz"
42+
"tools/crate-hierarchy-viz",
43+
"tools/node-docs"
4344
]
4445
default-members = [
4546
"editor",
@@ -137,6 +138,7 @@ log = "0.4"
137138
bitflags = { version = "2.4", features = ["serde"] }
138139
ctor = "0.2"
139140
convert_case = "0.8"
141+
indoc = "2.0.5"
140142
derivative = "2.2"
141143
thiserror = "2"
142144
anyhow = "1.0"
@@ -151,7 +153,7 @@ wgpu = { version = "27.0", features = [
151153
"spirv",
152154
"strict_asserts",
153155
] }
154-
once_cell = "1.13" # Remove when `core::cell::LazyCell` (<https://doc.rust-lang.org/core/cell/struct.LazyCell.html>) is stabilized in Rust 1.80 and we bump our MSRV
156+
once_cell = "1.13" # Remove and replace with `core::cell::LazyCell` (<https://doc.rust-lang.org/core/cell/struct.LazyCell.html>)
155157
wasm-bindgen = "=0.2.100" # NOTICE: ensure this stays in sync with the `wasm-bindgen-cli` version in `website/content/volunteer/guide/project-setup/_index.md`. We pin this version because wasm-bindgen upgrades may break various things.
156158
wasm-bindgen-futures = "0.4"
157159
js-sys = "=0.3.77"
@@ -177,10 +179,16 @@ winit = { git = "https://github.com/rust-windowing/winit.git" }
177179
keyboard-types = "0.8"
178180
url = "2.5"
179181
tokio = { version = "1.29", features = ["fs", "macros", "io-std", "rt"] }
182+
# Linebender ecosystem (BEGIN)
183+
kurbo = { version = "0.12", features = ["serde"] }
180184
vello = { git = "https://github.com/linebender/vello" }
181185
vello_encoding = { git = "https://github.com/linebender/vello" }
182186
resvg = "0.45"
183187
usvg = "0.45"
188+
parley = "0.6"
189+
skrifa = "0.36"
190+
polycool = "0.4"
191+
# Linebender ecosystem (END)
184192
rand = { version = "0.9", default-features = false, features = ["std_rng"] }
185193
rand_chacha = "0.9"
186194
glam = { version = "0.29", default-features = false, features = [
@@ -194,8 +202,6 @@ image = { version = "0.25", default-features = false, features = [
194202
"jpeg",
195203
"bmp",
196204
] }
197-
parley = "0.6"
198-
skrifa = "0.36"
199205
pretty_assertions = "1.4"
200206
fern = { version = "0.7", features = ["colored"] }
201207
num_enum = { version = "0.7", default-features = false }
@@ -217,7 +223,6 @@ syn = { version = "2.0", default-features = false, features = [
217223
"extra-traits",
218224
"proc-macro",
219225
] }
220-
kurbo = { version = "0.12", features = ["serde"] }
221226
lyon_geom = "1.0"
222227
petgraph = { version = "0.7", default-features = false, features = ["graphmap"] }
223228
half = { version = "2.4", default-features = false, features = ["bytemuck"] }
@@ -234,7 +239,6 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
234239
tracing = "0.1"
235240
rfd = "0.15"
236241
open = "5.3"
237-
polycool = "0.4"
238242
spin = "0.10"
239243
clap = "4.5"
240244
spirv-std = { git = "https://github.com/Firestar99/rust-gpu-new", rev = "c12f216121820580731440ee79ebc7403d6ea04f", features = ["bytemuck"] }

editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ pub struct DocumentNodeDefinition {
124124

125125
// We use the once_cell to use the document node definitions throughout the editor without passing a reference
126126
// TODO: If dynamic node library is required, use a Mutex as well
127+
// TODO: Replace with `core::cell::LazyCell` (<https://doc.rust-lang.org/core/cell/struct.LazyCell.html>) or similar
127128
static DOCUMENT_NODE_TYPES: once_cell::sync::Lazy<HashMap<DefinitionIdentifier, DocumentNodeDefinition>> = once_cell::sync::Lazy::new(document_node_definitions);
128129

129130
/// Defines the "signature" or "header file"-like metadata for the document nodes, but not the implementation (which is defined in the node registry).
@@ -1773,7 +1774,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
17731774
},
17741775
DocumentNodeDefinition {
17751776
identifier: "Boolean Operation",
1776-
category: "Vector",
1777+
category: "Vector: Modifier",
17771778
node_template: NodeTemplate {
17781779
document_node: DocumentNode {
17791780
implementation: DocumentNodeImplementation::Network(NodeNetwork {
@@ -2076,6 +2077,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
20762077

20772078
type NodeProperties = HashMap<String, Box<dyn Fn(NodeId, &mut NodePropertiesContext) -> Vec<LayoutGroup> + Send + Sync>>;
20782079

2080+
// TODO: Replace with `core::cell::LazyCell` (<https://doc.rust-lang.org/core/cell/struct.LazyCell.html>) or similar
20792081
pub static NODE_OVERRIDES: once_cell::sync::Lazy<NodeProperties> = once_cell::sync::Lazy::new(static_node_properties);
20802082

20812083
/// Defines the logic for inputs to display a custom properties panel widget.
@@ -2102,6 +2104,7 @@ fn static_node_properties() -> NodeProperties {
21022104

21032105
type InputProperties = HashMap<String, Box<dyn Fn(NodeId, usize, &mut NodePropertiesContext) -> Result<Vec<LayoutGroup>, String> + Send + Sync>>;
21042106

2107+
// TODO: Replace with `core::cell::LazyCell` (<https://doc.rust-lang.org/core/cell/struct.LazyCell.html>) or similar
21052108
static INPUT_OVERRIDES: once_cell::sync::Lazy<InputProperties> = once_cell::sync::Lazy::new(static_input_properties);
21062109

21072110
/// Defines the logic for inputs to display a custom properties panel widget.

editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ pub(super) fn post_process_nodes(custom: Vec<DocumentNodeDefinition>) -> HashMap
7575
..Default::default()
7676
},
7777
},
78-
category: category.unwrap_or("UNCATEGORIZED"),
78+
category,
7979
description: Cow::Borrowed(description),
8080
properties: *properties,
8181
},

editor/src/node_graph_executor/runtime.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ use graphene_std::wasm_application_io::{RenderOutputType, WasmApplicationIo, Was
2222
use graphene_std::{Artboard, Context, Graphic};
2323
use interpreted_executor::dynamic_executor::{DynamicExecutor, IntrospectError, ResolvedDocumentNodeTypesDelta};
2424
use interpreted_executor::util::wrap_network_in_scope;
25-
use once_cell::sync::Lazy;
2625
use spin::Mutex;
2726
use std::sync::Arc;
2827
use std::sync::mpsc::{Receiver, Sender};
@@ -108,7 +107,8 @@ impl NodeGraphUpdateSender for InternalNodeGraphUpdateSender {
108107
}
109108
}
110109

111-
pub static NODE_RUNTIME: Lazy<Mutex<Option<NodeRuntime>>> = Lazy::new(|| Mutex::new(None));
110+
// TODO: Replace with `core::cell::LazyCell` (<https://doc.rust-lang.org/core/cell/struct.LazyCell.html>) or similar
111+
pub static NODE_RUNTIME: once_cell::sync::Lazy<Mutex<Option<NodeRuntime>>> = once_cell::sync::Lazy::new(|| Mutex::new(None));
112112

113113
impl NodeRuntime {
114114
pub fn new(receiver: Receiver<GraphRuntimeRequest>, sender: Sender<NodeGraphUpdate>) -> Self {

node-graph/graph-craft/src/document/value.rs

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -116,15 +116,15 @@ macro_rules! tagged_value {
116116
_ => Err(format!("Cannot convert {:?} to TaggedValue",std::any::type_name_of_val(input))),
117117
}
118118
}
119+
/// Returns a TaggedValue from the type, where that value is its type's `Default::default()`
119120
pub fn from_type(input: &Type) -> Option<Self> {
120121
match input {
121122
Type::Generic(_) => None,
122123
Type::Concrete(concrete_type) => {
123-
let internal_id = concrete_type.id?;
124124
use std::any::TypeId;
125125
// TODO: Add default implementations for types such as TaggedValue::Subpaths, and use the defaults here and in document_node_types
126126
// Tries using the default for the tagged value type. If it not implemented, then uses the default used in document_node_types. If it is not used there, then TaggedValue::None is returned.
127-
Some(match internal_id {
127+
Some(match concrete_type.id? {
128128
x if x == TypeId::of::<()>() => TaggedValue::None,
129129
$( x if x == TypeId::of::<$ty>() => TaggedValue::$identifier(Default::default()), )*
130130
_ => return None,
@@ -139,6 +139,15 @@ macro_rules! tagged_value {
139139
pub fn from_type_or_none(input: &Type) -> Self {
140140
Self::from_type(input).unwrap_or(TaggedValue::None)
141141
}
142+
pub fn to_debug_string(&self) -> String {
143+
match self {
144+
Self::None => "()".to_string(),
145+
$( Self::$identifier(x) => format!("{:?}", x), )*
146+
Self::RenderOutput(_) => "RenderOutput".to_string(),
147+
Self::SurfaceFrame(_) => "SurfaceFrame".to_string(),
148+
Self::EditorApi(_) => "WasmEditorApi".to_string(),
149+
}
150+
}
142151
}
143152

144153
$(
@@ -351,24 +360,24 @@ impl TaggedValue {
351360
match ty {
352361
Type::Generic(_) => None,
353362
Type::Concrete(concrete_type) => {
354-
let internal_id = concrete_type.id?;
363+
let ty = concrete_type.id?;
355364
use std::any::TypeId;
356365
// TODO: Add default implementations for types such as TaggedValue::Subpaths, and use the defaults here and in document_node_types
357366
// Tries using the default for the tagged value type. If it not implemented, then uses the default used in document_node_types. If it is not used there, then TaggedValue::None is returned.
358-
let ty = match internal_id {
359-
x if x == TypeId::of::<()>() => TaggedValue::None,
360-
x if x == TypeId::of::<String>() => TaggedValue::String(string.into()),
361-
x if x == TypeId::of::<f64>() => FromStr::from_str(string).map(TaggedValue::F64).ok()?,
362-
x if x == TypeId::of::<f32>() => FromStr::from_str(string).map(TaggedValue::F32).ok()?,
363-
x if x == TypeId::of::<u64>() => FromStr::from_str(string).map(TaggedValue::U64).ok()?,
364-
x if x == TypeId::of::<u32>() => FromStr::from_str(string).map(TaggedValue::U32).ok()?,
365-
x if x == TypeId::of::<DVec2>() => to_dvec2(string).map(TaggedValue::DVec2)?,
366-
x if x == TypeId::of::<bool>() => FromStr::from_str(string).map(TaggedValue::Bool).ok()?,
367-
x if x == TypeId::of::<Color>() => to_color(string).map(TaggedValue::ColorNotInTable)?,
368-
x if x == TypeId::of::<Option<Color>>() => TaggedValue::ColorNotInTable(to_color(string)?),
369-
x if x == TypeId::of::<Table<Color>>() => to_color(string).map(|color| TaggedValue::Color(Table::new_from_element(color)))?,
370-
x if x == TypeId::of::<Fill>() => to_color(string).map(|color| TaggedValue::Fill(Fill::solid(color)))?,
371-
x if x == TypeId::of::<ReferencePoint>() => to_reference_point(string).map(TaggedValue::ReferencePoint)?,
367+
let ty = match () {
368+
() if ty == TypeId::of::<()>() => TaggedValue::None,
369+
() if ty == TypeId::of::<String>() => TaggedValue::String(string.into()),
370+
() if ty == TypeId::of::<f64>() => FromStr::from_str(string).map(TaggedValue::F64).ok()?,
371+
() if ty == TypeId::of::<f32>() => FromStr::from_str(string).map(TaggedValue::F32).ok()?,
372+
() if ty == TypeId::of::<u64>() => FromStr::from_str(string).map(TaggedValue::U64).ok()?,
373+
() if ty == TypeId::of::<u32>() => FromStr::from_str(string).map(TaggedValue::U32).ok()?,
374+
() if ty == TypeId::of::<DVec2>() => to_dvec2(string).map(TaggedValue::DVec2)?,
375+
() if ty == TypeId::of::<bool>() => FromStr::from_str(string).map(TaggedValue::Bool).ok()?,
376+
() if ty == TypeId::of::<Color>() => to_color(string).map(TaggedValue::ColorNotInTable)?,
377+
() if ty == TypeId::of::<Option<Color>>() => TaggedValue::ColorNotInTable(to_color(string)?),
378+
() if ty == TypeId::of::<Table<Color>>() => to_color(string).map(|color| TaggedValue::Color(Table::new_from_element(color)))?,
379+
() if ty == TypeId::of::<Fill>() => to_color(string).map(|color| TaggedValue::Fill(Fill::solid(color)))?,
380+
() if ty == TypeId::of::<ReferencePoint>() => to_reference_point(string).map(TaggedValue::ReferencePoint)?,
372381
_ => return None,
373382
};
374383
Some(ty)

node-graph/graphene-cli/src/export.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub fn detect_file_type(path: &Path) -> Result<FileType, String> {
2121
Some("svg") => Ok(FileType::Svg),
2222
Some("png") => Ok(FileType::Png),
2323
Some("jpg" | "jpeg") => Ok(FileType::Jpg),
24-
_ => Err(format!("Unsupported file extension. Supported formats: .svg, .png, .jpg")),
24+
_ => Err("Unsupported file extension. Supported formats: .svg, .png, .jpg".to_string()),
2525
}
2626
}
2727

@@ -31,8 +31,7 @@ pub async fn export_document(
3131
output_path: PathBuf,
3232
file_type: FileType,
3333
scale: f64,
34-
width: Option<u32>,
35-
height: Option<u32>,
34+
(width, height): (Option<u32>, Option<u32>),
3635
transparent: bool,
3736
) -> Result<(), Box<dyn Error>> {
3837
// Determine export format based on file type
@@ -42,10 +41,12 @@ pub async fn export_document(
4241
};
4342

4443
// Create render config with export settings
45-
let mut render_config = RenderConfig::default();
46-
render_config.export_format = export_format;
47-
render_config.for_export = true;
48-
render_config.scale = scale;
44+
let mut render_config = RenderConfig {
45+
scale,
46+
export_format,
47+
for_export: true,
48+
..Default::default()
49+
};
4950

5051
// Set viewport dimensions if specified
5152
if let (Some(w), Some(h)) = (width, height) {

node-graph/graphene-cli/src/main.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
9797
Command::Compile { ref document, .. } => document,
9898
Command::Export { ref document, .. } => document,
9999
Command::ListNodeIdentifiers => {
100-
let mut ids: Vec<_> = graphene_std::registry::NODE_METADATA.lock().unwrap().keys().cloned().collect();
101-
ids.sort_by_key(|x| x.as_str().to_string());
102-
for id in ids {
100+
let mut nodes: Vec<_> = graphene_std::registry::NODE_METADATA.lock().unwrap().keys().cloned().collect();
101+
nodes.sort_by_key(|x| x.as_str().to_string());
102+
for id in nodes {
103103
println!("{}", id.as_str());
104104
}
105105
return Ok(());
@@ -108,7 +108,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
108108

109109
let document_string = std::fs::read_to_string(document_path).expect("Failed to read document");
110110

111-
log::info!("creating gpu context",);
111+
log::info!("Creating GPU context");
112112
let mut application_io = block_on(WasmApplicationIo::new_offscreen());
113113

114114
if let Command::Export { image: Some(ref image_path), .. } = app.command {
@@ -164,7 +164,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
164164
let executor = create_executor(proto_graph)?;
165165

166166
// Perform export
167-
export::export_document(&executor, wgpu_executor_ref, output, file_type, scale, width, height, transparent).await?;
167+
export::export_document(&executor, wgpu_executor_ref, output, file_type, scale, (width, height), transparent).await?;
168168
}
169169
_ => unreachable!("All other commands should be handled before this match statement is run"),
170170
}

0 commit comments

Comments
 (0)