mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-20 03:18:06 +08:00
Add multi-output nodes with struct returns destructured by #[node_macro::destructure]
A #[node_macro::node] function returning a struct tagged with
field is a named output connector (title-cased from the field name,
renamed with #[name("...")], described by its doc comment). By default
the node has a hidden primary output carrying the whole struct with the
fields as secondary outputs; marking at most one field #[primary] makes
that field the primary output instead.
The macro generates one hidden extractor proto node per field plus a
registration keyed by the struct's TypeId. The Graphene preprocessor
recognizes nodes returning a registered struct and substitutes them, in
the transient runtime copy of the network only, with a generated network
exporting each field through its extractor. The destructuring machinery
therefore never appears when drilling into a node, in copied clipboard
content, or in saved documents. When a Memoize implementation is
registered for the struct type, the struct is computed once and shared
across all outputs rather than re-evaluated per output.
The editor derives output counts, names, and types for such nodes from
the registry. The old hand-authored "Split Vec2" and "Split Channels"
wrapper-network definitions are replaced by multi-output split_vec2 and
split_channels proto nodes, with document migrations that keep existing
wires valid since the output indices are unchanged.
The "Position on Path" and "Tangent on Path" nodes are combined into a
single multi-output "Evaluate Path" node whose primary output is the
position and whose secondary output is the tangent angle. A migration
converts old instances, forwarding the shared inputs and remapping the
tangent nodes' downstream connections to the new tangent output index.
The now-redundant "Extract XY" node is removed (its role is subsumed by
Split Vec2's destructuring), and "Extract Channel" becomes a plain helper
used by Split Channels rather than a standalone node.
This commit is contained in:
@@ -238,7 +238,6 @@ mod test {
|
||||
use core_types::transform::Footprint;
|
||||
use glam::DVec2;
|
||||
use graphene_core::ReadPositionNode;
|
||||
use graphene_core::extract_xy::{ExtractXyNode, XY};
|
||||
use graphic_types::Vector;
|
||||
use kurbo::Shape;
|
||||
use kurbo::{BezPath, DEFAULT_ACCURACY, Rect};
|
||||
@@ -278,15 +277,27 @@ mod test {
|
||||
}
|
||||
}
|
||||
|
||||
/// Test helper that extracts the Y component of an upstream node's `Item<DVec2>` output.
|
||||
#[derive(Clone)]
|
||||
struct ExtractYNode<Position>(Position);
|
||||
|
||||
impl<'i, I: Ctx, Position> Node<'i, I> for ExtractYNode<Position>
|
||||
where
|
||||
Position: Node<'i, I, Output = Pin<Box<dyn Future<Output = Item<DVec2>> + 'i + Send>>>,
|
||||
{
|
||||
type Output = Pin<Box<dyn Future<Output = Item<f64>> + 'i + Send>>;
|
||||
fn eval(&'i self, input: I) -> Self::Output {
|
||||
let position = self.0.eval(input);
|
||||
Box::pin(async move { Item::new_from_element(position.await.element().y) })
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn repeat_on_points_test() {
|
||||
let context = OwnedContextImpl::default().into_context();
|
||||
let rect = RectangleNode::new(
|
||||
FutureWrapperNode(()),
|
||||
ExtractXyNode::new(
|
||||
ReadPositionNode::new(FutureWrapperNode(()), FutureWrapperNode(Item::new_from_element(0_u32))),
|
||||
FutureWrapperNode(Item::new_from_element(XY::Y)),
|
||||
),
|
||||
ExtractYNode(ReadPositionNode::new(FutureWrapperNode(()), FutureWrapperNode(Item::new_from_element(0_u32)))),
|
||||
FutureWrapperNode(Item::new_from_element(2_f64)),
|
||||
FutureWrapperNode(Item::new_from_element(BoxCorners::default())),
|
||||
FutureWrapperNode(Item::new_from_element(false)),
|
||||
|
||||
Reference in New Issue
Block a user