mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-08 00:05:00 +00:00
New node: Assign Colors (#1938)
* New node: Assign Colors * Simplify the Procedural String Lights demo artwork * Add "Group" node to the node catalog * Better comment styling in profiling action
This commit is contained in:
parent
12ebc6f972
commit
fa981a0897
12 changed files with 573 additions and 231 deletions
150
.github/workflows/profiling.yaml
vendored
150
.github/workflows/profiling.yaml
vendored
|
@ -2,7 +2,7 @@ name: Profiling
|
|||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
branches: [master]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
@ -11,90 +11,90 @@ jobs:
|
|||
profile:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
|
||||
- name: Install Valgrind
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y valgrind
|
||||
- name: Install Valgrind
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y valgrind
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- name: Install iai-callgrind
|
||||
run: |
|
||||
cargo install iai-callgrind-runner@0.12.3
|
||||
- name: Install iai-callgrind
|
||||
run: |
|
||||
cargo install iai-callgrind-runner@0.12.3
|
||||
|
||||
- name: Checkout master branch
|
||||
run: |
|
||||
git fetch origin master:master
|
||||
git checkout master
|
||||
- name: Checkout master branch
|
||||
run: |
|
||||
git fetch origin master:master
|
||||
git checkout master
|
||||
|
||||
- name: Run baseline benchmarks
|
||||
run: |
|
||||
cargo bench --bench compile_demo_art --features=iai -- --save-baseline=master
|
||||
- name: Run baseline benchmarks
|
||||
run: |
|
||||
cargo bench --bench compile_demo_art --features=iai -- --save-baseline=master
|
||||
|
||||
- name: Checkout PR branch
|
||||
run: |
|
||||
git checkout ${{ github.event.pull_request.head.sha }}
|
||||
- name: Checkout PR branch
|
||||
run: |
|
||||
git checkout ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Run PR benchmarks
|
||||
id: benchmark
|
||||
run: |
|
||||
BENCH_OUTPUT=$(cargo bench --bench compile_demo_art --features=iai -- --baseline=master --output-format=json | jq -sc | sed 's/\\"//g')
|
||||
echo "BENCHMARK_OUTPUT<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$BENCH_OUTPUT" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
- name: Run PR benchmarks
|
||||
id: benchmark
|
||||
run: |
|
||||
BENCH_OUTPUT=$(cargo bench --bench compile_demo_art --features=iai -- --baseline=master --output-format=json | jq -sc | sed 's/\\"//g')
|
||||
echo "BENCHMARK_OUTPUT<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$BENCH_OUTPUT" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Comment PR
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
const benchmarkOutput = JSON.parse(`${{ steps.benchmark.outputs.BENCHMARK_OUTPUT }}`);
|
||||
|
||||
let significantChanges = false;
|
||||
let commentBody = "#### Performance Benchmark Results\n\n";
|
||||
|
||||
for (const benchmark of benchmarkOutput) {
|
||||
if (benchmark.callgrind_summary && benchmark.callgrind_summary.summaries) {
|
||||
for (const summary of benchmark.callgrind_summary.summaries) {
|
||||
for (const [eventKind, costsDiff] of Object.entries(summary.events)) {
|
||||
if (costsDiff.diff_pct !== null && Math.abs(costsDiff.diff_pct) > 5) {
|
||||
significantChanges = true;
|
||||
const changeDirection = costsDiff.diff_pct > 0 ? "increase" : "decrease";
|
||||
const color = costsDiff.diff_pct > 0 ? "red" : "green";
|
||||
commentBody += `\`${benchmark.module_path}\` - ${eventKind}:\n`;
|
||||
commentBody += `\\color{${color}}${changeDirection} of ${Math.abs(costsDiff.diff_pct).toFixed(2)}%\n`;
|
||||
commentBody += `Old: ${costsDiff.old}, New: ${costsDiff.new}\n\n`;
|
||||
- name: Comment PR
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
const benchmarkOutput = JSON.parse(`${{ steps.benchmark.outputs.BENCHMARK_OUTPUT }}`);
|
||||
|
||||
let significantChanges = false;
|
||||
let commentBody = "#### Performance Benchmark Results\n\n";
|
||||
|
||||
for (const benchmark of benchmarkOutput) {
|
||||
if (benchmark.callgrind_summary && benchmark.callgrind_summary.summaries) {
|
||||
for (const summary of benchmark.callgrind_summary.summaries) {
|
||||
for (const [eventKind, costsDiff] of Object.entries(summary.events)) {
|
||||
if (costsDiff.diff_pct !== null && Math.abs(costsDiff.diff_pct) > 5) {
|
||||
significantChanges = true;
|
||||
const changeDirection = costsDiff.diff_pct > 0 ? "Increase" : "Decrease";
|
||||
const color = costsDiff.diff_pct > 0 ? "red" : "lime";
|
||||
commentBody += `\`${benchmark.module_path}\` - ${eventKind}:\n`;
|
||||
commentBody += `${changeDirection} of $$\\color{${color}}${Math.abs(costsDiff.diff_pct).toFixed(2)}\\\\%$$\n`;
|
||||
commentBody += `Old: ${costsDiff.old}, New: ${costsDiff.new}\n\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (significantChanges) {
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: commentBody
|
||||
});
|
||||
} else {
|
||||
console.log("No significant performance changes detected. Skipping comment.");
|
||||
console.log(commentBody);
|
||||
}
|
||||
|
||||
if (significantChanges) {
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: commentBody
|
||||
});
|
||||
} else {
|
||||
console.log("No significant performance changes detected. Skipping comment.");
|
||||
console.log(commentBody);
|
||||
}
|
||||
|
|
2
demo-artwork/isometric-fountain.graphite
generated
2
demo-artwork/isometric-fountain.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/painted-dreams.graphite
generated
2
demo-artwork/painted-dreams.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/procedural-string-lights.graphite
generated
2
demo-artwork/procedural-string-lights.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/red-dress.graphite
generated
2
demo-artwork/red-dress.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/valley-of-spires.graphite
generated
2
demo-artwork/valley-of-spires.graphite
generated
File diff suppressed because one or more lines are too long
|
@ -172,6 +172,23 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
category: "Structural",
|
||||
properties: |_document_node, _node_id, _context| node_properties::string_properties("The Monitor node stores the value of its last evaluation"),
|
||||
},
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Group",
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::ToGraphicGroupNode"),
|
||||
inputs: vec![NodeInput::value(TaggedValue::VectorData(VectorData::empty()), true)],
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_names: vec!["Element".to_string()],
|
||||
output_names: vec!["Graphic Group".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
category: "General",
|
||||
properties: node_properties::node_no_properties,
|
||||
},
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Merge",
|
||||
node_template: NodeTemplate {
|
||||
|
@ -235,7 +252,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
},
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "To Graphic Group".to_string(),
|
||||
display_name: "Group".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-14, -3)),
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -3499,6 +3516,39 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
category: "Transform",
|
||||
properties: node_properties::node_no_properties,
|
||||
},
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Assign Colors",
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::vector::AssignColorsNode<_, _, _, _, _, _>"),
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::GraphicGroup(graphene_core::GraphicGroup::default()), true),
|
||||
NodeInput::value(TaggedValue::Bool(true), false),
|
||||
NodeInput::value(TaggedValue::Bool(false), false),
|
||||
NodeInput::value(TaggedValue::GradientStops(vector::style::GradientStops::default()), false),
|
||||
NodeInput::value(TaggedValue::Bool(false), false),
|
||||
NodeInput::value(TaggedValue::Bool(false), false),
|
||||
NodeInput::value(TaggedValue::U32(0), false),
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_names: vec![
|
||||
"Vector Group".to_string(),
|
||||
"Fill".to_string(),
|
||||
"Stroke".to_string(),
|
||||
"Gradient".to_string(),
|
||||
"Reverse".to_string(),
|
||||
"Randomize".to_string(),
|
||||
"Repeat Every".to_string(),
|
||||
],
|
||||
output_names: vec!["Vector Group".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
category: "Vector",
|
||||
properties: node_properties::assign_colors_properties,
|
||||
},
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Fill",
|
||||
node_template: NodeTemplate {
|
||||
|
|
|
@ -87,7 +87,11 @@ fn start_widgets(document_node: &DocumentNode, node_id: NodeId, index: usize, na
|
|||
fn text_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> Vec<WidgetHolder> {
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
||||
|
||||
if let Some(TaggedValue::String(x)) = &document_node.inputs[index].as_non_exposed_value() {
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
};
|
||||
if let Some(TaggedValue::String(x)) = &input.as_non_exposed_value() {
|
||||
widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
TextInput::new(x.clone())
|
||||
|
@ -102,7 +106,11 @@ fn text_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name
|
|||
fn text_area_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> Vec<WidgetHolder> {
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
||||
|
||||
if let Some(TaggedValue::String(x)) = &document_node.inputs[index].as_non_exposed_value() {
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
};
|
||||
if let Some(TaggedValue::String(x)) = &input.as_non_exposed_value() {
|
||||
widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
TextAreaInput::new(x.clone())
|
||||
|
@ -117,7 +125,11 @@ fn text_area_widget(document_node: &DocumentNode, node_id: NodeId, index: usize,
|
|||
fn bool_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> Vec<WidgetHolder> {
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
||||
|
||||
if let Some(&TaggedValue::Bool(x)) = &document_node.inputs[index].as_non_exposed_value() {
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
};
|
||||
if let Some(&TaggedValue::Bool(x)) = &input.as_non_exposed_value() {
|
||||
widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
CheckboxInput::new(x)
|
||||
|
@ -141,7 +153,11 @@ fn footprint_widget(document_node: &DocumentNode, node_id: NodeId, index: usize)
|
|||
add_blank_assist(&mut resolution_widgets);
|
||||
resolution_widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
|
||||
if let Some(&TaggedValue::Footprint(footprint)) = &document_node.inputs[index].as_non_exposed_value() {
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
};
|
||||
if let Some(&TaggedValue::Footprint(footprint)) = &input.as_non_exposed_value() {
|
||||
let top_left = footprint.transform.transform_point2(DVec2::ZERO);
|
||||
let bounds = footprint.scale();
|
||||
let oversample = footprint.resolution.as_dvec2() / bounds;
|
||||
|
@ -271,77 +287,86 @@ fn vec2_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name
|
|||
|
||||
assist(&mut widgets);
|
||||
|
||||
if let Some(&TaggedValue::DVec2(dvec2)) = document_node.inputs[index].as_non_exposed_value() {
|
||||
widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
NumberInput::new(Some(dvec2.x))
|
||||
.label(x)
|
||||
.unit(unit)
|
||||
.min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64)))
|
||||
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
||||
.on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(input.value.unwrap(), dvec2.y)), node_id, index))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
Separator::new(SeparatorType::Related).widget_holder(),
|
||||
NumberInput::new(Some(dvec2.y))
|
||||
.label(y)
|
||||
.unit(unit)
|
||||
.min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64)))
|
||||
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
||||
.on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(dvec2.x, input.value.unwrap())), node_id, index))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
]);
|
||||
} else if let Some(&TaggedValue::IVec2(ivec2)) = document_node.inputs[index].as_non_exposed_value() {
|
||||
let update_x = move |input: &NumberInput| TaggedValue::IVec2(IVec2::new(input.value.unwrap() as i32, ivec2.y));
|
||||
let update_y = move |input: &NumberInput| TaggedValue::IVec2(IVec2::new(ivec2.x, input.value.unwrap() as i32));
|
||||
widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
NumberInput::new(Some(ivec2.x as f64))
|
||||
.int()
|
||||
.label(x)
|
||||
.unit(unit)
|
||||
.min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64)))
|
||||
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
||||
.on_update(update_value(update_x, node_id, index))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
Separator::new(SeparatorType::Related).widget_holder(),
|
||||
NumberInput::new(Some(ivec2.y as f64))
|
||||
.int()
|
||||
.label(y)
|
||||
.unit(unit)
|
||||
.min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64)))
|
||||
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
||||
.on_update(update_value(update_y, node_id, index))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
]);
|
||||
} else if let Some(&TaggedValue::UVec2(uvec2)) = document_node.inputs[index].as_non_exposed_value() {
|
||||
let update_x = move |input: &NumberInput| TaggedValue::UVec2(UVec2::new(input.value.unwrap() as u32, uvec2.y));
|
||||
let update_y = move |input: &NumberInput| TaggedValue::UVec2(UVec2::new(uvec2.x, input.value.unwrap() as u32));
|
||||
widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
NumberInput::new(Some(uvec2.x as f64))
|
||||
.int()
|
||||
.label(x)
|
||||
.unit(unit)
|
||||
.min(min.unwrap_or(0.))
|
||||
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
||||
.on_update(update_value(update_x, node_id, index))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
Separator::new(SeparatorType::Related).widget_holder(),
|
||||
NumberInput::new(Some(uvec2.y as f64))
|
||||
.int()
|
||||
.label(y)
|
||||
.unit(unit)
|
||||
.min(min.unwrap_or(0.))
|
||||
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
||||
.on_update(update_value(update_y, node_id, index))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
]);
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return LayoutGroup::Row { widgets: vec![] };
|
||||
};
|
||||
match input.as_non_exposed_value() {
|
||||
Some(&TaggedValue::DVec2(dvec2)) => {
|
||||
widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
NumberInput::new(Some(dvec2.x))
|
||||
.label(x)
|
||||
.unit(unit)
|
||||
.min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64)))
|
||||
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
||||
.on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(input.value.unwrap(), dvec2.y)), node_id, index))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
Separator::new(SeparatorType::Related).widget_holder(),
|
||||
NumberInput::new(Some(dvec2.y))
|
||||
.label(y)
|
||||
.unit(unit)
|
||||
.min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64)))
|
||||
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
||||
.on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(dvec2.x, input.value.unwrap())), node_id, index))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
]);
|
||||
}
|
||||
Some(&TaggedValue::IVec2(ivec2)) => {
|
||||
let update_x = move |input: &NumberInput| TaggedValue::IVec2(IVec2::new(input.value.unwrap() as i32, ivec2.y));
|
||||
let update_y = move |input: &NumberInput| TaggedValue::IVec2(IVec2::new(ivec2.x, input.value.unwrap() as i32));
|
||||
widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
NumberInput::new(Some(ivec2.x as f64))
|
||||
.int()
|
||||
.label(x)
|
||||
.unit(unit)
|
||||
.min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64)))
|
||||
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
||||
.on_update(update_value(update_x, node_id, index))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
Separator::new(SeparatorType::Related).widget_holder(),
|
||||
NumberInput::new(Some(ivec2.y as f64))
|
||||
.int()
|
||||
.label(y)
|
||||
.unit(unit)
|
||||
.min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64)))
|
||||
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
||||
.on_update(update_value(update_y, node_id, index))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
]);
|
||||
}
|
||||
Some(&TaggedValue::UVec2(uvec2)) => {
|
||||
let update_x = move |input: &NumberInput| TaggedValue::UVec2(UVec2::new(input.value.unwrap() as u32, uvec2.y));
|
||||
let update_y = move |input: &NumberInput| TaggedValue::UVec2(UVec2::new(uvec2.x, input.value.unwrap() as u32));
|
||||
widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
NumberInput::new(Some(uvec2.x as f64))
|
||||
.int()
|
||||
.label(x)
|
||||
.unit(unit)
|
||||
.min(min.unwrap_or(0.))
|
||||
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
||||
.on_update(update_value(update_x, node_id, index))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
Separator::new(SeparatorType::Related).widget_holder(),
|
||||
NumberInput::new(Some(uvec2.y as f64))
|
||||
.int()
|
||||
.label(y)
|
||||
.unit(unit)
|
||||
.min(min.unwrap_or(0.))
|
||||
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
||||
.on_update(update_value(update_y, node_id, index))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
]);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
LayoutGroup::Row { widgets }
|
||||
|
@ -360,7 +385,11 @@ fn vec_f64_input(document_node: &DocumentNode, node_id: NodeId, index: usize, na
|
|||
.map(TaggedValue::VecF64)
|
||||
};
|
||||
|
||||
if let Some(TaggedValue::VecF64(x)) = &document_node.inputs[index].as_non_exposed_value() {
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
};
|
||||
if let Some(TaggedValue::VecF64(x)) = &input.as_non_exposed_value() {
|
||||
widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
text_props
|
||||
|
@ -385,7 +414,11 @@ fn vec_dvec2_input(document_node: &DocumentNode, node_id: NodeId, index: usize,
|
|||
.map(TaggedValue::VecDVec2)
|
||||
};
|
||||
|
||||
if let Some(TaggedValue::VecDVec2(x)) = &document_node.inputs[index].as_non_exposed_value() {
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
};
|
||||
if let Some(TaggedValue::VecDVec2(x)) = &input.as_non_exposed_value() {
|
||||
widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
text_props
|
||||
|
@ -403,7 +436,11 @@ fn font_inputs(document_node: &DocumentNode, node_id: NodeId, index: usize, name
|
|||
|
||||
let from_font_input = |font: &FontInput| TaggedValue::Font(Font::new(font.font_family.clone(), font.font_style.clone()));
|
||||
|
||||
if let Some(TaggedValue::Font(font)) = &document_node.inputs[index].as_non_exposed_value() {
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return (vec![], None);
|
||||
};
|
||||
if let Some(TaggedValue::Font(font)) = &input.as_non_exposed_value() {
|
||||
first_widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
FontInput::new(font.font_family.clone(), font.font_style.clone())
|
||||
|
@ -439,32 +476,41 @@ fn vector_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, na
|
|||
fn number_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, number_props: NumberInput, blank_assist: bool) -> Vec<WidgetHolder> {
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Number, blank_assist);
|
||||
|
||||
if let Some(&TaggedValue::F64(x)) = document_node.inputs[index].as_non_exposed_value() {
|
||||
widgets.extend_from_slice(&[
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
};
|
||||
match input.as_non_exposed_value() {
|
||||
Some(&TaggedValue::F64(x)) => widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
number_props
|
||||
.value(Some(x))
|
||||
.on_update(update_value(move |x: &NumberInput| TaggedValue::F64(x.value.unwrap()), node_id, index))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
])
|
||||
} else if let Some(&TaggedValue::U32(x)) = document_node.inputs[index].as_non_exposed_value() {
|
||||
widgets.extend_from_slice(&[
|
||||
]),
|
||||
Some(&TaggedValue::U32(x)) => widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
number_props
|
||||
.value(Some(x as f64))
|
||||
.on_update(update_value(move |x: &NumberInput| TaggedValue::U32((x.value.unwrap()) as u32), node_id, index))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
])
|
||||
]),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
widgets
|
||||
}
|
||||
|
||||
// TODO: Generalize this instead of using a separate function per dropdown menu enum
|
||||
fn color_channel(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup {
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
||||
if let Some(&TaggedValue::RedGreenBlue(mode)) = &document_node.inputs[index].as_non_exposed_value() {
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return LayoutGroup::Row { widgets: vec![] };
|
||||
};
|
||||
if let Some(&TaggedValue::RedGreenBlue(mode)) = &input.as_non_exposed_value() {
|
||||
let calculation_modes = [RedGreenBlue::Red, RedGreenBlue::Green, RedGreenBlue::Blue];
|
||||
let mut entries = Vec::with_capacity(calculation_modes.len());
|
||||
for method in calculation_modes {
|
||||
|
@ -487,7 +533,11 @@ fn color_channel(document_node: &DocumentNode, node_id: NodeId, index: usize, na
|
|||
|
||||
fn rgba_channel(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup {
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
||||
if let Some(&TaggedValue::RedGreenBlueAlpha(mode)) = &document_node.inputs[index].as_non_exposed_value() {
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return LayoutGroup::Row { widgets: vec![] };
|
||||
};
|
||||
if let Some(&TaggedValue::RedGreenBlueAlpha(mode)) = &input.as_non_exposed_value() {
|
||||
let calculation_modes = [RedGreenBlueAlpha::Red, RedGreenBlueAlpha::Green, RedGreenBlueAlpha::Blue, RedGreenBlueAlpha::Alpha];
|
||||
let mut entries = Vec::with_capacity(calculation_modes.len());
|
||||
for method in calculation_modes {
|
||||
|
@ -511,7 +561,11 @@ fn rgba_channel(document_node: &DocumentNode, node_id: NodeId, index: usize, nam
|
|||
// TODO: Generalize this instead of using a separate function per dropdown menu enum
|
||||
fn noise_type(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup {
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
||||
if let Some(&TaggedValue::NoiseType(noise_type)) = &document_node.inputs[index].as_non_exposed_value() {
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return LayoutGroup::Row { widgets: vec![] };
|
||||
};
|
||||
if let Some(&TaggedValue::NoiseType(noise_type)) = &input.as_non_exposed_value() {
|
||||
let entries = NoiseType::list()
|
||||
.iter()
|
||||
.map(|noise_type| {
|
||||
|
@ -533,7 +587,11 @@ fn noise_type(document_node: &DocumentNode, node_id: NodeId, index: usize, name:
|
|||
// TODO: Generalize this instead of using a separate function per dropdown menu enum
|
||||
fn fractal_type(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool, disabled: bool) -> LayoutGroup {
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
||||
if let Some(&TaggedValue::FractalType(fractal_type)) = &document_node.inputs[index].as_non_exposed_value() {
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return LayoutGroup::Row { widgets: vec![] };
|
||||
};
|
||||
if let Some(&TaggedValue::FractalType(fractal_type)) = &input.as_non_exposed_value() {
|
||||
let entries = FractalType::list()
|
||||
.iter()
|
||||
.map(|fractal_type| {
|
||||
|
@ -555,7 +613,11 @@ fn fractal_type(document_node: &DocumentNode, node_id: NodeId, index: usize, nam
|
|||
// TODO: Generalize this instead of using a separate function per dropdown menu enum
|
||||
fn cellular_distance_function(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool, disabled: bool) -> LayoutGroup {
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
||||
if let Some(&TaggedValue::CellularDistanceFunction(cellular_distance_function)) = &document_node.inputs[index].as_non_exposed_value() {
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return LayoutGroup::Row { widgets: vec![] };
|
||||
};
|
||||
if let Some(&TaggedValue::CellularDistanceFunction(cellular_distance_function)) = &input.as_non_exposed_value() {
|
||||
let entries = CellularDistanceFunction::list()
|
||||
.iter()
|
||||
.map(|cellular_distance_function| {
|
||||
|
@ -580,7 +642,11 @@ fn cellular_distance_function(document_node: &DocumentNode, node_id: NodeId, ind
|
|||
// TODO: Generalize this instead of using a separate function per dropdown menu enum
|
||||
fn cellular_return_type(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool, disabled: bool) -> LayoutGroup {
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
||||
if let Some(&TaggedValue::CellularReturnType(cellular_return_type)) = &document_node.inputs[index].as_non_exposed_value() {
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return LayoutGroup::Row { widgets: vec![] };
|
||||
};
|
||||
if let Some(&TaggedValue::CellularReturnType(cellular_return_type)) = &input.as_non_exposed_value() {
|
||||
let entries = CellularReturnType::list()
|
||||
.iter()
|
||||
.map(|cellular_return_type| {
|
||||
|
@ -602,7 +668,11 @@ fn cellular_return_type(document_node: &DocumentNode, node_id: NodeId, index: us
|
|||
// TODO: Generalize this instead of using a separate function per dropdown menu enum
|
||||
fn domain_warp_type(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool, disabled: bool) -> LayoutGroup {
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
||||
if let Some(&TaggedValue::DomainWarpType(domain_warp_type)) = &document_node.inputs[index].as_non_exposed_value() {
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return LayoutGroup::Row { widgets: vec![] };
|
||||
};
|
||||
if let Some(&TaggedValue::DomainWarpType(domain_warp_type)) = &input.as_non_exposed_value() {
|
||||
let entries = DomainWarpType::list()
|
||||
.iter()
|
||||
.map(|domain_warp_type| {
|
||||
|
@ -624,7 +694,11 @@ fn domain_warp_type(document_node: &DocumentNode, node_id: NodeId, index: usize,
|
|||
// TODO: Generalize this instead of using a separate function per dropdown menu enum
|
||||
fn blend_mode(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup {
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
||||
if let Some(&TaggedValue::BlendMode(blend_mode)) = &document_node.inputs[index].as_non_exposed_value() {
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return LayoutGroup::Row { widgets: vec![] };
|
||||
};
|
||||
if let Some(&TaggedValue::BlendMode(blend_mode)) = &input.as_non_exposed_value() {
|
||||
let entries = BlendMode::list_svg_subset()
|
||||
.iter()
|
||||
.map(|category| {
|
||||
|
@ -653,7 +727,11 @@ fn blend_mode(document_node: &DocumentNode, node_id: NodeId, index: usize, name:
|
|||
// TODO: Generalize this for all dropdowns (also see blend_mode and channel_extration)
|
||||
fn luminance_calculation(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup {
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
||||
if let Some(&TaggedValue::LuminanceCalculation(calculation)) = &document_node.inputs[index].as_non_exposed_value() {
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return LayoutGroup::Row { widgets: vec![] };
|
||||
};
|
||||
if let Some(&TaggedValue::LuminanceCalculation(calculation)) = &input.as_non_exposed_value() {
|
||||
let calculation_modes = LuminanceCalculation::list();
|
||||
let mut entries = Vec::with_capacity(calculation_modes.len());
|
||||
for method in calculation_modes {
|
||||
|
@ -677,7 +755,11 @@ fn luminance_calculation(document_node: &DocumentNode, node_id: NodeId, index: u
|
|||
fn boolean_operation_radio_buttons(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup {
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
||||
|
||||
if let Some(&TaggedValue::BooleanOperation(calculation)) = &document_node.inputs[index].as_non_exposed_value() {
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return LayoutGroup::Row { widgets: vec![] };
|
||||
};
|
||||
if let Some(&TaggedValue::BooleanOperation(calculation)) = &input.as_non_exposed_value() {
|
||||
let operations = BooleanOperation::list();
|
||||
let icons = BooleanOperation::icons();
|
||||
let mut entries = Vec::with_capacity(operations.len());
|
||||
|
@ -702,7 +784,11 @@ fn boolean_operation_radio_buttons(document_node: &DocumentNode, node_id: NodeId
|
|||
|
||||
fn line_cap_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup {
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
||||
if let Some(&TaggedValue::LineCap(line_cap)) = &document_node.inputs[index].as_non_exposed_value() {
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return LayoutGroup::Row { widgets: vec![] };
|
||||
};
|
||||
if let Some(&TaggedValue::LineCap(line_cap)) = &input.as_non_exposed_value() {
|
||||
let entries = [("Butt", LineCap::Butt), ("Round", LineCap::Round), ("Square", LineCap::Square)]
|
||||
.into_iter()
|
||||
.map(|(name, val)| {
|
||||
|
@ -723,7 +809,11 @@ fn line_cap_widget(document_node: &DocumentNode, node_id: NodeId, index: usize,
|
|||
|
||||
fn line_join_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup {
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
||||
if let Some(&TaggedValue::LineJoin(line_join)) = &document_node.inputs[index].as_non_exposed_value() {
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return LayoutGroup::Row { widgets: vec![] };
|
||||
};
|
||||
if let Some(&TaggedValue::LineJoin(line_join)) = &input.as_non_exposed_value() {
|
||||
let entries = [("Miter", LineJoin::Miter), ("Bevel", LineJoin::Bevel), ("Round", LineJoin::Round)]
|
||||
.into_iter()
|
||||
.map(|(name, val)| {
|
||||
|
@ -742,7 +832,7 @@ fn line_join_widget(document_node: &DocumentNode, node_id: NodeId, index: usize,
|
|||
LayoutGroup::Row { widgets }
|
||||
}
|
||||
|
||||
fn color_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, color_props: ColorButton, blank_assist: bool) -> LayoutGroup {
|
||||
fn color_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, color_button: ColorButton, blank_assist: bool) -> LayoutGroup {
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
||||
|
||||
// Return early with just the label if the input is exposed to the graph, meaning we don't want to show the color picker widget in the Properties panel
|
||||
|
@ -754,14 +844,14 @@ fn color_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, nam
|
|||
|
||||
match &**tagged_value {
|
||||
TaggedValue::Color(color) => widgets.push(
|
||||
color_props
|
||||
color_button
|
||||
.value(FillChoice::Solid(*color))
|
||||
.on_update(update_value(|x: &ColorButton| TaggedValue::Color(x.value.as_solid().unwrap_or_default()), node_id, index))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
),
|
||||
TaggedValue::OptionalColor(color) => widgets.push(
|
||||
color_props
|
||||
color_button
|
||||
.value(match color {
|
||||
Some(color) => FillChoice::Solid(*color),
|
||||
None => FillChoice::None,
|
||||
|
@ -771,7 +861,7 @@ fn color_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, nam
|
|||
.widget_holder(),
|
||||
),
|
||||
TaggedValue::GradientStops(ref x) => widgets.push(
|
||||
color_props
|
||||
color_button
|
||||
.value(FillChoice::Gradient(x.clone()))
|
||||
.on_update(update_value(
|
||||
|x: &ColorButton| TaggedValue::GradientStops(x.value.as_gradient().cloned().unwrap_or_default()),
|
||||
|
@ -790,7 +880,11 @@ fn color_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, nam
|
|||
fn curves_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup {
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
||||
|
||||
if let Some(TaggedValue::Curve(curve)) = &document_node.inputs[index].as_non_exposed_value() {
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return LayoutGroup::Row { widgets: vec![] };
|
||||
};
|
||||
if let Some(TaggedValue::Curve(curve)) = &input.as_non_exposed_value() {
|
||||
widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
CurveInput::new(curve.clone())
|
||||
|
@ -804,7 +898,11 @@ fn curves_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, na
|
|||
|
||||
fn centroid_widget(document_node: &DocumentNode, node_id: NodeId, index: usize) -> LayoutGroup {
|
||||
let mut widgets = start_widgets(document_node, node_id, index, "Centroid Type", FrontendGraphDataType::General, true);
|
||||
if let Some(&TaggedValue::CentroidType(centroid_type)) = &document_node.inputs[index].as_non_exposed_value() {
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return LayoutGroup::Row { widgets: vec![] };
|
||||
};
|
||||
if let Some(&TaggedValue::CentroidType(centroid_type)) = &input.as_non_exposed_value() {
|
||||
let entries = vec![
|
||||
RadioEntryData::new("area")
|
||||
.label("Area")
|
||||
|
@ -902,7 +1000,7 @@ pub fn color_properties(document_node: &DocumentNode, node_id: NodeId, _context:
|
|||
}
|
||||
|
||||
pub fn load_image_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
let url = text_widget(document_node, node_id, 1, "Url", true);
|
||||
let url = text_widget(document_node, node_id, 1, "URL", true);
|
||||
|
||||
vec![LayoutGroup::Row { widgets: url }]
|
||||
}
|
||||
|
@ -1137,40 +1235,63 @@ pub fn threshold_properties(document_node: &DocumentNode, node_id: NodeId, _cont
|
|||
}
|
||||
|
||||
pub fn gradient_map_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
let gradient_input = 1;
|
||||
let reverse_input = 2;
|
||||
let gradient_index = 1;
|
||||
let reverse_index = 2;
|
||||
|
||||
let gradient = if let Some(TaggedValue::GradientStops(gradient)) = &document_node.inputs[gradient_input].as_value() {
|
||||
gradient.clone()
|
||||
let gradient_row = color_widget(document_node, node_id, gradient_index, "Gradient", ColorButton::default().allow_none(false), true);
|
||||
let reverse_row = bool_widget(document_node, node_id, reverse_index, "Reverse", true);
|
||||
|
||||
vec![gradient_row, LayoutGroup::Row { widgets: reverse_row }]
|
||||
}
|
||||
|
||||
pub fn assign_colors_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
let fill_index = 1;
|
||||
let stroke_index = 2;
|
||||
let gradient_index = 3;
|
||||
let reverse_index = 4;
|
||||
let randomize_index = 5;
|
||||
let repeat_every_index = 6;
|
||||
|
||||
let fill_row = bool_widget(document_node, node_id, fill_index, "Fill", true);
|
||||
let stroke_row = bool_widget(document_node, node_id, stroke_index, "Stroke", true);
|
||||
let gradient_row = color_widget(document_node, node_id, gradient_index, "Gradient", ColorButton::default().allow_none(false), true);
|
||||
let reverse_row = bool_widget(document_node, node_id, reverse_index, "Reverse", true);
|
||||
let randomize_row = bool_widget(document_node, node_id, randomize_index, "Randomize", true);
|
||||
let randomize_enabled = if let Some(&TaggedValue::Bool(randomize_enabled)) = &document_node.inputs[randomize_index].as_value() {
|
||||
randomize_enabled
|
||||
} else {
|
||||
return vec![LayoutGroup::Row { widgets: vec![] }];
|
||||
false
|
||||
};
|
||||
let mut gradient_row = vec![TextLabel::new("Gradient").widget_holder()];
|
||||
add_blank_assist(&mut gradient_row);
|
||||
gradient_row.extend([
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
ColorButton::default()
|
||||
.allow_none(false)
|
||||
.value(FillChoice::Gradient(gradient))
|
||||
.on_update(move |x: &ColorButton| {
|
||||
NodeGraphMessage::SetInputValue {
|
||||
node_id,
|
||||
input_index: gradient_input,
|
||||
value: TaggedValue::GradientStops(x.value.as_gradient().unwrap().clone()),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
]);
|
||||
let repeat_every_row = number_widget(
|
||||
document_node,
|
||||
node_id,
|
||||
repeat_every_index,
|
||||
"Repeat Every",
|
||||
NumberInput::default().min(0.).int().disabled(randomize_enabled),
|
||||
true,
|
||||
);
|
||||
|
||||
let reverse_row = bool_widget(document_node, node_id, reverse_input, "Reverse", true);
|
||||
|
||||
vec![LayoutGroup::Row { widgets: gradient_row }, LayoutGroup::Row { widgets: reverse_row }]
|
||||
vec![
|
||||
LayoutGroup::Row { widgets: fill_row },
|
||||
LayoutGroup::Row { widgets: stroke_row },
|
||||
gradient_row,
|
||||
LayoutGroup::Row { widgets: reverse_row },
|
||||
LayoutGroup::Row { widgets: randomize_row },
|
||||
LayoutGroup::Row { widgets: repeat_every_row },
|
||||
]
|
||||
}
|
||||
|
||||
pub fn vibrance_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
let vibrance = number_widget(document_node, node_id, 1, "Vibrance", NumberInput::default().mode_range().min(-100.).max(100.).unit("%"), true);
|
||||
let vibrance_index = 1;
|
||||
|
||||
let vibrance = number_widget(
|
||||
document_node,
|
||||
node_id,
|
||||
vibrance_index,
|
||||
"Vibrance",
|
||||
NumberInput::default().mode_range().min(-100.).max(100.).unit("%"),
|
||||
true,
|
||||
);
|
||||
|
||||
vec![LayoutGroup::Row { widgets: vibrance }]
|
||||
}
|
||||
|
@ -1189,7 +1310,12 @@ pub fn channel_mixer_properties(document_node: &DocumentNode, node_id: NodeId, _
|
|||
let output_channel_index = 18;
|
||||
let mut output_channel = vec![TextLabel::new("Output Channel").widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder()];
|
||||
add_blank_assist(&mut output_channel);
|
||||
if let Some(&TaggedValue::RedGreenBlue(choice)) = &document_node.inputs[output_channel_index].as_non_exposed_value() {
|
||||
|
||||
let Some(input) = document_node.inputs.get(output_channel_index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
};
|
||||
if let Some(&TaggedValue::RedGreenBlue(choice)) = input.as_non_exposed_value() {
|
||||
let entries = vec![
|
||||
RadioEntryData::new(format!("{:?}", RedGreenBlue::Red))
|
||||
.label(RedGreenBlue::Red.to_string())
|
||||
|
@ -1206,6 +1332,7 @@ pub fn channel_mixer_properties(document_node: &DocumentNode, node_id: NodeId, _
|
|||
];
|
||||
output_channel.extend([RadioInput::new(entries).selected_index(Some(choice as u32)).widget_holder()]);
|
||||
};
|
||||
|
||||
let is_output_channel = if let Some(&TaggedValue::RedGreenBlue(choice)) = &document_node.inputs[output_channel_index].as_value() {
|
||||
choice
|
||||
} else {
|
||||
|
@ -1274,7 +1401,12 @@ pub fn selective_color_properties(document_node: &DocumentNode, node_id: NodeId,
|
|||
let colors_index = 38;
|
||||
let mut colors = vec![TextLabel::new("Colors").widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder()];
|
||||
add_blank_assist(&mut colors);
|
||||
if let Some(&TaggedValue::SelectiveColorChoice(choice)) = &document_node.inputs[colors_index].as_non_exposed_value() {
|
||||
|
||||
let Some(input) = document_node.inputs.get(colors_index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
};
|
||||
if let Some(&TaggedValue::SelectiveColorChoice(choice)) = input.as_non_exposed_value() {
|
||||
use SelectiveColorChoice::*;
|
||||
let entries = [[Reds, Yellows, Greens, Cyans, Blues, Magentas].as_slice(), [Whites, Neutrals, Blacks].as_slice()]
|
||||
.into_iter()
|
||||
|
@ -1291,7 +1423,8 @@ pub fn selective_color_properties(document_node: &DocumentNode, node_id: NodeId,
|
|||
})
|
||||
.collect();
|
||||
colors.extend([DropdownInput::new(entries).selected_index(Some(choice as u32)).widget_holder()]);
|
||||
};
|
||||
}
|
||||
|
||||
let colors_choice_index = if let Some(&TaggedValue::SelectiveColorChoice(choice)) = &document_node.inputs[colors_index].as_value() {
|
||||
choice
|
||||
} else {
|
||||
|
@ -1320,7 +1453,12 @@ pub fn selective_color_properties(document_node: &DocumentNode, node_id: NodeId,
|
|||
let mode_index = 1;
|
||||
let mut mode = start_widgets(document_node, node_id, mode_index, "Mode", FrontendGraphDataType::General, true);
|
||||
mode.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
if let Some(&TaggedValue::RelativeAbsolute(relative_or_absolute)) = &document_node.inputs[mode_index].as_non_exposed_value() {
|
||||
|
||||
let Some(input) = document_node.inputs.get(mode_index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
};
|
||||
if let Some(&TaggedValue::RelativeAbsolute(relative_or_absolute)) = &input.as_non_exposed_value() {
|
||||
let entries = vec![
|
||||
RadioEntryData::new("relative")
|
||||
.label("Relative")
|
||||
|
@ -1490,14 +1628,22 @@ pub fn rectangle_properties(document_node: &DocumentNode, node_id: NodeId, _cont
|
|||
corner_radius_row_2.push(TextLabel::new("").widget_holder());
|
||||
add_blank_assist(&mut corner_radius_row_2);
|
||||
|
||||
if let Some(&TaggedValue::Bool(is_individual)) = &document_node.inputs[corner_rounding_type_index].as_non_exposed_value() {
|
||||
let Some(input) = document_node.inputs.get(corner_rounding_type_index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
};
|
||||
if let Some(&TaggedValue::Bool(is_individual)) = &input.as_non_exposed_value() {
|
||||
// Values
|
||||
let uniform_val = match document_node.inputs[corner_radius_index].as_non_exposed_value() {
|
||||
let Some(input) = document_node.inputs.get(corner_radius_index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
};
|
||||
let uniform_val = match input.as_non_exposed_value() {
|
||||
Some(TaggedValue::F64(x)) => *x,
|
||||
Some(TaggedValue::F64Array4(x)) => x[0],
|
||||
_ => 0.,
|
||||
};
|
||||
let individual_val = match document_node.inputs[corner_radius_index].as_non_exposed_value() {
|
||||
let individual_val = match input.as_non_exposed_value() {
|
||||
Some(&TaggedValue::F64Array4(x)) => x,
|
||||
Some(&TaggedValue::F64(x)) => [x; 4],
|
||||
_ => [0.; 4],
|
||||
|
@ -1624,7 +1770,11 @@ pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _cont
|
|||
|
||||
let mut widgets = start_widgets(document_node, node_id, index, "Rotation", FrontendGraphDataType::Number, true);
|
||||
|
||||
if let Some(&TaggedValue::F64(val)) = document_node.inputs[index].as_non_exposed_value() {
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
};
|
||||
if let Some(&TaggedValue::F64(val)) = input.as_non_exposed_value() {
|
||||
widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
NumberInput::new(Some(val.to_degrees()))
|
||||
|
@ -1839,7 +1989,11 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
|
|||
let seed = {
|
||||
let mut widgets = start_widgets(document_node, node_id, seed_index, "Seed", FrontendGraphDataType::Number, false);
|
||||
|
||||
if let Some(&TaggedValue::F64(seed)) = &document_node.inputs[seed_index].as_non_exposed_value() {
|
||||
let Some(input) = document_node.inputs.get(seed_index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
};
|
||||
if let Some(&TaggedValue::F64(seed)) = &input.as_non_exposed_value() {
|
||||
widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
IconButton::new("Regenerate", 24)
|
||||
|
@ -1906,7 +2060,11 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
|
|||
DVec2::new(x as f64, y as f64)
|
||||
};
|
||||
|
||||
if let Some(&TaggedValue::OptionalDVec2(vec2)) = &document_node.inputs[resolution_index].as_non_exposed_value() {
|
||||
let Some(input) = document_node.inputs.get(resolution_index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
};
|
||||
if let Some(&TaggedValue::OptionalDVec2(vec2)) = &input.as_non_exposed_value() {
|
||||
let dimensions_is_auto = vec2.is_none();
|
||||
let vec2 = vec2.unwrap_or_else(|| round((image_size.0 as f64, image_size.1 as f64).into()));
|
||||
|
||||
|
@ -1985,7 +2143,11 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
|
|||
let sampling_method = {
|
||||
let mut widgets = start_widgets(document_node, node_id, sampling_method_index, "Sampling Method", FrontendGraphDataType::General, true);
|
||||
|
||||
if let Some(&TaggedValue::ImaginateSamplingMethod(sampling_method)) = &document_node.inputs[sampling_method_index].as_non_exposed_value() {
|
||||
let Some(input) = document_node.inputs.get(sampling_method_index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
};
|
||||
if let Some(&TaggedValue::ImaginateSamplingMethod(sampling_method)) = &input.as_non_exposed_value() {
|
||||
let sampling_methods = ImaginateSamplingMethod::list();
|
||||
let mut entries = Vec::with_capacity(sampling_methods.len());
|
||||
for method in sampling_methods {
|
||||
|
|
|
@ -55,6 +55,22 @@ impl core::hash::Hash for GraphicGroup {
|
|||
}
|
||||
}
|
||||
|
||||
impl GraphicGroup {
|
||||
pub const EMPTY: Self = Self {
|
||||
elements: Vec::new(),
|
||||
transform: DAffine2::IDENTITY,
|
||||
alpha_blending: AlphaBlending::new(),
|
||||
};
|
||||
|
||||
pub fn new(elements: Vec<GraphicElement>) -> Self {
|
||||
Self {
|
||||
elements,
|
||||
transform: DAffine2::IDENTITY,
|
||||
alpha_blending: AlphaBlending::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The possible forms of graphical content held in a Vec by the `elements` field of [`GraphicElement`].
|
||||
/// Can be another recursively nested [`GraphicGroup`], a [`VectorData`] shape, an [`ImageFrame`], or an [`Artboard`].
|
||||
#[derive(Clone, Debug, Hash, PartialEq, DynAny)]
|
||||
|
@ -74,6 +90,50 @@ impl Default for GraphicElement {
|
|||
}
|
||||
}
|
||||
|
||||
impl GraphicElement {
|
||||
pub fn as_group(&self) -> Option<&GraphicGroup> {
|
||||
match self {
|
||||
GraphicElement::GraphicGroup(group) => Some(group),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_group_mut(&mut self) -> Option<&mut GraphicGroup> {
|
||||
match self {
|
||||
GraphicElement::GraphicGroup(group) => Some(group),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_vector_data(&self) -> Option<&VectorData> {
|
||||
match self {
|
||||
GraphicElement::VectorData(data) => Some(data),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_vector_data_mut(&mut self) -> Option<&mut VectorData> {
|
||||
match self {
|
||||
GraphicElement::VectorData(data) => Some(data),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_raster(&self) -> Option<&Raster> {
|
||||
match self {
|
||||
GraphicElement::Raster(raster) => Some(raster),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_raster_mut(&mut self) -> Option<&mut Raster> {
|
||||
match self {
|
||||
GraphicElement::Raster(raster) => Some(raster),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, DynAny)]
|
||||
pub enum Raster {
|
||||
/// A bitmap image with a finite position and extent, equivalent to the SVG <image> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image
|
||||
|
@ -301,11 +361,3 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GraphicGroup {
|
||||
pub const EMPTY: Self = Self {
|
||||
elements: Vec::new(),
|
||||
transform: DAffine2::IDENTITY,
|
||||
alpha_blending: AlphaBlending::new(),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -60,6 +60,10 @@ impl GradientStops {
|
|||
|
||||
Color::BLACK
|
||||
}
|
||||
|
||||
pub fn reversed(&self) -> Self {
|
||||
Self(self.0.iter().rev().map(|(position, color)| (1. - position, *color)).collect())
|
||||
}
|
||||
}
|
||||
|
||||
/// A gradient fill.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use super::misc::CentroidType;
|
||||
use super::style::{Fill, Stroke};
|
||||
use super::style::{Fill, GradientStops, Stroke};
|
||||
use super::{PointId, SegmentId, StrokeId, VectorData};
|
||||
use crate::renderer::GraphicElementRendered;
|
||||
use crate::transform::{Footprint, Transform, TransformMut};
|
||||
|
@ -9,6 +9,78 @@ use bezier_rs::{Cap, Join, Subpath, SubpathTValue, TValue};
|
|||
use glam::{DAffine2, DVec2};
|
||||
use rand::{Rng, SeedableRng};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct AssignColorsNode<Fill, Stroke, Gradient, Reverse, Randomize, RepeatEvery> {
|
||||
fill: Fill,
|
||||
stroke: Stroke,
|
||||
gradient: Gradient,
|
||||
reverse: Reverse,
|
||||
randomize: Randomize,
|
||||
repeat_every: RepeatEvery,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(AssignColorsNode)]
|
||||
fn assign_colors_node(group: GraphicGroup, fill: bool, stroke: bool, gradient: GradientStops, reverse: bool, randomize: bool, repeat_every: u32) -> GraphicGroup {
|
||||
let mut group = group;
|
||||
let vector_data_list: Vec<_> = group.iter_mut().filter_map(|element| element.as_vector_data_mut()).collect();
|
||||
let list = (vector_data_list.len(), vector_data_list.into_iter());
|
||||
|
||||
assign_colors(list, fill, stroke, gradient, reverse, randomize, repeat_every);
|
||||
|
||||
group
|
||||
}
|
||||
|
||||
#[node_macro::node_impl(AssignColorsNode)]
|
||||
fn assign_colors_node(vector_data: VectorData, fill: bool, stroke: bool, gradient: GradientStops, reverse: bool, randomize: bool, repeat_every: u32) -> GraphicGroup {
|
||||
let mut vector_data_list: Vec<_> = vector_data
|
||||
.region_bezier_paths()
|
||||
.map(|(_, subpath)| {
|
||||
let mut vector = VectorData::from_subpath(subpath);
|
||||
|
||||
vector.style = vector_data.style.clone();
|
||||
|
||||
crate::GraphicElement::VectorData(Box::new(vector))
|
||||
})
|
||||
.collect();
|
||||
let list = (vector_data_list.len(), vector_data_list.iter_mut().map(|element| element.as_vector_data_mut().unwrap()));
|
||||
|
||||
assign_colors(list, fill, stroke, gradient, reverse, randomize, repeat_every);
|
||||
|
||||
let mut group = GraphicGroup::new(vector_data_list);
|
||||
group.transform = vector_data.transform;
|
||||
group.alpha_blending = vector_data.alpha_blending;
|
||||
|
||||
group
|
||||
}
|
||||
|
||||
fn assign_colors<'a>((length, vector_data): (usize, impl Iterator<Item = &'a mut VectorData>), fill: bool, stroke: bool, gradient: GradientStops, reverse: bool, randomize: bool, repeat_every: u32) {
|
||||
let gradient = if reverse { gradient.reversed() } else { gradient };
|
||||
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(0);
|
||||
|
||||
for (i, vector_data) in vector_data.enumerate() {
|
||||
let factor = match randomize {
|
||||
true => rng.gen::<f64>(),
|
||||
false => match repeat_every {
|
||||
0 => i as f64 / (length - 1) as f64,
|
||||
1 => 0.,
|
||||
_ => i as f64 % repeat_every as f64 / (repeat_every - 1) as f64,
|
||||
},
|
||||
};
|
||||
|
||||
let color = gradient.evalute(factor);
|
||||
|
||||
if fill {
|
||||
vector_data.style.set_fill(Fill::Solid(color));
|
||||
}
|
||||
if stroke {
|
||||
if let Some(stroke) = vector_data.style.stroke().and_then(|stroke| stroke.with_color(&Some(color))) {
|
||||
vector_data.style.set_stroke(stroke);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SetFillNode<Fill> {
|
||||
fill: Fill,
|
||||
|
|
|
@ -679,6 +679,8 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
|||
register_node!(graphene_core::vector::SetFillNode<_>, input: VectorData, params: [Color]),
|
||||
register_node!(graphene_core::vector::SetFillNode<_>, input: VectorData, params: [Option<Color>]),
|
||||
register_node!(graphene_core::vector::SetFillNode<_>, input: VectorData, params: [graphene_std::vector::style::Gradient]),
|
||||
register_node!(graphene_core::vector::AssignColorsNode<_, _, _, _, _, _>, input: GraphicGroup, params: [bool, bool, graphene_std::vector::style::GradientStops, bool, bool, u32]),
|
||||
register_node!(graphene_core::vector::AssignColorsNode<_, _, _, _, _, _>, input: VectorData, params: [bool, bool, graphene_std::vector::style::GradientStops, bool, bool, u32]),
|
||||
register_node!(graphene_core::vector::SetStrokeNode<_, _, _, _, _, _, _>, input: VectorData, params: [Option<graphene_core::Color>, f64, Vec<f64>, f64, graphene_core::vector::style::LineCap, graphene_core::vector::style::LineJoin, f64]),
|
||||
register_node!(graphene_core::vector::RepeatNode<_, _, _>, input: VectorData, params: [DVec2, f64, u32]),
|
||||
register_node!(graphene_core::vector::BoundingBoxNode, input: VectorData, params: []),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue