New node: Morph (#1576)

* Add morph node

* Range slider time parameter, better lerping

* Lerp more fill and stroke parameters

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube
2024-01-13 23:35:40 -08:00
committed by GitHub
co-authored by Keavon Chambers
parent 768bbe820d
commit 484acbcde3
10 changed files with 240 additions and 29 deletions
@@ -2580,7 +2580,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentInputType::value("Start", TaggedValue::DVec2(DVec2::new(0., 0.5)), false),
DocumentInputType::value("End", TaggedValue::DVec2(DVec2::new(1., 0.5)), false),
DocumentInputType::value("Transform", TaggedValue::DAffine2(DAffine2::IDENTITY), false),
DocumentInputType::value("Positions", TaggedValue::GradientPositions(vec![(0., Some(Color::BLACK)), (1., Some(Color::WHITE))]), false),
DocumentInputType::value("Positions", TaggedValue::GradientPositions(vec![(0., Color::BLACK), (1., Color::WHITE)]), false),
],
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
properties: node_properties::fill_properties,
@@ -2715,6 +2715,21 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
properties: node_properties::node_no_properties,
..Default::default()
},
DocumentNodeDefinition {
name: "Morph",
category: "Vector",
implementation: NodeImplementation::proto("graphene_core::vector::MorphNode<_, _, _, _>"),
inputs: vec![
DocumentInputType::value("Source", TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true),
DocumentInputType::value("Target", TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true),
DocumentInputType::value("Start Index", TaggedValue::U32(0), false),
DocumentInputType::value("Time", TaggedValue::F64(0.5), false),
],
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
manual_composition: Some(concrete!(Footprint)),
properties: node_properties::morph_properties,
..Default::default()
},
// TODO: This needs to work with resolution-aware (raster with footprint, post-Cull node) data.
DocumentNodeDefinition {
name: "Image Segmentation",
@@ -623,18 +623,18 @@ fn gradient_type_widget(document_node: &DocumentNode, node_id: NodeId, index: us
LayoutGroup::Row { widgets }
}
fn gradient_row(row: &mut Vec<WidgetHolder>, positions: &Vec<(f64, Option<Color>)>, index: usize, node_id: NodeId, input_index: usize) {
fn gradient_row(row: &mut Vec<WidgetHolder>, positions: &Vec<(f64, Color)>, index: usize, node_id: NodeId, input_index: usize) {
let label = TextLabel::new(format!("Gradient: {:.0}%", positions[index].0 * 100.)).tooltip("Adjustable by dragging the gradient stops in the viewport with the Gradient tool active");
row.push(label.widget_holder());
let on_update = {
let positions = positions.clone();
move |color_button: &ColorButton| {
let mut new_positions = positions.clone();
new_positions[index].1 = color_button.value;
new_positions[index].1 = color_button.value.unwrap();
TaggedValue::GradientPositions(new_positions)
}
};
let color = ColorButton::new(positions[index].1).on_update(update_value(on_update, node_id, input_index));
let color = ColorButton::new(Some(positions[index].1)).on_update(update_value(on_update, node_id, input_index)).allow_none(false);
add_blank_assist(row);
row.push(Separator::new(SeparatorType::Unrelated).widget_holder());
row.push(color.widget_holder());
@@ -668,10 +668,9 @@ fn gradient_row(row: &mut Vec<WidgetHolder>, positions: &Vec<(f64, Option<Color>
let mut new_positions = positions.clone();
// Blend linearly between the two colors.
let get_color = |index: usize| match (new_positions[index].1, new_positions.get(index + 1).and_then(|x| x.1)) {
(Some(a), Some(b)) => Color::from_rgbaf32((a.r() + b.r()) / 2., (a.g() + b.g()) / 2., (a.b() + b.b()) / 2., ((a.a() + b.a()) / 2.).clamp(0., 1.)),
(Some(v), _) | (_, Some(v)) => Some(v),
_ => Some(Color::WHITE),
let get_color = |index: usize| match (new_positions[index].1, new_positions.get(index + 1).map(|x| x.1)) {
(a, Some(b)) => Color::from_rgbaf32_unchecked((a.r() + b.r()) / 2., (a.g() + b.g()) / 2., (a.b() + b.b()) / 2., ((a.a() + b.a()) / 2.).clamp(0., 1.)),
(_, None) => Color::WHITE,
};
let get_pos = |index: usize| (new_positions[index].0 + new_positions.get(index + 1).map(|v| v.0).unwrap_or(1.)) / 2.;
@@ -2056,6 +2055,16 @@ pub fn sample_points_properties(document_node: &DocumentNode, node_id: NodeId, _
]
}
pub fn morph_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let start_index = number_widget(document_node, node_id, 2, "Start Index", NumberInput::default().min(0.), true);
let time = number_widget(document_node, node_id, 3, "Time", NumberInput::default().min(0.).max(1.).mode_range(), true);
vec![
LayoutGroup::Row { widgets: start_index }.with_tooltip("The index of point on the target that morphs to the first point of the source"),
LayoutGroup::Row { widgets: time }.with_tooltip("Linear time of transition - 0. is source, 1. is target"),
]
}
/// Fill Node Widgets LayoutGroup
pub fn fill_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let fill_type_index = 1;
@@ -5,7 +5,6 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
use crate::messages::tool::common_functionality::graph_modification_utils::get_gradient;
use crate::messages::tool::common_functionality::snapping::SnapManager;
use graphene_core::raster::color::Color;
use graphene_core::vector::style::{Fill, Gradient, GradientType};
#[derive(Default)]
@@ -335,7 +334,7 @@ impl Fsm for GradientToolFsmState {
if selected_gradient.gradient.positions.len() == 1 {
responses.add(GraphOperationMessage::FillSet {
layer: selected_gradient.layer,
fill: Fill::Solid(selected_gradient.gradient.positions[0].1.unwrap_or(Color::BLACK)),
fill: Fill::Solid(selected_gradient.gradient.positions[0].1),
});
return self;
}
@@ -344,7 +343,7 @@ impl Fsm for GradientToolFsmState {
let min_position = selected_gradient.gradient.positions.iter().map(|(pos, _)| *pos).reduce(f64::min).expect("No min");
let max_position = selected_gradient.gradient.positions.iter().map(|(pos, _)| *pos).reduce(f64::max).expect("No max");
// Recompute the start and end posiiton of the gradient (in viewport transform)
// Recompute the start and end position of the gradient (in viewport transform)
let transform = selected_gradient.transform;
let (start, end) = (transform.transform_point2(selected_gradient.gradient.start), transform.transform_point2(selected_gradient.gradient.end));
let (new_start, new_end) = (start.lerp(end, min_position), start.lerp(end, max_position));