Skip to content

Commit eb0f019

Browse files
authored
New nodes: 'Reset Transform', 'Replace Transform', 'Count Points', 'Index Points' (#3420)
- Add the 'Reset Transform' and 'Replace Transform' nodes - Add the 'Count Points' and 'Index Points' nodes - Make the 'Index Elements' node support negative indexing from the end - Make the 'Flatten Vector' node's implementation reusable - Fix crash displaying 0x0 raster image in the Data panel - Fix the 'Points to Polyline' node not working on two-point objects
1 parent 117ce30 commit eb0f019

File tree

9 files changed

+208
-77
lines changed

9 files changed

+208
-77
lines changed

editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -502,10 +502,17 @@ impl TableRowLayout for Raster<CPU> {
502502
format!("Raster ({}x{})", self.width, self.height)
503503
}
504504
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
505-
let base64_string = self.data().base64_string.clone().unwrap_or_else(|| {
505+
let raster = self.data();
506+
507+
if raster.width == 0 || raster.height == 0 {
508+
let widgets = vec![TextLabel::new("Image has no area").widget_holder()];
509+
return vec![LayoutGroup::Row { widgets }];
510+
}
511+
512+
let base64_string = raster.base64_string.clone().unwrap_or_else(|| {
506513
use base64::Engine;
507514

508-
let output = self.data().to_png();
515+
let output = raster.to_png();
509516
let preamble = "data:image/png;base64,";
510517
let mut base64_string = String::with_capacity(preamble.len() + output.len() * 4);
511518
base64_string.push_str(preamble);

editor/src/messages/portfolio/document/node_graph/node_properties.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ pub(crate) fn property_from_type(
154154
Some("PixelLength") => number_widget(default_info, number_input.min(min(0.)).unit(unit.unwrap_or(" px"))).into(),
155155
Some("Length") => number_widget(default_info, number_input.min(min(0.))).into(),
156156
Some("Fraction") => number_widget(default_info, number_input.mode_range().min(min(0.)).max(max(1.))).into(),
157+
Some("SignedInteger") => number_widget(default_info, number_input.int()).into(),
157158
Some("IntegerCount") => number_widget(default_info, number_input.int().min(min(1.))).into(),
158159
Some("SeedValue") => number_widget(default_info, number_input.int().min(min(0.))).into(),
159160
Some("PixelSize") => vec2_widget(default_info, "X", "Y", unit.unwrap_or(" px"), None, false),

node-graph/graph-craft/src/document.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -848,7 +848,7 @@ impl NodeNetwork {
848848
// If the input to self is a node, connect the corresponding output of the inner network to it
849849
NodeInput::Node { node_id, output_index } => {
850850
nested_node.populate_first_network_input(node_id, output_index, nested_input_index, node.original_location.inputs(*import_index), 1);
851-
let input_node = self.nodes.get_mut(&node_id).unwrap_or_else(|| panic!("unable find input node {node_id:?}"));
851+
let input_node = self.nodes.get_mut(&node_id).unwrap_or_else(|| panic!("Unable to find input node {node_id:?}"));
852852
input_node.original_location.dependants[output_index].push(nested_node_id);
853853
}
854854
NodeInput::Import { import_index, .. } => {

node-graph/libraries/core-types/src/ops.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ impl Convert<DVec2, ()> for DVec2 {
7979
}
8080
}
8181

82+
// TODO: Add a DVec2 to Table<Vector> anchor point conversion implementation to replace the 'Vec2 to Point' node
83+
8284
/// Implements the [`Convert`] trait for conversion between the cartesian product of Rust's primitive numeric types.
8385
macro_rules! impl_convert {
8486
($from:ty, $to:ty) => {

node-graph/libraries/graphic-types/src/graphic.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,55 @@ impl From<Table<GradientStops>> for Graphic {
119119
// Local trait to convert types to Table<Graphic> (avoids orphan rule issues)
120120
pub trait IntoGraphicTable {
121121
fn into_graphic_table(self) -> Table<Graphic>;
122+
123+
/// Deeply flattens any vector content within a graphic table, discarding non-vector content, and returning a table of only vector elements.
124+
fn into_flattened_vector_table(self) -> Table<Vector>
125+
where
126+
Self: std::marker::Sized,
127+
{
128+
let content = self.into_graphic_table();
129+
130+
// TODO: Avoid mutable reference, instead return a new Table<Graphic>?
131+
fn flatten_table(output_vector_table: &mut Table<Vector>, current_graphic_table: Table<Graphic>) {
132+
for current_graphic_row in current_graphic_table.iter() {
133+
let current_graphic = current_graphic_row.element.clone();
134+
let source_node_id = *current_graphic_row.source_node_id;
135+
136+
match current_graphic {
137+
// If we're allowed to recurse, flatten any tables we encounter
138+
Graphic::Graphic(mut current_graphic_table) => {
139+
// Apply the parent graphic's transform to all child elements
140+
for graphic in current_graphic_table.iter_mut() {
141+
*graphic.transform = *current_graphic_row.transform * *graphic.transform;
142+
}
143+
144+
flatten_table(output_vector_table, current_graphic_table);
145+
}
146+
// Push any leaf Vector elements we encounter
147+
Graphic::Vector(vector_table) => {
148+
for current_vector_row in vector_table.iter() {
149+
output_vector_table.push(TableRow {
150+
element: current_vector_row.element.clone(),
151+
transform: *current_graphic_row.transform * *current_vector_row.transform,
152+
alpha_blending: AlphaBlending {
153+
blend_mode: current_vector_row.alpha_blending.blend_mode,
154+
opacity: current_graphic_row.alpha_blending.opacity * current_vector_row.alpha_blending.opacity,
155+
fill: current_vector_row.alpha_blending.fill,
156+
clip: current_vector_row.alpha_blending.clip,
157+
},
158+
source_node_id,
159+
});
160+
}
161+
}
162+
_ => {}
163+
}
164+
}
165+
}
166+
167+
let mut output = Table::new();
168+
flatten_table(&mut output, content);
169+
output
170+
}
122171
}
123172

124173
impl IntoGraphicTable for Table<Graphic> {
@@ -284,13 +333,18 @@ impl RenderComplexity for Graphic {
284333
pub trait AtIndex {
285334
type Output;
286335
fn at_index(&self, index: usize) -> Option<Self::Output>;
336+
fn at_index_from_end(&self, index: usize) -> Option<Self::Output>;
287337
}
288338
impl<T: Clone> AtIndex for Vec<T> {
289339
type Output = T;
290340

291341
fn at_index(&self, index: usize) -> Option<Self::Output> {
292342
self.get(index).cloned()
293343
}
344+
345+
fn at_index_from_end(&self, index: usize) -> Option<Self::Output> {
346+
if index == 0 || index > self.len() { None } else { self.get(self.len() - index).cloned() }
347+
}
294348
}
295349
impl<T: Clone> AtIndex for Table<T> {
296350
type Output = Table<T>;
@@ -304,6 +358,18 @@ impl<T: Clone> AtIndex for Table<T> {
304358
None
305359
}
306360
}
361+
362+
fn at_index_from_end(&self, index: usize) -> Option<Self::Output> {
363+
let mut result_table = Self::default();
364+
if index == 0 || index > self.len() {
365+
None
366+
} else if let Some(row) = self.iter().nth(self.len() - index) {
367+
result_table.push(row.into_cloned());
368+
Some(result_table)
369+
} else {
370+
None
371+
}
372+
}
307373
}
308374

309375
// TODO: Eventually remove this migration document upgrade code

node-graph/libraries/no-std-types/src/registry.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ pub mod types {
1919
pub type Length = f64;
2020
/// 0 to 1
2121
pub type Fraction = f64;
22+
/// Signed integer that's actually a float because we don't handle type conversions very well yet
23+
pub type SignedInteger = f64;
2224
/// Unsigned integer
2325
pub type IntegerCount = u32;
2426
/// Unsigned integer to be used for random seeds

node-graph/nodes/graphic/src/graphic.rs

Lines changed: 18 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
11
use core_types::Color;
2-
use core_types::{
3-
Ctx,
4-
blending::AlphaBlending,
5-
table::{Table, TableRow},
6-
uuid::NodeId,
7-
};
2+
use core_types::Ctx;
3+
use core_types::registry::types::SignedInteger;
4+
use core_types::table::{Table, TableRow};
5+
use core_types::uuid::NodeId;
86
use glam::{DAffine2, DVec2};
9-
use graphic_types::{
10-
Artboard, Vector,
11-
graphic::{Graphic, IntoGraphicTable},
12-
};
7+
use graphic_types::graphic::{Graphic, IntoGraphicTable};
8+
use graphic_types::{Artboard, Vector};
139
use raster_types::{CPU, GPU, Raster};
1410
use vector_types::GradientStops;
1511

@@ -164,48 +160,8 @@ pub async fn flatten_graphic(_: impl Ctx, content: Table<Graphic>, fully_flatten
164160

165161
/// Converts a graphic table into a vector table by deeply flattening any vector content it contains, and discarding any non-vector content.
166162
#[node_macro::node(category("Vector"))]
167-
pub async fn flatten_vector(_: impl Ctx, content: Table<Graphic>) -> Table<Vector> {
168-
// TODO: Avoid mutable reference, instead return a new Table<Graphic>?
169-
fn flatten_table(output_vector_table: &mut Table<Vector>, current_graphic_table: Table<Graphic>) {
170-
for current_graphic_row in current_graphic_table.iter() {
171-
let current_graphic = current_graphic_row.element.clone();
172-
let source_node_id = *current_graphic_row.source_node_id;
173-
174-
match current_graphic {
175-
// If we're allowed to recurse, flatten any tables we encounter
176-
Graphic::Graphic(mut current_graphic_table) => {
177-
// Apply the parent graphic's transform to all child elements
178-
for graphic in current_graphic_table.iter_mut() {
179-
*graphic.transform = *current_graphic_row.transform * *graphic.transform;
180-
}
181-
182-
flatten_table(output_vector_table, current_graphic_table);
183-
}
184-
// Push any leaf Vector elements we encounter
185-
Graphic::Vector(vector_table) => {
186-
for current_vector_row in vector_table.iter() {
187-
output_vector_table.push(TableRow {
188-
element: current_vector_row.element.clone(),
189-
transform: *current_graphic_row.transform * *current_vector_row.transform,
190-
alpha_blending: AlphaBlending {
191-
blend_mode: current_vector_row.alpha_blending.blend_mode,
192-
opacity: current_graphic_row.alpha_blending.opacity * current_vector_row.alpha_blending.opacity,
193-
fill: current_vector_row.alpha_blending.fill,
194-
clip: current_vector_row.alpha_blending.clip,
195-
},
196-
source_node_id,
197-
});
198-
}
199-
}
200-
_ => {}
201-
}
202-
}
203-
}
204-
205-
let mut output = Table::new();
206-
flatten_table(&mut output, content);
207-
208-
output
163+
pub async fn flatten_vector<I: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Vector>)] content: I) -> Table<Vector> {
164+
content.into_flattened_vector_table()
209165
}
210166

211167
/// Returns the value at the specified index in the collection.
@@ -229,11 +185,18 @@ pub fn index_elements<T: graphic_types::graphic::AtIndex + Clone + Default>(
229185
Table<GradientStops>,
230186
)]
231187
collection: T,
232-
/// The index of the item to retrieve, starting from 0 for the first item.
233-
index: u32,
188+
/// The index of the item to retrieve, starting from 0 for the first item. Negative indices count backwards from the end of the collection, starting from -1 for the last item.
189+
index: SignedInteger,
234190
) -> T::Output
235191
where
236192
T::Output: Clone + Default,
237193
{
238-
collection.at_index(index as usize).unwrap_or_default()
194+
let index = index as i32;
195+
196+
if index < 0 {
197+
collection.at_index_from_end(-index as usize)
198+
} else {
199+
collection.at_index(index as usize)
200+
}
201+
.unwrap_or_default()
239202
}

node-graph/nodes/transform/src/transform_nodes.rs

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use core_types::color::Color;
33
use core_types::table::Table;
44
use core_types::transform::{ApplyTransform, Transform};
55
use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, InjectFootprint, ModifyFootprint, OwnedContextImpl};
6-
use glam::{DAffine2, DVec2};
6+
use glam::{DAffine2, DMat2, DVec2};
77
use graphic_types::Graphic;
88
use graphic_types::Vector;
99
use graphic_types::raster_types::{CPU, GPU, Raster};
@@ -16,14 +16,14 @@ async fn transform<T: ApplyTransform + 'n + 'static>(
1616
#[implementations(
1717
Context -> DAffine2,
1818
Context -> DVec2,
19-
Context -> Table<Vector>,
2019
Context -> Table<Graphic>,
20+
Context -> Table<Vector>,
2121
Context -> Table<Raster<CPU>>,
2222
Context -> Table<Raster<GPU>>,
2323
Context -> Table<Color>,
2424
Context -> Table<GradientStops>,
2525
)]
26-
value: impl Node<Context<'static>, Output = T>,
26+
content: impl Node<Context<'static>, Output = T>,
2727
translation: DVec2,
2828
rotation: f64,
2929
scale: DVec2,
@@ -41,24 +41,75 @@ async fn transform<T: ApplyTransform + 'n + 'static>(
4141
ctx = ctx.with_footprint(footprint);
4242
}
4343

44-
let mut transform_target = value.eval(ctx.into_context()).await;
44+
let mut transform_target = content.eval(ctx.into_context()).await;
4545

4646
transform_target.left_apply_transform(&matrix);
4747

4848
transform_target
4949
}
5050

51+
/// Resets the desired components of the input transform to their default values. If all components are reset, the output will be set to the identity transform.
52+
/// Shear is represented jointly by rotation and scale, so resetting both will also remove any shear.
53+
#[node_macro::node(category("Math: Transform"))]
54+
fn reset_transform<T>(
55+
_: impl Ctx,
56+
#[implementations(
57+
Table<Graphic>,
58+
Table<Vector>,
59+
Table<Raster<CPU>>,
60+
Table<Raster<GPU>>,
61+
Table<Color>,
62+
Table<GradientStops>,
63+
)]
64+
mut content: Table<T>,
65+
#[default(true)] reset_translation: bool,
66+
reset_rotation: bool,
67+
reset_scale: bool,
68+
) -> Table<T> {
69+
for row in content.iter_mut() {
70+
// Translation
71+
if reset_translation {
72+
row.transform.translation = DVec2::ZERO;
73+
}
74+
// (Rotation, Scale)
75+
match (reset_rotation, reset_scale) {
76+
(true, true) => {
77+
row.transform.matrix2 = DMat2::IDENTITY;
78+
}
79+
(true, false) => {
80+
let scale = row.transform.decompose_scale();
81+
row.transform.matrix2 = DMat2::from_diagonal(scale);
82+
}
83+
(false, true) => {
84+
let rotation = row.transform.decompose_rotation();
85+
let rotation_matrix = DMat2::from_angle(rotation);
86+
row.transform.matrix2 = rotation_matrix;
87+
}
88+
(false, false) => {}
89+
}
90+
}
91+
content
92+
}
93+
5194
/// Overwrites the transform of each element in the input table with the specified transform.
52-
#[node_macro::node(category(""))]
53-
fn replace_transform<Data, TransformInput: Transform>(
95+
#[node_macro::node(category("Math: Transform"))]
96+
fn replace_transform<T>(
5497
_: impl Ctx + InjectFootprint,
55-
#[implementations(Table<Vector>, Table<Raster<CPU>>, Table<Graphic>, Table<Color>, Table<GradientStops>)] mut data: Table<Data>,
56-
#[implementations(DAffine2)] transform: TransformInput,
57-
) -> Table<Data> {
58-
for data_transform in data.iter_mut() {
59-
*data_transform.transform = transform.transform();
98+
#[implementations(
99+
Table<Graphic>,
100+
Table<Vector>,
101+
Table<Raster<CPU>>,
102+
Table<Raster<GPU>>,
103+
Table<Color>,
104+
Table<GradientStops>,
105+
)]
106+
mut content: Table<T>,
107+
transform: DAffine2,
108+
) -> Table<T> {
109+
for row in content.iter_mut() {
110+
*row.transform = transform.transform();
60111
}
61-
data
112+
content
62113
}
63114

64115
// TODO: Figure out how this node should behave once #2982 is implemented.
@@ -74,9 +125,9 @@ async fn extract_transform<T>(
74125
Table<Color>,
75126
Table<GradientStops>,
76127
)]
77-
vector: Table<T>,
128+
content: Table<T>,
78129
) -> DAffine2 {
79-
vector.iter().next().map(|row| *row.transform).unwrap_or_default()
130+
content.iter().next().map(|row| *row.transform).unwrap_or_default()
80131
}
81132

82133
/// Produces the inverse of the input transform, which is the transform that undoes the effect of the original transform.

0 commit comments

Comments
 (0)