Implement the Brush tool (#1099)

* Implement Brush Node

* Add color Input

* Add VectorPointsNode

* Add Erase Node

* Adapt compilation infrastructure to allow non Image Frame inputs

* Remove debug output from TransformNode

* Fix transform calculation

* Fix Blending by making the brush texture use associated alpha

* Code improvements and UX polish

* Rename Opacity to Flow

* Add erase option to brush node + fix freehand tool

* Fix crash

* Revert erase implementation

* Fix flattening id calculation

* Fix some transformation issues

* Fix changing the pivot location

* Fix vector data modify bounds

* Minor fn name cleanup

* Fix some tests

* Fix tests

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
Co-authored-by: hypercube <0hypercube@gmail.com>
This commit is contained in:
Dennis Kobert
2023-04-11 10:35:21 +02:00
committed by Keavon Chambers
co-authored by Keavon Chambers hypercube
parent 758f757775
commit 589ff9a2d3
36 changed files with 1527 additions and 406 deletions
+32 -24
View File
@@ -403,6 +403,11 @@ impl TypingContext {
self.constructor.get(&node_id).copied()
}
/// Returns the type of a given node id if it exists
pub fn type_of(&self, node_id: NodeId) -> Option<&NodeIOTypes> {
self.inferred.get(&node_id)
}
/// Returns the inferred types for a given node id.
pub fn infer(&mut self, node_id: NodeId, node: &ProtoNode) -> Result<NodeIOTypes, String> {
let identifier = node.identifier.name.clone();
@@ -416,7 +421,8 @@ impl TypingContext {
// If the node has a value parameter we can infer the return type from it
ConstructionArgs::Value(ref v) => {
assert!(matches!(node.input, ProtoNodeInput::None));
let types = NodeIOTypes::new(concrete!(()), v.ty(), vec![]);
// TODO: This should return a reference to the value
let types = NodeIOTypes::new(concrete!(()), v.ty(), vec![v.ty()]);
self.inferred.insert(node_id, types.clone());
return Ok(types);
}
@@ -427,9 +433,9 @@ impl TypingContext {
self.inferred
.get(id)
.ok_or(format!("Inferring type of {node_id} depends on {id} which is not present in the typing context"))
.map(|node| (node.input.clone(), node.output.clone()))
.map(|node| node.ty())
})
.collect::<Result<Vec<(Type, Type)>, String>>()?,
.collect::<Result<Vec<Type>, String>>()?,
};
// Get the node input type from the proto node declaration
@@ -450,26 +456,27 @@ impl TypingContext {
if matches!(input, Type::Generic(_)) {
return Err(format!("Generic types are not supported as inputs yet {:?} occured in {:?}", &input, node.identifier));
}
if parameters.iter().any(|p| matches!(p.1, Type::Generic(_))) {
if parameters.iter().any(|p| match p {
Type::Fn(_, b) if matches!(b.as_ref(), Type::Generic(_)) => true,
_ => false,
}) {
return Err(format!("Generic types are not supported in parameters: {:?} occured in {:?}", parameters, node.identifier));
}
let covariant = |output, input| match (&output, &input) {
(Type::Concrete(t1), Type::Concrete(t2)) => t1 == t2,
(Type::Concrete(_), Type::Generic(_)) => true,
// TODO: relax this requirement when allowing generic types as inputs
(Type::Generic(_), _) => false,
};
fn covariant(from: &Type, to: &Type) -> bool {
match (from, to) {
(Type::Concrete(t1), Type::Concrete(t2)) => t1 == t2,
(Type::Fn(a1, b1), Type::Fn(a2, b2)) => covariant(a1, a2) && covariant(b1, b2),
// TODO: relax this requirement when allowing generic types as inputs
(Type::Generic(_), _) => false,
(_, Type::Generic(_)) => true,
_ => false,
}
}
// List of all implementations that match the input and parameter types
let valid_output_types = impls
.keys()
.filter(|node_io| {
covariant(input.clone(), node_io.input.clone())
&& parameters
.iter()
.zip(node_io.parameters.iter())
.all(|(p1, p2)| covariant(p1.0.clone(), p2.0.clone()) && covariant(p1.1.clone(), p2.1.clone()))
})
.filter(|node_io| covariant(&input, &node_io.input) && parameters.iter().zip(node_io.parameters.iter()).all(|(p1, p2)| covariant(p1, p2) && covariant(p1, p2)))
.collect::<Vec<_>>();
// Attempt to substitute generic types with concrete types and save the list of results
@@ -517,7 +524,7 @@ impl TypingContext {
/// Returns a list of all generic types used in the node
fn collect_generics(types: &NodeIOTypes) -> Vec<Cow<'static, str>> {
let inputs = [&types.input].into_iter().chain(types.parameters.iter().map(|(_, x)| x));
let inputs = [&types.input].into_iter().chain(types.parameters.iter().flat_map(|x| x.fn_output()));
let mut generics = inputs
.filter_map(|t| match t {
Type::Generic(out) => Some(out.clone()),
@@ -532,15 +539,16 @@ fn collect_generics(types: &NodeIOTypes) -> Vec<Cow<'static, str>> {
}
/// Checks if a generic type can be substituted with a concrete type and returns the concrete type
fn check_generic(types: &NodeIOTypes, input: &Type, parameters: &[(Type, Type)], generic: &str) -> Result<Type, String> {
let inputs = [(&types.input, input)]
fn check_generic(types: &NodeIOTypes, input: &Type, parameters: &[Type], generic: &str) -> Result<Type, String> {
let inputs = [(Some(&types.input), Some(input))]
.into_iter()
.chain(types.parameters.iter().map(|(_, x)| x).zip(parameters.iter().map(|(_, x)| x)));
let mut concrete_inputs = inputs.filter(|(ni, _)| matches!(ni, Type::Generic(input) if generic == input));
let (_, out_ty) = concrete_inputs
.chain(types.parameters.iter().map(|x| x.fn_output()).zip(parameters.iter().map(|x| x.fn_output())));
let concrete_inputs = inputs.filter(|(ni, _)| matches!(ni, Some(Type::Generic(input)) if generic == input));
let mut outputs = concrete_inputs.flat_map(|(_, out)| out);
let out_ty = outputs
.next()
.ok_or_else(|| format!("Generic output type {generic} is not dependent on input {input:?} or parameters {parameters:?}",))?;
if concrete_inputs.any(|(_, ty)| ty != out_ty) {
if outputs.any(|ty| ty != out_ty) {
return Err(format!("Generic output type {generic} is dependent on multiple inputs or parameters",));
}
Ok(out_ty.clone())