New node: Math (#2121)

* 2115 IP

* Initial implementation of Expression node

* Register Expression Node

* Add Expression DocumentNode Definition

* DocumentNodeImplementation::Expresssion in guess_type_from_node

* Move expression.rs to graphene-core

* WIP: Investigating 'exposed' & 'value_source' params for Expression property

* Node graph render debug IP

* Single input can change node properties; complex debug IP

* Fix epsilon in test

* Handle invalid expressions in expression_node by returning 0.0

* Run cargo fmt

* Set the default expression to "1 + 1"

* Hardcode the A and B inputs at Keavon's request

* Rename and clean up UX

* Move into ops.rs

---------

Co-authored-by: hypercube <0hypercube@gmail.com>
Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Jacin Yan
2024-12-17 07:08:14 +00:00
committed by GitHub
co-authored by hypercube Keavon Chambers
parent 79b4f4df7b
commit 3423c8ec13
6 changed files with 152 additions and 8 deletions
@@ -2530,6 +2530,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
"graphene_core::raster::adjustments::SelectiveColorNode",
&node_properties::selective_color_properties as PropertiesLayout,
),
("graphene_core::ops::MathNode", &node_properties::math_properties as PropertiesLayout),
("graphene_core::raster::ExposureNode", &node_properties::exposure_properties as PropertiesLayout),
("graphene_core::vector::generator_nodes::RectangleNode", &node_properties::rectangle_properties as PropertiesLayout),
("graphene_core::vector::AssignColorsNode", &node_properties::assign_colors_properties as PropertiesLayout),
@@ -2616,3 +2616,52 @@ pub(crate) fn artboard_properties(document_node: &DocumentNode, node_id: NodeId,
vec![location, dimensions, background, clip_row]
}
pub fn math_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let expression_index = 1;
let operation_b_index = 2;
let expression = (|| {
let mut widgets = start_widgets(document_node, node_id, expression_index, "Expression", FrontendGraphDataType::General, true);
let Some(input) = document_node.inputs.get(expression_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())
.centered(true)
.on_update(update_value(
|x: &TextInput| {
TaggedValue::String({
let mut expression = x.value.trim().to_string();
if ["+", "-", "*", "/", "^", "%"].iter().any(|&infix| infix == expression) {
expression = format!("A {} B", expression);
} else if expression == "^" {
expression = String::from("A^B");
}
expression
})
},
node_id,
expression_index,
))
.on_commit(commit_value)
.widget_holder(),
])
}
widgets
})();
let operand_b = number_widget(document_node, node_id, operation_b_index, "Operand B", NumberInput::default(), true);
let operand_a_hint = vec![TextLabel::new("(Operand A is the primary input)").widget_holder()];
vec![
LayoutGroup::Row { widgets: expression }.with_tooltip(r#"A math expression that may incorporate "A" and/or "B", such as "sqrt(A + B) - B^2""#),
LayoutGroup::Row { widgets: operand_b }.with_tooltip(r#"The value of "B" when calculating the expression"#),
LayoutGroup::Row { widgets: operand_a_hint }.with_tooltip(r#""A" is fed by the value from the previous node in the primary data flow, or it is 0 if disconnected"#),
]
}