Add corner rounding to the Rectangle node (#1648)

* add skeleton implementation

* add corner rounding

* fix crash when `border_radius` is zero

* rename `Border Radius` to `Corner Radius`

* add clamped property

* add `TaggedValue::F64Array4`

* add frontend support for individual corner rounding

* added individual corner rounding

* fix rebase

* change default values when switching rounding type

* fix crash caused by negative scale

* remove `Any` trait

* add `Message::Batched`

* fix stale property bug

* add smarter clamping for individual rounding

* Rearrange widgets in properties panel

* update individual clamping algorithm

* add better variable names

* make variable names clearer

* Final code cleanup

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Karthik Prakash
2024-04-06 10:39:55 +05:30
committed by GitHub
parent d09e7eaf86
commit 438c45eb80
8 changed files with 221 additions and 17 deletions

View File

@@ -32,18 +32,47 @@ fn ellipse_generator(_input: (), radius_x: f64, radius_y: f64) -> VectorData {
}
#[derive(Debug, Clone, Copy)]
pub struct RectangleGenerator<SizeX, SizeY> {
pub struct RectangleGenerator<SizeX, SizeY, IsIndividual, CornerRadius, Clamped> {
size_x: SizeX,
size_y: SizeY,
is_individual: IsIndividual,
corner_radius: CornerRadius,
clamped: Clamped,
}
trait CornerRadius {
fn generate(self, size: DVec2, clamped: bool) -> super::VectorData;
}
impl CornerRadius for f64 {
fn generate(self, size: DVec2, clamped: bool) -> super::VectorData {
let clamped_radius = if clamped { self.clamp(0., size.x.min(size.y).max(0.) / 2.) } else { self };
super::VectorData::from_subpaths(vec![Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4])])
}
}
impl CornerRadius for [f64; 4] {
fn generate(self, size: DVec2, clamped: bool) -> super::VectorData {
let clamped_radius = if clamped {
// Algorithm follows the CSS spec: <https://drafts.csswg.org/css-backgrounds/#corner-overlap>
let mut scale_factor: f64 = 1.;
for i in 0..4 {
let side_length = if i % 2 == 0 { size.x } else { size.y };
let adjacent_corner_radius_sum = self[i] + self[(i + 1) % 4];
if side_length < adjacent_corner_radius_sum {
scale_factor = scale_factor.min(side_length / adjacent_corner_radius_sum);
}
}
self.map(|x| x * scale_factor)
} else {
self
};
super::VectorData::from_subpaths(vec![Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius)])
}
}
#[node_macro::node_fn(RectangleGenerator)]
fn square_generator(_input: (), size_x: f64, size_y: f64) -> VectorData {
let size = DVec2::new(size_x, size_y);
let corner1 = -size / 2.;
let corner2 = size / 2.;
super::VectorData::from_subpath(Subpath::new_rect(corner1, corner2))
fn square_generator<T: CornerRadius>(_input: (), size_x: f64, size_y: f64, is_individual: bool, corner_radius: T, clamped: bool) -> VectorData {
corner_radius.generate(DVec2::new(size_x, size_y), clamped)
}
#[derive(Debug, Clone, Copy)]

View File

@@ -42,6 +42,7 @@ pub enum TaggedValue {
VectorData(graphene_core::vector::VectorData),
Fill(graphene_core::vector::style::Fill),
Stroke(graphene_core::vector::style::Stroke),
F64Array4([f64; 4]),
VecF64(Vec<f64>),
VecDVec2(Vec<DVec2>),
RedGreenBlue(graphene_core::raster::RedGreenBlue),
@@ -109,6 +110,7 @@ impl Hash for TaggedValue {
Self::VectorData(x) => x.hash(state),
Self::Fill(x) => x.hash(state),
Self::Stroke(x) => x.hash(state),
Self::F64Array4(x) => x.iter().for_each(|x| x.to_bits().hash(state)),
Self::VecF64(x) => x.iter().for_each(|val| val.to_bits().hash(state)),
Self::VecDVec2(x) => x.iter().for_each(|val| val.to_array().iter().for_each(|x| x.to_bits().hash(state))),
Self::RedGreenBlue(x) => x.hash(state),
@@ -183,6 +185,7 @@ impl<'a> TaggedValue {
TaggedValue::VectorData(x) => Box::new(x),
TaggedValue::Fill(x) => Box::new(x),
TaggedValue::Stroke(x) => Box::new(x),
TaggedValue::F64Array4(x) => Box::new(x),
TaggedValue::VecF64(x) => Box::new(x),
TaggedValue::VecDVec2(x) => Box::new(x),
TaggedValue::RedGreenBlue(x) => Box::new(x),
@@ -259,6 +262,7 @@ impl<'a> TaggedValue {
TaggedValue::VectorData(_) => concrete!(graphene_core::vector::VectorData),
TaggedValue::Fill(_) => concrete!(graphene_core::vector::style::Fill),
TaggedValue::Stroke(_) => concrete!(graphene_core::vector::style::Stroke),
TaggedValue::F64Array4(_) => concrete!([f64; 4]),
TaggedValue::VecF64(_) => concrete!(Vec<f64>),
TaggedValue::VecDVec2(_) => concrete!(Vec<DVec2>),
TaggedValue::RedGreenBlue(_) => concrete!(graphene_core::raster::RedGreenBlue),

View File

@@ -757,7 +757,8 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::vector::MorphNode<_, _, _, _>, input: Footprint, output: VectorData, fn_params: [Footprint => VectorData, Footprint => VectorData, () => u32, () => f64]),
register_node!(graphene_core::vector::generator_nodes::CircleGenerator<_>, input: (), params: [f64]),
register_node!(graphene_core::vector::generator_nodes::EllipseGenerator<_, _>, input: (), params: [f64, f64]),
register_node!(graphene_core::vector::generator_nodes::RectangleGenerator<_, _>, input: (), params: [f64, f64]),
register_node!(graphene_core::vector::generator_nodes::RectangleGenerator<_, _, _, _, _>, input: (), params: [f64, f64, bool, f64, bool]),
register_node!(graphene_core::vector::generator_nodes::RectangleGenerator<_, _, _, _, _>, input: (), params: [f64, f64, bool, [f64; 4], bool]),
register_node!(graphene_core::vector::generator_nodes::RegularPolygonGenerator<_, _>, input: (), params: [u32, f64]),
register_node!(graphene_core::vector::generator_nodes::StarGenerator<_, _, _>, input: (), params: [u32, f64, f64]),
register_node!(graphene_core::vector::generator_nodes::LineGenerator<_, _>, input: (), params: [DVec2, DVec2]),