mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Implement dynamic table attributes to generalize the graphic-specific Table type (#4050)
* Feature-gate serde derives behind cfg_attr in all runtime node graph type crates * Refactor Table to move its hard-coded fields into an attributes field * Encapsulate TableRow/TableRowRef/TableRowMut attribute fields behind accessor methods * Remove TaggedValue::GraphicUnused * Refactor Table<T> to use dynamic attributes instead fixed names * Fix code review soundness concerns * Add todo work * Replace row-oriented Table<T> API with column-oriented access * Fix attribute propagation bugs ---------
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
use core_types::table::{Table, TableRow, TableRowRef};
|
||||
use core_types::{Color, Ctx};
|
||||
use core_types::table::{Table, TableRow};
|
||||
use core_types::uuid::NodeId;
|
||||
use core_types::{AlphaBlending, Color, Ctx};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphic_types::vector_types::subpath::{ManipulatorGroup, Subpath};
|
||||
use graphic_types::vector_types::vector::PointId;
|
||||
@@ -34,19 +35,22 @@ async fn boolean_operation<I: graphic_types::IntoGraphicTable + 'n + Send + Clon
|
||||
let content = content.into_graphic_table();
|
||||
|
||||
// The first index is the bottom of the stack
|
||||
let mut result_vector_table = boolean_operation_on_vector_table(flatten_vector(&content).iter(), operation);
|
||||
let flattened = flatten_vector(&content);
|
||||
let mut result_vector_table = boolean_operation_on_vector_table(&flattened, operation);
|
||||
|
||||
// Replace the transformation matrix with a mutation of the vector points themselves
|
||||
if let Some(result_vector) = result_vector_table.iter_mut().next() {
|
||||
let transform = *result_vector.transform;
|
||||
*result_vector.transform = DAffine2::IDENTITY;
|
||||
if result_vector_table.element_mut(0).is_some() {
|
||||
let transform: DAffine2 = result_vector_table.attribute_cloned_or_default("transform", 0);
|
||||
result_vector_table.set_attribute("transform", 0, DAffine2::IDENTITY);
|
||||
|
||||
Vector::transform(result_vector.element, transform);
|
||||
result_vector.element.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||
result_vector.element.upstream_data = Some(content.clone());
|
||||
let result_vector = result_vector_table.element_mut(0).unwrap();
|
||||
Vector::transform(result_vector, transform);
|
||||
result_vector.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||
result_vector.upstream_data = Some(content.clone());
|
||||
|
||||
// Clean up the boolean operation result by merging duplicated points
|
||||
result_vector.element.merge_by_distance_spatial(*result_vector.transform, 0.0001);
|
||||
let merge_transform: DAffine2 = result_vector_table.attribute_cloned_or_default("transform", 0);
|
||||
result_vector_table.element_mut(0).unwrap().merge_by_distance_spatial(merge_transform, 0.0001);
|
||||
}
|
||||
|
||||
result_vector_table
|
||||
@@ -106,26 +110,34 @@ impl WindingNumber {
|
||||
}
|
||||
}
|
||||
|
||||
fn boolean_operation_on_vector_table<'a>(vector: impl DoubleEndedIterator<Item = TableRowRef<'a, Vector>> + Clone, boolean_operation: BooleanOperation) -> Table<Vector> {
|
||||
fn boolean_operation_on_vector_table(vector: &Table<Vector>, boolean_operation: BooleanOperation) -> Table<Vector> {
|
||||
const EPSILON: f64 = 1e-5;
|
||||
let mut table = Table::new();
|
||||
let mut paths = Vec::new();
|
||||
let mut row = TableRow::<Vector>::default();
|
||||
|
||||
let copy_from = if matches!(boolean_operation, BooleanOperation::SubtractFront) {
|
||||
vector.clone().next()
|
||||
let copy_from_index = if matches!(boolean_operation, BooleanOperation::SubtractFront) {
|
||||
if !vector.is_empty() { Some(0) } else { None }
|
||||
} else {
|
||||
vector.clone().next_back()
|
||||
if !vector.is_empty() { Some(vector.len() - 1) } else { None }
|
||||
};
|
||||
let mut row = if let Some(index) = copy_from_index {
|
||||
let mut attributes = vector.clone_row_attributes(index);
|
||||
// The boolean op bakes input transforms into the output geometry, so the result row carries no transform of its own
|
||||
attributes.insert("transform", DAffine2::IDENTITY);
|
||||
let copy_from = vector.element(index).unwrap();
|
||||
let element = Vector {
|
||||
style: copy_from.style.clone(),
|
||||
upstream_data: copy_from.upstream_data.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
TableRow::from_parts(element, attributes)
|
||||
} else {
|
||||
TableRow::<Vector>::default()
|
||||
};
|
||||
if let Some(copy_from) = copy_from {
|
||||
row.alpha_blending = *copy_from.alpha_blending;
|
||||
row.source_node_id = *copy_from.source_node_id;
|
||||
row.element.style = copy_from.element.style.clone();
|
||||
row.element.upstream_data = copy_from.element.upstream_data.clone();
|
||||
}
|
||||
|
||||
for element in vector {
|
||||
paths.push(to_bez_path(element.element, *element.transform));
|
||||
for index in 0..vector.len() {
|
||||
let element = vector.element(index).unwrap();
|
||||
paths.push(to_bez_path(element, vector.attribute_cloned_or_default("transform", index)));
|
||||
}
|
||||
|
||||
let top = match Topology::<WindingNumber>::from_paths(paths.iter().enumerate().map(|(idx, path)| (path, (idx, paths.len()))), EPSILON) {
|
||||
@@ -139,7 +151,7 @@ fn boolean_operation_on_vector_table<'a>(vector: impl DoubleEndedIterator<Item =
|
||||
let contours = top.contours(|winding| winding.is_inside(boolean_operation));
|
||||
|
||||
for subpath in from_bez_paths(contours.contours().map(|c| &c.path)) {
|
||||
row.element.append_subpath(subpath, false);
|
||||
row.element_mut().append_subpath(subpath, false);
|
||||
}
|
||||
|
||||
table.push(row);
|
||||
@@ -147,95 +159,107 @@ fn boolean_operation_on_vector_table<'a>(vector: impl DoubleEndedIterator<Item =
|
||||
}
|
||||
|
||||
fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
|
||||
graphic_table
|
||||
.iter()
|
||||
.flat_map(|element| {
|
||||
match element.element.clone() {
|
||||
(0..graphic_table.len())
|
||||
.flat_map(|index| {
|
||||
let graphic = graphic_table.element(index).unwrap();
|
||||
match graphic.clone() {
|
||||
Graphic::Vector(vector) => {
|
||||
// Apply the parent graphic's transform to each element of the vector table
|
||||
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default("transform", index);
|
||||
vector
|
||||
.into_iter()
|
||||
.map(|mut sub_vector| {
|
||||
sub_vector.transform = *element.transform * sub_vector.transform;
|
||||
|
||||
let current_transform: DAffine2 = sub_vector.attribute_cloned_or_default("transform");
|
||||
*sub_vector.attribute_mut_or_insert_default("transform") = parent_transform * current_transform;
|
||||
sub_vector
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
Graphic::RasterCPU(image) => {
|
||||
let make_row = |transform| {
|
||||
// Convert the image frame into a rectangular subpath with the image's transform
|
||||
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default("transform", index);
|
||||
let make_row = |transform, source_node_id, alpha_blending| {
|
||||
let mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
|
||||
subpath.apply_transform(transform);
|
||||
|
||||
// Create a vector table row from the rectangular subpath, with a default black fill
|
||||
let mut element = Vector::from_subpath(subpath);
|
||||
element.style.set_fill(Fill::Solid(Color::BLACK));
|
||||
|
||||
TableRow { element, ..Default::default() }
|
||||
TableRow::new_from_element(element)
|
||||
.with_attribute("alpha_blending", alpha_blending)
|
||||
.with_attribute("source_node_id", source_node_id)
|
||||
};
|
||||
|
||||
// Apply the parent graphic's transform to each raster element
|
||||
image.iter().map(|row| make_row(*element.transform * *row.transform)).collect::<Vec<_>>()
|
||||
// Apply the parent graphic's transform to each raster element, preserving each row's source_node_id
|
||||
// and alpha_blending so the boolean op downstream can route clicks (and inherit blending state)
|
||||
// back to the originating raster layer
|
||||
(0..image.len())
|
||||
.map(|i| {
|
||||
let row_transform: DAffine2 = image.attribute_cloned_or_default("transform", i);
|
||||
let source_node_id: Option<NodeId> = image.attribute_cloned_or_default("source_node_id", i);
|
||||
let alpha_blending: AlphaBlending = image.attribute_cloned_or_default("alpha_blending", i);
|
||||
make_row(parent_transform * row_transform, source_node_id, alpha_blending)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
Graphic::RasterGPU(image) => {
|
||||
let make_row = |transform| {
|
||||
// Convert the image frame into a rectangular subpath with the image's transform
|
||||
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default("transform", index);
|
||||
let make_row = |transform, source_node_id, alpha_blending| {
|
||||
let mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
|
||||
subpath.apply_transform(transform);
|
||||
|
||||
// Create a vector table row from the rectangular subpath, with a default black fill
|
||||
let mut element = Vector::from_subpath(subpath);
|
||||
element.style.set_fill(Fill::Solid(Color::BLACK));
|
||||
|
||||
TableRow { element, ..Default::default() }
|
||||
TableRow::new_from_element(element)
|
||||
.with_attribute("alpha_blending", alpha_blending)
|
||||
.with_attribute("source_node_id", source_node_id)
|
||||
};
|
||||
|
||||
// Apply the parent graphic's transform to each raster element
|
||||
image.iter().map(|row| make_row(*element.transform * *row.transform)).collect::<Vec<_>>()
|
||||
// Apply the parent graphic's transform to each raster element, preserving each row's source_node_id
|
||||
// and alpha_blending so the boolean op downstream can route clicks (and inherit blending state)
|
||||
// back to the originating raster layer
|
||||
(0..image.len())
|
||||
.map(|i| {
|
||||
let row_transform: DAffine2 = image.attribute_cloned_or_default("transform", i);
|
||||
let source_node_id: Option<NodeId> = image.attribute_cloned_or_default("source_node_id", i);
|
||||
let alpha_blending: AlphaBlending = image.attribute_cloned_or_default("alpha_blending", i);
|
||||
make_row(parent_transform * row_transform, source_node_id, alpha_blending)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
Graphic::Graphic(mut graphic) => {
|
||||
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default("transform", index);
|
||||
// Apply the parent graphic's transform to each element of inner table
|
||||
for sub_element in graphic.iter_mut() {
|
||||
*sub_element.transform = *element.transform * *sub_element.transform;
|
||||
for transform in graphic.iter_attribute_values_mut_or_default::<DAffine2>("transform") {
|
||||
*transform = parent_transform * *transform;
|
||||
}
|
||||
|
||||
// Recursively flatten the inner table into the output vector table
|
||||
let unioned = boolean_operation_on_vector_table(flatten_vector(&graphic).iter(), BooleanOperation::Union);
|
||||
let flattened = flatten_vector(&graphic);
|
||||
let unioned = boolean_operation_on_vector_table(&flattened, BooleanOperation::Union);
|
||||
|
||||
unioned.into_iter().collect::<Vec<_>>()
|
||||
}
|
||||
Graphic::Color(color) => color
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
let (color, attributes) = row.into_parts();
|
||||
let mut element = Vector::default();
|
||||
element.style.set_fill(Fill::Solid(row.element));
|
||||
element.style.set_fill(Fill::Solid(color));
|
||||
element.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||
|
||||
TableRow {
|
||||
element,
|
||||
transform: row.transform,
|
||||
alpha_blending: row.alpha_blending,
|
||||
source_node_id: row.source_node_id,
|
||||
}
|
||||
TableRow::from_parts(element, attributes)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
Graphic::Gradient(gradient) => gradient
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
let (stops, attributes) = row.into_parts();
|
||||
let mut element = Vector::default();
|
||||
element.style.set_fill(Fill::Gradient(graphic_types::vector_types::gradient::Gradient {
|
||||
stops: row.element,
|
||||
..Default::default()
|
||||
}));
|
||||
element.style.set_fill(Fill::Gradient(graphic_types::vector_types::gradient::Gradient { stops, ..Default::default() }));
|
||||
element.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||
|
||||
TableRow {
|
||||
element,
|
||||
transform: row.transform,
|
||||
alpha_blending: row.alpha_blending,
|
||||
source_node_id: row.source_node_id,
|
||||
}
|
||||
TableRow::from_parts(element, attributes)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user