mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-20 11:28:30 +08:00
Add basic vector nodes (#1059)
* Add some derives * Allow node_fn with mutable inputs * Add basic vector nodes * Revert elipse tool changes * Fix the elipse tool again * Change spacer width * Bubble serde feature in graph craft --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
committed by
Keavon Chambers
parent
7e37fb41a4
commit
a2046a51b1
@@ -12,7 +12,7 @@ use graphene_core::raster::color::Color;
|
||||
use glam::DAffine2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, specta::Type)]
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ArtboardMessageHandler {
|
||||
pub artboards_document: DocumentLegacy,
|
||||
pub artboard_ids: Vec<LayerId>,
|
||||
|
||||
@@ -46,7 +46,7 @@ impl FrontendGraphDataType {
|
||||
TaggedValue::Image(_) => Self::Raster,
|
||||
TaggedValue::ImageFrame(_) => Self::Raster,
|
||||
TaggedValue::Color(_) => Self::Color,
|
||||
TaggedValue::RcSubpath(_) | TaggedValue::Subpath(_) => Self::Subpath,
|
||||
TaggedValue::RcSubpath(_) | TaggedValue::Subpath(_) | TaggedValue::VectorData(_) => Self::Subpath,
|
||||
_ => Self::General,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ impl DocumentInputType {
|
||||
Self { name, data_type, default }
|
||||
}
|
||||
|
||||
pub const fn _none() -> Self {
|
||||
pub const fn none() -> Self {
|
||||
Self {
|
||||
name: "None",
|
||||
data_type: FrontendGraphDataType::General,
|
||||
@@ -589,18 +589,10 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
||||
properties: node_properties::add_properties,
|
||||
},
|
||||
(*IMAGINATE_NODE).clone(),
|
||||
/*DocumentNodeType {
|
||||
DocumentNodeType {
|
||||
name: "Unit Circle Generator",
|
||||
category: "Vector",
|
||||
identifier: NodeImplementation::proto("graphene_std::vector::generator_nodes::UnitCircleGenerator", &[]),
|
||||
inputs: vec![DocumentInputType::none()],
|
||||
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
|
||||
properties: node_properties::no_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Unit Square Generator",
|
||||
category: "Vector",
|
||||
identifier: NodeImplementation::proto("graphene_std::vector::generator_nodes::UnitSquareGenerator", &[]),
|
||||
identifier: NodeImplementation::proto("graphene_core::vector::generator_nodes::UnitCircleGenerator"),
|
||||
inputs: vec![DocumentInputType::none()],
|
||||
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
|
||||
properties: node_properties::no_properties,
|
||||
@@ -608,40 +600,63 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
||||
DocumentNodeType {
|
||||
name: "Path Generator",
|
||||
category: "Vector",
|
||||
identifier: NodeImplementation::proto("graphene_core::ops::IdNode"),
|
||||
identifier: NodeImplementation::proto("graphene_core::vector::generator_nodes::PathGenerator"),
|
||||
inputs: vec![DocumentInputType {
|
||||
name: "Path Data",
|
||||
data_type: FrontendGraphDataType::Subpath,
|
||||
default: NodeInput::value(TaggedValue::Subpath(Subpath::new()), false),
|
||||
default: NodeInput::value(TaggedValue::Subpath(bezier_rs::Subpath::new(Vec::new(), false)), false),
|
||||
}],
|
||||
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
|
||||
properties: node_properties::no_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Transform Subpath",
|
||||
name: "Transform",
|
||||
category: "Vector",
|
||||
identifier: NodeImplementation::proto("graphene_std::vector::generator_nodes::TransformSubpathNode", &[]),
|
||||
identifier: NodeImplementation::proto("graphene_core::vector::TransformNode<_, _, _, _>"),
|
||||
inputs: vec![
|
||||
DocumentInputType::new("Subpath", TaggedValue::Subpath(Subpath::empty()), true),
|
||||
DocumentInputType::new("Translation", TaggedValue::DVec2(DVec2::ZERO), false),
|
||||
DocumentInputType::new("Rotation", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::new("Scale", TaggedValue::DVec2(DVec2::ONE), false),
|
||||
DocumentInputType::new("Skew", TaggedValue::DVec2(DVec2::ZERO), false),
|
||||
DocumentInputType::value("Vector Data", TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true),
|
||||
DocumentInputType::value("Translation", TaggedValue::DVec2(DVec2::ZERO), false),
|
||||
DocumentInputType::value("Rotation", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("Scale", TaggedValue::DVec2(DVec2::ONE), false),
|
||||
DocumentInputType::value("Skew", TaggedValue::DVec2(DVec2::ZERO), false),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
|
||||
properties: node_properties::transform_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Blit Subpath",
|
||||
name: "Fill",
|
||||
category: "Vector",
|
||||
identifier: NodeImplementation::proto("graphene_std::vector::generator_nodes::BlitSubpath", &[]),
|
||||
identifier: NodeImplementation::proto("graphene_core::vector::SetFillNode<_, _, _, _, _, _, _>"),
|
||||
inputs: vec![
|
||||
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
|
||||
DocumentInputType::new("Subpath", TaggedValue::Subpath(Subpath::empty()), true),
|
||||
DocumentInputType::value("Vector Data", TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true),
|
||||
DocumentInputType::value("Fill Type", TaggedValue::FillType(vector::style::FillType::Solid), false),
|
||||
DocumentInputType::value("Solid Color", TaggedValue::Color(Color::BLACK), false),
|
||||
DocumentInputType::value("Gradient Type", TaggedValue::GradientType(vector::style::GradientType::Linear), false),
|
||||
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),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::no_properties,
|
||||
},*/
|
||||
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
|
||||
properties: node_properties::fill_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Stroke",
|
||||
category: "Vector",
|
||||
identifier: NodeImplementation::proto("graphene_core::vector::SetStrokeNode<_, _, _, _, _, _, _>"),
|
||||
inputs: vec![
|
||||
DocumentInputType::value("Vector Data", TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true),
|
||||
DocumentInputType::value("Color", TaggedValue::Color(Color::BLACK), false),
|
||||
DocumentInputType::value("Weight", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("Dash Lengths", TaggedValue::VecF32(Vec::new()), false),
|
||||
DocumentInputType::value("Dash Offset", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("Line Cap", TaggedValue::LineCap(graphene_core::vector::style::LineCap::Butt), false),
|
||||
DocumentInputType::value("Line Join", TaggedValue::LineJoin(graphene_core::vector::style::LineJoin::Miter), false),
|
||||
DocumentInputType::value("Miter Limit", TaggedValue::F64(4.), false),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
|
||||
properties: node_properties::stroke_properties,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -718,14 +733,25 @@ impl DocumentNodeType {
|
||||
DocumentNodeImplementation::Network(inner_network)
|
||||
}
|
||||
|
||||
/// Converts the [DocumentNodeType] type to a [DocumentNode], based on the inputs from the graph (which must be the correct length) and the metadata
|
||||
pub fn to_document_node(&self, inputs: impl IntoIterator<Item = NodeInput>, metadata: graph_craft::document::DocumentNodeMetadata) -> DocumentNode {
|
||||
let inputs: Vec<_> = inputs.into_iter().collect();
|
||||
assert_eq!(inputs.len(), self.inputs.len(), "Inputs passed from the graph must be equal to the number required");
|
||||
DocumentNode {
|
||||
name: self.name.to_string(),
|
||||
inputs: inputs.into_iter().collect(),
|
||||
inputs,
|
||||
implementation: self.generate_implementation(),
|
||||
metadata,
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the [DocumentNodeType] type to a [DocumentNode], using the provided `input_override` and falling back to the default inputs.
|
||||
/// `input_override` does not have to be the correct length.
|
||||
pub fn to_document_node_default_inputs(&self, input_override: impl IntoIterator<Item = Option<NodeInput>>, metadata: graph_craft::document::DocumentNodeMetadata) -> DocumentNode {
|
||||
let mut input_override = input_override.into_iter();
|
||||
let inputs = self.inputs.iter().map(|default| input_override.next().unwrap_or_default().unwrap_or_else(|| default.default.clone()));
|
||||
self.to_document_node(inputs, metadata)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wrap_network_in_scope(network: NodeNetwork) -> NodeNetwork {
|
||||
@@ -764,7 +790,7 @@ pub fn new_image_network(output_offset: i32, output_node_id: NodeId) -> NodeNetw
|
||||
nodes: [
|
||||
resolve_document_node_type("Input")
|
||||
.expect("Input node does not exist")
|
||||
.to_document_node([NodeInput::Network(concrete!(ImageFrame))], DocumentNodeMetadata::position((8, 4))),
|
||||
.to_document_node_default_inputs([], DocumentNodeMetadata::position((8, 4))),
|
||||
resolve_document_node_type("Output")
|
||||
.expect("Output node does not exist")
|
||||
.to_document_node([NodeInput::node(output_node_id, 0)], DocumentNodeMetadata::position((output_offset + 8, 4))),
|
||||
@@ -776,3 +802,37 @@ pub fn new_image_network(output_offset: i32, output_node_id: NodeId) -> NodeNetw
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_vector_network(subpath: bezier_rs::Subpath<uuid::ManipulatorGroupId>) -> NodeNetwork {
|
||||
let input = resolve_document_node_type("Input").expect("Input node does not exist");
|
||||
let path_generator = resolve_document_node_type("Path Generator").expect("Path Generator node does not exist");
|
||||
let transform = resolve_document_node_type("Transform").expect("Transform node does not exist");
|
||||
let fill = resolve_document_node_type("Fill").expect("Fill node does not exist");
|
||||
let stroke = resolve_document_node_type("Stroke").expect("Stroke node does not exist");
|
||||
let output = resolve_document_node_type("Output").expect("Output node does not exist");
|
||||
|
||||
let mut pos = 0;
|
||||
let mut next_pos = || {
|
||||
let node_pos = DocumentNodeMetadata::position((pos, 4));
|
||||
pos += 8;
|
||||
node_pos
|
||||
};
|
||||
|
||||
NodeNetwork {
|
||||
inputs: vec![0],
|
||||
outputs: vec![NodeOutput::new(5, 0)],
|
||||
nodes: [
|
||||
input.to_document_node_default_inputs([], next_pos()),
|
||||
path_generator.to_document_node_default_inputs([Some(NodeInput::value(TaggedValue::Subpath(subpath), false))], next_pos()),
|
||||
transform.to_document_node_default_inputs([Some(NodeInput::node(1, 0))], next_pos()),
|
||||
fill.to_document_node_default_inputs([Some(NodeInput::node(2, 0))], next_pos()),
|
||||
stroke.to_document_node_default_inputs([Some(NodeInput::node(3, 0))], next_pos()),
|
||||
output.to_document_node_default_inputs([Some(NodeInput::node(4, 0))], next_pos()),
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(id, node)| (id as NodeId, node))
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
|
||||
use graph_craft::imaginate_input::*;
|
||||
use graphene_core::raster::{BlendMode, Color, LuminanceCalculation};
|
||||
use graphene_core::vector::style::{FillType, GradientType, LineCap, LineJoin};
|
||||
|
||||
use super::document_node_types::NodePropertiesContext;
|
||||
use super::{FrontendGraphDataType, IMAGINATE_NODE};
|
||||
@@ -18,17 +19,20 @@ pub fn string_properties(text: impl Into<String>) -> Vec<LayoutGroup> {
|
||||
vec![LayoutGroup::Row { widgets: vec![widget] }]
|
||||
}
|
||||
|
||||
fn update_value<T>(value: impl Fn(&T) -> TaggedValue + 'static + Send + Sync, node_id: NodeId, input_index: usize) -> impl Fn(&T) -> Message + 'static + Send + Sync {
|
||||
fn optionally_update_value<T>(value: impl Fn(&T) -> Option<TaggedValue> + 'static + Send + Sync, node_id: NodeId, input_index: usize) -> impl Fn(&T) -> Message + 'static + Send + Sync {
|
||||
move |input_value: &T| {
|
||||
NodeGraphMessage::SetInputValue {
|
||||
node_id,
|
||||
input_index,
|
||||
value: value(input_value),
|
||||
if let Some(value) = value(input_value) {
|
||||
NodeGraphMessage::SetInputValue { node_id, input_index, value }.into()
|
||||
} else {
|
||||
Message::NoOp
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
fn update_value<T>(value: impl Fn(&T) -> TaggedValue + 'static + Send + Sync, node_id: NodeId, input_index: usize) -> impl Fn(&T) -> Message + 'static + Send + Sync {
|
||||
optionally_update_value(move |v| Some(value(v)), node_id, input_index)
|
||||
}
|
||||
|
||||
fn expose_widget(node_id: NodeId, index: usize, data_type: FrontendGraphDataType, exposed: bool) -> WidgetHolder {
|
||||
ParameterExposeButton::new()
|
||||
.exposed(exposed)
|
||||
@@ -45,6 +49,15 @@ fn expose_widget(node_id: NodeId, index: usize, data_type: FrontendGraphDataType
|
||||
.widget_holder()
|
||||
}
|
||||
|
||||
fn add_blank_assist(widgets: &mut Vec<WidgetHolder>) {
|
||||
widgets.extend_from_slice(&[
|
||||
WidgetHolder::unrelated_separator(), // TODO: These three separators add up to 24px,
|
||||
WidgetHolder::unrelated_separator(), // TODO: which is the width of the Assist area.
|
||||
WidgetHolder::unrelated_separator(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists.
|
||||
WidgetHolder::unrelated_separator(), // TODO: This last one is the separator after the 24px assist.
|
||||
]);
|
||||
}
|
||||
|
||||
fn start_widgets(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, data_type: FrontendGraphDataType, blank_assist: bool) -> Vec<WidgetHolder> {
|
||||
let input = document_node.inputs.get(index).unwrap();
|
||||
let mut widgets = vec![
|
||||
@@ -53,12 +66,7 @@ fn start_widgets(document_node: &DocumentNode, node_id: NodeId, index: usize, na
|
||||
WidgetHolder::text_widget(name),
|
||||
];
|
||||
if blank_assist {
|
||||
widgets.extend_from_slice(&[
|
||||
WidgetHolder::unrelated_separator(), // TODO: These three separators add up to 24px,
|
||||
WidgetHolder::unrelated_separator(), // TODO: which is the width of the Assist area.
|
||||
WidgetHolder::unrelated_separator(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists.
|
||||
WidgetHolder::unrelated_separator(), // TODO: This last one is the separator after the 24px assist.
|
||||
]);
|
||||
add_blank_assist(&mut widgets);
|
||||
}
|
||||
widgets
|
||||
}
|
||||
@@ -117,6 +125,35 @@ fn bool_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name
|
||||
widgets
|
||||
}
|
||||
|
||||
fn vec_f32_input(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, text_props: TextInput, blank_assist: bool) -> Vec<WidgetHolder> {
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Color, blank_assist);
|
||||
|
||||
let from_string = |string: &str| {
|
||||
string
|
||||
.split(&[',', ' '])
|
||||
.filter(|x| !x.is_empty())
|
||||
.map(str::parse::<f32>)
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.ok()
|
||||
.map(TaggedValue::VecF32)
|
||||
};
|
||||
|
||||
if let NodeInput::Value {
|
||||
tagged_value: TaggedValue::VecF32(x),
|
||||
exposed: false,
|
||||
} = &document_node.inputs[index]
|
||||
{
|
||||
widgets.extend_from_slice(&[
|
||||
WidgetHolder::unrelated_separator(),
|
||||
text_props
|
||||
.value(x.iter().map(|v| v.to_string()).collect::<Vec<_>>().join(", "))
|
||||
.on_update(optionally_update_value(move |x: &TextInput| from_string(&x.value), node_id, index))
|
||||
.widget_holder(),
|
||||
])
|
||||
}
|
||||
widgets
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -191,6 +228,172 @@ fn luminance_calculation(document_node: &DocumentNode, node_id: u64, index: usiz
|
||||
LayoutGroup::Row { widgets }.with_tooltip("Formula used to calculate the luminance of a pixel")
|
||||
}
|
||||
|
||||
fn line_cap_widget(document_node: &DocumentNode, node_id: u64, 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 &NodeInput::Value {
|
||||
tagged_value: TaggedValue::LineCap(line_cap),
|
||||
exposed: false,
|
||||
} = &document_node.inputs[index]
|
||||
{
|
||||
let entries = [("Butt", LineCap::Butt), ("Round", LineCap::Round), ("Square", LineCap::Square)]
|
||||
.into_iter()
|
||||
.map(|(name, val)| RadioEntryData::new(name).on_update(update_value(move |_| TaggedValue::LineCap(val), node_id, index)))
|
||||
.collect();
|
||||
|
||||
widgets.extend_from_slice(&[WidgetHolder::unrelated_separator(), RadioInput::new(entries).selected_index(line_cap as u32).widget_holder()]);
|
||||
}
|
||||
LayoutGroup::Row { widgets }
|
||||
}
|
||||
|
||||
fn line_join_widget(document_node: &DocumentNode, node_id: u64, 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 &NodeInput::Value {
|
||||
tagged_value: TaggedValue::LineJoin(line_join),
|
||||
exposed: false,
|
||||
} = &document_node.inputs[index]
|
||||
{
|
||||
let entries = [("Miter", LineJoin::Miter), ("Bevel", LineJoin::Bevel), ("Round", LineJoin::Round)]
|
||||
.into_iter()
|
||||
.map(|(name, val)| RadioEntryData::new(name).on_update(update_value(move |_| TaggedValue::LineJoin(val), node_id, index)))
|
||||
.collect();
|
||||
|
||||
widgets.extend_from_slice(&[WidgetHolder::unrelated_separator(), RadioInput::new(entries).selected_index(line_join as u32).widget_holder()]);
|
||||
}
|
||||
LayoutGroup::Row { widgets }
|
||||
}
|
||||
|
||||
fn gradient_type_widget(document_node: &DocumentNode, node_id: u64, 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 &NodeInput::Value {
|
||||
tagged_value: TaggedValue::GradientType(gradient_type),
|
||||
exposed: false,
|
||||
} = &document_node.inputs[index]
|
||||
{
|
||||
let entries = [("Linear", GradientType::Linear), ("Radial", GradientType::Radial)]
|
||||
.into_iter()
|
||||
.map(|(name, val)| RadioEntryData::new(name).on_update(update_value(move |_| TaggedValue::GradientType(val), node_id, index)))
|
||||
.collect();
|
||||
|
||||
widgets.extend_from_slice(&[WidgetHolder::unrelated_separator(), RadioInput::new(entries).selected_index(gradient_type as u32).widget_holder()]);
|
||||
}
|
||||
LayoutGroup::Row { widgets }
|
||||
}
|
||||
|
||||
fn gradient_row(row: &mut Vec<WidgetHolder>, positions: &Vec<(f64, Option<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_input: &ColorInput| {
|
||||
let mut new_positions = positions.clone();
|
||||
new_positions[index].1 = color_input.value;
|
||||
TaggedValue::GradientPositions(new_positions)
|
||||
}
|
||||
};
|
||||
let color = ColorInput::new(positions[index].1).on_update(update_value(on_update, node_id, input_index));
|
||||
add_blank_assist(row);
|
||||
row.push(WidgetHolder::unrelated_separator());
|
||||
row.push(color.widget_holder());
|
||||
|
||||
let mut skip_separator = false;
|
||||
// Remove button
|
||||
if positions.len() != index + 1 && index != 0 {
|
||||
let on_update = {
|
||||
let in_positions = positions.clone();
|
||||
move |_: &IconButton| {
|
||||
let mut new_positions = in_positions.clone();
|
||||
new_positions.remove(index);
|
||||
TaggedValue::GradientPositions(new_positions)
|
||||
}
|
||||
};
|
||||
|
||||
skip_separator = true;
|
||||
row.push(WidgetHolder::related_separator());
|
||||
row.push(
|
||||
IconButton::new("Remove", 16)
|
||||
.tooltip("Remove this gradient stop")
|
||||
.on_update(update_value(on_update, node_id, input_index))
|
||||
.widget_holder(),
|
||||
);
|
||||
}
|
||||
// Add button
|
||||
if positions.len() != index + 1 {
|
||||
let on_update = {
|
||||
let positions = positions.clone();
|
||||
move |_: &IconButton| {
|
||||
let mut new_positions = positions.clone();
|
||||
|
||||
// Blend linearly between the two colours.
|
||||
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_pos = |index: usize| (new_positions[index].0 + new_positions.get(index + 1).map(|v| v.0).unwrap_or(1.)) / 2.;
|
||||
|
||||
new_positions.push((get_pos(index), get_color(index)));
|
||||
|
||||
new_positions.sort_unstable_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
|
||||
|
||||
TaggedValue::GradientPositions(new_positions)
|
||||
}
|
||||
};
|
||||
|
||||
if !skip_separator {
|
||||
row.push(WidgetHolder::related_separator());
|
||||
}
|
||||
row.push(
|
||||
IconButton::new("Add", 16)
|
||||
.tooltip("Add a gradient stop after this")
|
||||
.on_update(update_value(on_update, node_id, input_index))
|
||||
.widget_holder(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn gradient_positions(rows: &mut Vec<LayoutGroup>, document_node: &DocumentNode, name: &str, node_id: u64, input_index: usize) {
|
||||
let mut widgets = vec![expose_widget(node_id, input_index, FrontendGraphDataType::General, document_node.inputs[input_index].is_exposed())];
|
||||
widgets.push(WidgetHolder::unrelated_separator());
|
||||
if let NodeInput::Value {
|
||||
tagged_value: TaggedValue::GradientPositions(gradient_positions),
|
||||
exposed: false,
|
||||
} = &document_node.inputs[input_index]
|
||||
{
|
||||
for index in 0..gradient_positions.len() {
|
||||
gradient_row(&mut widgets, gradient_positions, index, node_id, input_index);
|
||||
|
||||
let widgets = std::mem::take(&mut widgets);
|
||||
rows.push(LayoutGroup::Row { widgets });
|
||||
}
|
||||
let on_update = {
|
||||
let gradient_positions = gradient_positions.clone();
|
||||
move |_: &TextButton| {
|
||||
let mut new_positions = gradient_positions.clone();
|
||||
new_positions = new_positions.iter().map(|(distance, color)| (1. - distance, *color)).collect();
|
||||
new_positions.reverse();
|
||||
TaggedValue::GradientPositions(new_positions)
|
||||
}
|
||||
};
|
||||
let invert = TextButton::new("Invert")
|
||||
.icon(Some("Swap".into()))
|
||||
.tooltip("Reverse the order of each color stop")
|
||||
.on_update(update_value(on_update, node_id, input_index))
|
||||
.widget_holder();
|
||||
|
||||
if widgets.is_empty() {
|
||||
widgets.push(TextLabel::new("").widget_holder());
|
||||
add_blank_assist(&mut widgets);
|
||||
}
|
||||
widgets.push(WidgetHolder::unrelated_separator());
|
||||
widgets.push(invert);
|
||||
|
||||
rows.push(LayoutGroup::Row { widgets });
|
||||
} else {
|
||||
widgets.push(TextLabel::new(name).widget_holder());
|
||||
rows.push(LayoutGroup::Row { widgets })
|
||||
}
|
||||
}
|
||||
|
||||
fn color_widget(document_node: &DocumentNode, node_id: u64, index: usize, name: &str, color_props: ColorInput, blank_assist: bool) -> LayoutGroup {
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Number, blank_assist);
|
||||
|
||||
@@ -365,11 +568,11 @@ pub fn add_properties(document_node: &DocumentNode, node_id: NodeId, _context: &
|
||||
vec![operand("Input", 0), operand("Addend", 1)]
|
||||
}
|
||||
|
||||
pub fn _transform_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext, blank_assist: bool) -> Vec<LayoutGroup> {
|
||||
pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
let translation = {
|
||||
let index = 1;
|
||||
|
||||
let mut widgets = start_widgets(document_node, node_id, index, "Translation", FrontendGraphDataType::Vector, blank_assist);
|
||||
let mut widgets = start_widgets(document_node, node_id, index, "Translation", FrontendGraphDataType::Vector, true);
|
||||
|
||||
if let NodeInput::Value {
|
||||
tagged_value: TaggedValue::DVec2(vec2),
|
||||
@@ -383,7 +586,7 @@ pub fn _transform_properties(document_node: &DocumentNode, node_id: NodeId, _con
|
||||
.unit(" px")
|
||||
.on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(input.value.unwrap(), vec2.y)), node_id, index))
|
||||
.widget_holder(),
|
||||
WidgetHolder::unrelated_separator(),
|
||||
WidgetHolder::related_separator(),
|
||||
NumberInput::new(Some(vec2.y))
|
||||
.label("Y")
|
||||
.unit(" px")
|
||||
@@ -398,7 +601,7 @@ pub fn _transform_properties(document_node: &DocumentNode, node_id: NodeId, _con
|
||||
let rotation = {
|
||||
let index = 2;
|
||||
|
||||
let mut widgets = start_widgets(document_node, node_id, index, "Rotation", FrontendGraphDataType::Number, blank_assist);
|
||||
let mut widgets = start_widgets(document_node, node_id, index, "Rotation", FrontendGraphDataType::Number, true);
|
||||
|
||||
if let NodeInput::Value {
|
||||
tagged_value: TaggedValue::F64(val),
|
||||
@@ -423,7 +626,7 @@ pub fn _transform_properties(document_node: &DocumentNode, node_id: NodeId, _con
|
||||
let scale = {
|
||||
let index = 3;
|
||||
|
||||
let mut widgets = start_widgets(document_node, node_id, index, "Scale", FrontendGraphDataType::Vector, blank_assist);
|
||||
let mut widgets = start_widgets(document_node, node_id, index, "Scale", FrontendGraphDataType::Vector, true);
|
||||
|
||||
if let NodeInput::Value {
|
||||
tagged_value: TaggedValue::DVec2(vec2),
|
||||
@@ -436,7 +639,7 @@ pub fn _transform_properties(document_node: &DocumentNode, node_id: NodeId, _con
|
||||
.label("X")
|
||||
.on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(input.value.unwrap(), vec2.y)), node_id, index))
|
||||
.widget_holder(),
|
||||
WidgetHolder::unrelated_separator(),
|
||||
WidgetHolder::related_separator(),
|
||||
NumberInput::new(Some(vec2.y))
|
||||
.label("Y")
|
||||
.on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(vec2.x, input.value.unwrap())), node_id, index))
|
||||
@@ -996,3 +1199,86 @@ pub fn generate_node_properties(document_node: &DocumentNode, node_id: NodeId, c
|
||||
};
|
||||
LayoutGroup::Section { name, layout }
|
||||
}
|
||||
|
||||
pub fn stroke_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
let color_index = 1;
|
||||
let weight_index = 2;
|
||||
let dash_lengths_index = 3;
|
||||
let dash_offset_index = 4;
|
||||
let line_cap_index = 5;
|
||||
let line_join_index = 6;
|
||||
let miter_limit_index = 7;
|
||||
|
||||
let color = color_widget(document_node, node_id, color_index, "Color", ColorInput::default(), true);
|
||||
let weight = number_widget(document_node, node_id, weight_index, "Weight", NumberInput::default().unit("px").min(0.), true);
|
||||
let dash_lengths = vec_f32_input(document_node, node_id, dash_lengths_index, "Dash Lengths", TextInput::default().centered(true), true);
|
||||
let dash_offset = number_widget(document_node, node_id, dash_offset_index, "Dash Offset", NumberInput::default().unit("px").min(0.), true);
|
||||
let line_cap = line_cap_widget(document_node, node_id, line_cap_index, "Line Cap", true);
|
||||
let line_join = line_join_widget(document_node, node_id, line_join_index, "Line Join", true);
|
||||
let miter_limit = number_widget(document_node, node_id, miter_limit_index, "Miter Limit", NumberInput::default().min(0.), true);
|
||||
|
||||
vec![
|
||||
color,
|
||||
LayoutGroup::Row { widgets: weight },
|
||||
LayoutGroup::Row { widgets: dash_lengths },
|
||||
LayoutGroup::Row { widgets: dash_offset },
|
||||
line_cap,
|
||||
line_join,
|
||||
LayoutGroup::Row { widgets: miter_limit },
|
||||
]
|
||||
}
|
||||
|
||||
pub fn fill_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
let fill_type_index = 1;
|
||||
let solid_color_index = 2;
|
||||
let gradient_type_index = 3;
|
||||
let positions_index = 7;
|
||||
|
||||
let fill_type = if let &NodeInput::Value {
|
||||
tagged_value: TaggedValue::FillType(fill_type),
|
||||
..
|
||||
} = &document_node.inputs[fill_type_index]
|
||||
{
|
||||
Some(fill_type)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut widgets = Vec::new();
|
||||
let gradient = fill_type == Some(graphene_core::vector::style::FillType::Gradient);
|
||||
let solid = fill_type == Some(graphene_core::vector::style::FillType::Solid);
|
||||
if fill_type.is_none() || solid {
|
||||
let solid_color = color_widget(document_node, node_id, solid_color_index, "Color", ColorInput::default(), true);
|
||||
widgets.push(solid_color);
|
||||
}
|
||||
|
||||
if fill_type.is_none() || gradient {
|
||||
let gradient_type = gradient_type_widget(document_node, node_id, gradient_type_index, "Gradient Type", true);
|
||||
widgets.push(gradient_type);
|
||||
gradient_positions(&mut widgets, document_node, "Gradient Positions", node_id, positions_index);
|
||||
}
|
||||
|
||||
if gradient || solid {
|
||||
let new_fill_type = if gradient { FillType::Solid } else { FillType::Gradient };
|
||||
let switch_button = TextButton::new(if gradient { "Use Solid Color" } else { "Use Gradient" })
|
||||
.tooltip(if gradient {
|
||||
"Change this fill from a gradient to a solid color, keeping the 0% stop color"
|
||||
} else {
|
||||
"Change this fill from a solid color to a gradient"
|
||||
})
|
||||
.on_update(update_value(move |_| TaggedValue::FillType(new_fill_type), node_id, fill_type_index));
|
||||
|
||||
widgets.push(LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut widgets = Vec::new();
|
||||
widgets.push(TextLabel::new("").widget_holder());
|
||||
add_blank_assist(&mut widgets);
|
||||
widgets.push(WidgetHolder::unrelated_separator());
|
||||
widgets.push(switch_button.widget_holder());
|
||||
widgets
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
widgets
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user