mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-02 05:58:12 +08:00
Replace deprecated row/cell/instance terminology with "item" and "value" terms (#4075)
This commit is contained in:
@@ -102,22 +102,22 @@ impl LayoutMessageHandler {
|
|||||||
}
|
}
|
||||||
LayoutGroup::Table(WidgetTable { rows, .. }) => {
|
LayoutGroup::Table(WidgetTable { rows, .. }) => {
|
||||||
for (row_index, row) in rows.iter().enumerate() {
|
for (row_index, row) in rows.iter().enumerate() {
|
||||||
for (cell_index, cell) in row.iter().enumerate() {
|
for (value_index, value) in row.iter().enumerate() {
|
||||||
// Return if this is the correct ID
|
// Return if this is the correct ID
|
||||||
if cell.widget_id == widget_id {
|
if value.widget_id == widget_id {
|
||||||
widget_path.push(row_index);
|
widget_path.push(row_index);
|
||||||
widget_path.push(cell_index);
|
widget_path.push(value_index);
|
||||||
return Some((cell, widget_path));
|
return Some((value, widget_path));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Widget::PopoverButton(popover) = &*cell.widget {
|
if let Widget::PopoverButton(popover) = &*value.widget {
|
||||||
stack.extend(
|
stack.extend(
|
||||||
popover
|
popover
|
||||||
.popover_layout
|
.popover_layout
|
||||||
.0
|
.0
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(child, val)| ([widget_path.as_slice(), &[row_index, cell_index, child]].concat(), val)),
|
.map(|(child, val)| ([widget_path.as_slice(), &[row_index, value_index, child]].concat(), val)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ macro_rules! define_layout_target {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
define_layout_target!(
|
define_layout_target!(
|
||||||
/// The spreadsheet panel allows for the visualisation of data in the graph.
|
/// The Data panel visualizes the output data flowing through the selected node in the graph.
|
||||||
DataPanel,
|
DataPanel,
|
||||||
/// Contains the action buttons at the bottom of the dialog. Must be shown with the `FrontendMessage::DisplayDialog` message.
|
/// Contains the action buttons at the bottom of the dialog. Must be shown with the `FrontendMessage::DisplayDialog` message.
|
||||||
DialogButtons,
|
DialogButtons,
|
||||||
@@ -785,7 +785,7 @@ pub enum Widget {
|
|||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct WidgetDiff {
|
pub struct WidgetDiff {
|
||||||
/// A path to the change
|
/// A path to the change
|
||||||
/// e.g. [0, 1, 2] in the properties panel is the first section, second row and third widget.
|
/// e.g. [0, 1, 2] in the Properties panel is the first section, second row and third widget.
|
||||||
/// An empty path [] shows that the entire panel has changed and is sent when the UI is first created.
|
/// An empty path [] shows that the entire panel has changed and is sent when the UI is first created.
|
||||||
#[serde(rename = "widgetPath")]
|
#[serde(rename = "widgetPath")]
|
||||||
pub widget_path: Vec<usize>,
|
pub widget_path: Vec<usize>,
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ pub enum DataPanelMessage {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One hop in the breadcrumb path through nested data the data panel is displaying.
|
/// One hop in the breadcrumb path through nested data the Data panel is displaying.
|
||||||
/// Drilling into a row's element produces an `Element` step; drilling into one of a row's attributes produces an `Attribute` step.
|
/// Drilling into an item's element produces an `Element` step; drilling into one of an item's attributes produces an `Attribute` step.
|
||||||
#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum PathStep {
|
pub enum PathStep {
|
||||||
Element(usize),
|
Element(usize),
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ pub struct DataPanelMessageContext<'a> {
|
|||||||
pub data_panel_open: bool,
|
pub data_panel_open: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The data panel allows for graph data to be previewed.
|
/// The Data panel allows for graph data to be previewed.
|
||||||
#[derive(Default, Debug, Clone, ExtractField)]
|
#[derive(Default, Debug, Clone, ExtractField)]
|
||||||
pub struct DataPanelMessageHandler {
|
pub struct DataPanelMessageHandler {
|
||||||
/// Full path from the root network to the introspected node, with the node itself as the last element.
|
/// Full path from the root network to the introspected node, with the node itself as the last element.
|
||||||
@@ -155,7 +155,7 @@ struct LayoutData<'a> {
|
|||||||
current_depth: usize,
|
current_depth: usize,
|
||||||
desired_path: &'a mut Vec<PathStep>,
|
desired_path: &'a mut Vec<PathStep>,
|
||||||
network_interface: &'a NodeNetworkInterface,
|
network_interface: &'a NodeNetworkInterface,
|
||||||
/// The `network_path` to use when resolving a `NodeId` cell or leaf page against the network interface.
|
/// The `network_path` to use when resolving a `NodeId` against the network interface.
|
||||||
/// Defaults to root (`&[]`); `Table<NodeId>` rendering temporarily sets it to the path's prefix so nested
|
/// Defaults to root (`&[]`); `Table<NodeId>` rendering temporarily sets it to the path's prefix so nested
|
||||||
/// layers (e.g. inside a Ctrl+M-merged custom subgraph) resolve correctly.
|
/// layers (e.g. inside a Ctrl+M-merged custom subgraph) resolve correctly.
|
||||||
node_lookup_network_path: Vec<NodeId>,
|
node_lookup_network_path: Vec<NodeId>,
|
||||||
@@ -177,7 +177,7 @@ macro_rules! generate_layout_downcast {
|
|||||||
// TODO: We simply try all these types sequentially. Find a better strategy.
|
// TODO: We simply try all these types sequentially. Find a better strategy.
|
||||||
fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'static>, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> {
|
fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'static>, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> {
|
||||||
// `Table<NodeId>` is interpreted as a path (e.g. the value produced by `path_of_subgraph`), shown as a
|
// `Table<NodeId>` is interpreted as a path (e.g. the value produced by `path_of_subgraph`), shown as a
|
||||||
// table where each row's NodeId resolves against the prefix made up of the rows above it.
|
// `Table` where each item's NodeId resolves against the prefix made up of the items above it.
|
||||||
if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<NodeId>>>() {
|
if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<NodeId>>>() {
|
||||||
return Some(table_node_id_path_layout_with_breadcrumb(&io.output, data));
|
return Some(table_node_id_path_layout_with_breadcrumb(&io.output, data));
|
||||||
}
|
}
|
||||||
@@ -218,22 +218,22 @@ trait TableRowLayout {
|
|||||||
fn identifier(&self) -> String;
|
fn identifier(&self) -> String;
|
||||||
fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
data.breadcrumbs.push(self.identifier());
|
data.breadcrumbs.push(self.identifier());
|
||||||
self.element_page(data)
|
self.value_page(data)
|
||||||
}
|
}
|
||||||
/// Renders this value as a single inline widget inside a row of a Vec/Table.
|
/// Renders this value as a single inline widget inside an item of a Table.
|
||||||
/// `target` is the [`PathStep`] to push when the cell is clicked to drill into the value.
|
/// `target` is the [`PathStep`] to push when the widget is clicked to drill into the value.
|
||||||
/// `data` provides shared context (notably `network_interface`) for types whose label or content
|
/// `data` provides shared context (notably `network_interface`) for types whose label or content
|
||||||
/// depends on lookup beyond their own value (e.g. `NodeId` resolving a node's display name).
|
/// depends on lookup beyond their own value (e.g. `NodeId` resolving a node's display name).
|
||||||
/// The default is a button labeled with `identifier()`. Types whose values are best shown
|
/// The default is a button labeled with `identifier()`. Types whose values are best shown
|
||||||
/// inline (colors, transforms, primitives, etc.) override this to ignore `target` and
|
/// inline (colors, transforms, primitives, etc.) override this to ignore `target` and
|
||||||
/// return a richer non-navigating widget.
|
/// return a richer non-navigating widget.
|
||||||
fn cell_widget(&self, target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
fn value_widget(&self, target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
||||||
TextButton::new(self.identifier())
|
TextButton::new(self.identifier())
|
||||||
.on_update(move |_| DataPanelMessage::PushToElementPath { step: target.clone() }.into())
|
.on_update(move |_| DataPanelMessage::PushToElementPath { step: target.clone() }.into())
|
||||||
.narrow(true)
|
.narrow(true)
|
||||||
.widget_instance()
|
.widget_instance()
|
||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -243,9 +243,9 @@ impl<T: TableRowLayout> TableRowLayout for Table<T> {
|
|||||||
"Table"
|
"Table"
|
||||||
}
|
}
|
||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
format!("Table<{}> ({} element{})", T::type_name(), self.len(), if self.len() == 1 { "" } else { "s" })
|
format!("{}[] ({} item{})", T::type_name(), self.len(), if self.len() == 1 { "" } else { "s" })
|
||||||
}
|
}
|
||||||
fn element_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn value_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
if let Some(step) = data.desired_path.get(data.current_depth).cloned() {
|
if let Some(step) = data.desired_path.get(data.current_depth).cloned() {
|
||||||
match step {
|
match step {
|
||||||
PathStep::Element(index) => {
|
PathStep::Element(index) => {
|
||||||
@@ -279,16 +279,16 @@ impl<T: TableRowLayout> TableRowLayout for Table<T> {
|
|||||||
let mut rows = (0..self.len())
|
let mut rows = (0..self.len())
|
||||||
.map(|index| {
|
.map(|index| {
|
||||||
let element = self.element(index).unwrap();
|
let element = self.element(index).unwrap();
|
||||||
let mut cells = vec![TextLabel::new(format!("{index}")).narrow(true).widget_instance(), element.cell_widget(PathStep::Element(index), data)];
|
let mut values = vec![TextLabel::new(format!("{index}")).narrow(true).widget_instance(), element.value_widget(PathStep::Element(index), data)];
|
||||||
for key in &attribute_keys {
|
for key in &attribute_keys {
|
||||||
let target = PathStep::Attribute { row: index, key: key.clone() };
|
let target = PathStep::Attribute { row: index, key: key.clone() };
|
||||||
let widget = self.attribute_any(key, index).and_then(|any| dispatch_cell_widget(any, target, data)).unwrap_or_else(|| {
|
let widget = self.attribute_any(key, index).and_then(|any| dispatch_value_widget(any, target, data)).unwrap_or_else(|| {
|
||||||
let text = self.attribute_display_value(key, index, |_| None).unwrap_or_else(|| "-".to_string());
|
let text = self.attribute_display_value(key, index, |_| None).unwrap_or_else(|| "-".to_string());
|
||||||
TextLabel::new(text).narrow(true).widget_instance()
|
TextLabel::new(text).narrow(true).widget_instance()
|
||||||
});
|
});
|
||||||
cells.push(widget);
|
values.push(widget);
|
||||||
}
|
}
|
||||||
cells
|
values
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
@@ -307,8 +307,8 @@ impl TableRowLayout for Artboard {
|
|||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
self.label.clone()
|
self.label.clone()
|
||||||
}
|
}
|
||||||
fn element_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn value_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
self.content.element_page(data)
|
self.content.value_page(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,9 +328,9 @@ impl TableRowLayout for Graphic {
|
|||||||
}
|
}
|
||||||
// Don't put a breadcrumb for Graphic
|
// Don't put a breadcrumb for Graphic
|
||||||
fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
self.element_page(data)
|
self.value_page(data)
|
||||||
}
|
}
|
||||||
fn element_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn value_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
match self {
|
match self {
|
||||||
Self::Graphic(table) => table.layout_with_breadcrumb(data),
|
Self::Graphic(table) => table.layout_with_breadcrumb(data),
|
||||||
Self::Vector(table) => table.layout_with_breadcrumb(data),
|
Self::Vector(table) => table.layout_with_breadcrumb(data),
|
||||||
@@ -355,7 +355,7 @@ impl TableRowLayout for Vector {
|
|||||||
if self.segment_domain.ids().len() == 1 { "" } else { "s" }
|
if self.segment_domain.ids().len() == 1 { "" } else { "s" }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
fn element_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn value_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
let table_tab_entries = [VectorTableTab::Properties, VectorTableTab::Points, VectorTableTab::Segments, VectorTableTab::Regions]
|
let table_tab_entries = [VectorTableTab::Properties, VectorTableTab::Points, VectorTableTab::Segments, VectorTableTab::Regions]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|tab| {
|
.map(|tab| {
|
||||||
@@ -508,7 +508,7 @@ impl TableRowLayout for Raster<CPU> {
|
|||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
format!("Raster ({} x {})", self.width, self.height)
|
format!("Raster ({} x {})", self.width, self.height)
|
||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
let raster = self.data();
|
let raster = self.data();
|
||||||
|
|
||||||
if raster.width == 0 || raster.height == 0 {
|
if raster.width == 0 || raster.height == 0 {
|
||||||
@@ -539,7 +539,7 @@ impl TableRowLayout for Raster<GPU> {
|
|||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
format!("Raster ({} x {})", self.data().width(), self.data().height())
|
format!("Raster ({} x {})", self.data().width(), self.data().height())
|
||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
let widgets = vec![TextLabel::new("Raster is a texture on the GPU and cannot currently be displayed here").widget_instance()];
|
let widgets = vec![TextLabel::new("Raster is a texture on the GPU and cannot currently be displayed here").widget_instance()];
|
||||||
vec![LayoutGroup::row(widgets)]
|
vec![LayoutGroup::row(widgets)]
|
||||||
}
|
}
|
||||||
@@ -552,15 +552,15 @@ impl TableRowLayout for Color {
|
|||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
format!("Color (#{})", self.to_gamma_srgb().to_rgba_hex_srgb())
|
format!("Color (#{})", self.to_gamma_srgb().to_rgba_hex_srgb())
|
||||||
}
|
}
|
||||||
fn cell_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
||||||
ColorInput::new(FillChoice::Solid(*self))
|
ColorInput::new(FillChoice::Solid(*self))
|
||||||
.disabled(true)
|
.disabled(true)
|
||||||
.menu_direction(Some(MenuDirection::Top))
|
.menu_direction(Some(MenuDirection::Top))
|
||||||
.narrow(true)
|
.narrow(true)
|
||||||
.widget_instance()
|
.widget_instance()
|
||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
let widgets = vec![self.cell_widget(PathStep::Element(0), _data)];
|
let widgets = vec![self.value_widget(PathStep::Element(0), _data)];
|
||||||
vec![LayoutGroup::row(widgets)]
|
vec![LayoutGroup::row(widgets)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -572,15 +572,15 @@ impl TableRowLayout for GradientStops {
|
|||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
format!("Gradient ({} stops)", self.len())
|
format!("Gradient ({} stops)", self.len())
|
||||||
}
|
}
|
||||||
fn cell_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
||||||
ColorInput::new(FillChoice::Gradient(self.clone()))
|
ColorInput::new(FillChoice::Gradient(self.clone()))
|
||||||
.menu_direction(Some(MenuDirection::Top))
|
.menu_direction(Some(MenuDirection::Top))
|
||||||
.disabled(true)
|
.disabled(true)
|
||||||
.narrow(true)
|
.narrow(true)
|
||||||
.widget_instance()
|
.widget_instance()
|
||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
let widgets = vec![self.cell_widget(PathStep::Element(0), _data)];
|
let widgets = vec![self.value_widget(PathStep::Element(0), _data)];
|
||||||
vec![LayoutGroup::row(widgets)]
|
vec![LayoutGroup::row(widgets)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -592,8 +592,8 @@ impl TableRowLayout for f64 {
|
|||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
format!("{self}")
|
format!("{self}")
|
||||||
}
|
}
|
||||||
// Cells fall back to the default drill-in button (labeled with the value via `identifier`); the leaf page shows the rich `NumberInput`.
|
// Values fall back to the default drill-in button (labeled via `identifier`); the value page shows the rich `NumberInput`.
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
vec![LayoutGroup::row(vec![
|
vec![LayoutGroup::row(vec![
|
||||||
NumberInput::new(Some(*self)).disabled(true).max_width(220).display_decimal_places(20).widget_instance(),
|
NumberInput::new(Some(*self)).disabled(true).max_width(220).display_decimal_places(20).widget_instance(),
|
||||||
])]
|
])]
|
||||||
@@ -607,8 +607,8 @@ impl TableRowLayout for u8 {
|
|||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
format!("{self:02X}")
|
format!("{self:02X}")
|
||||||
}
|
}
|
||||||
// Cells fall back to the default drill-in button (labeled with the hex value via `identifier`); the leaf page shows the same hex value as a label.
|
// Values fall back to the default drill-in button (labeled with the hex string via `identifier`); the value page shows the same hex value as a label.
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
vec![LayoutGroup::row(vec![TextLabel::new(self.identifier()).widget_instance()])]
|
vec![LayoutGroup::row(vec![TextLabel::new(self.identifier()).widget_instance()])]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -620,8 +620,8 @@ impl TableRowLayout for u32 {
|
|||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
format!("{self}")
|
format!("{self}")
|
||||||
}
|
}
|
||||||
// Cells fall back to the default drill-in button (labeled with the value via `identifier`); the leaf page shows the rich `NumberInput`.
|
// Values fall back to the default drill-in button (labeled via `identifier`); the value page shows the rich `NumberInput`.
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
vec![LayoutGroup::row(vec![
|
vec![LayoutGroup::row(vec![
|
||||||
NumberInput::new(Some(*self as f64)).disabled(true).max_width(220).display_decimal_places(20).widget_instance(),
|
NumberInput::new(Some(*self as f64)).disabled(true).max_width(220).display_decimal_places(20).widget_instance(),
|
||||||
])]
|
])]
|
||||||
@@ -635,9 +635,9 @@ impl TableRowLayout for u64 {
|
|||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
format!("{self}")
|
format!("{self}")
|
||||||
}
|
}
|
||||||
// Cells fall back to the default drill-in button (labeled with the value via `identifier`); the leaf page shows the rich `NumberInput`.
|
// Values fall back to the default drill-in button (labeled via `identifier`); the value page shows the rich `NumberInput`.
|
||||||
// TODO: Make this robust for large u64 values that don't fit in f64 (above roughly 2^53). Perhaps using a bigint kind of approach through the widget's data flow.
|
// TODO: Make this robust for large u64 values that don't fit in f64 (above roughly 2^53). Perhaps using a bigint kind of approach through the widget's data flow.
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
vec![LayoutGroup::row(vec![
|
vec![LayoutGroup::row(vec![
|
||||||
NumberInput::new(Some(*self as f64)).disabled(true).max_width(220).display_decimal_places(20).widget_instance(),
|
NumberInput::new(Some(*self as f64)).disabled(true).max_width(220).display_decimal_places(20).widget_instance(),
|
||||||
])]
|
])]
|
||||||
@@ -651,11 +651,11 @@ impl TableRowLayout for bool {
|
|||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
"Bool".to_string()
|
"Bool".to_string()
|
||||||
}
|
}
|
||||||
fn cell_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
||||||
TextLabel::new(self.to_string()).narrow(true).widget_instance()
|
TextLabel::new(self.to_string()).narrow(true).widget_instance()
|
||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0), _data)])]
|
vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -672,8 +672,8 @@ impl TableRowLayout for String {
|
|||||||
format!("\"{}\"", first_line)
|
format!("\"{}\"", first_line)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Cells fall back to the default drill-in button (labeled with the truncated quoted preview via `identifier`); the leaf page shows the full multi-line text in a `TextAreaInput`.
|
// Values fall back to the default drill-in button (labeled with the truncated quoted preview via `identifier`); the value page shows the full multi-line text in a `TextAreaInput`.
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
vec![LayoutGroup::row(vec![TextAreaInput::new(self.to_string()).monospace(true).disabled(true).widget_instance()])]
|
vec![LayoutGroup::row(vec![TextAreaInput::new(self.to_string()).monospace(true).disabled(true).widget_instance()])]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -685,11 +685,11 @@ impl TableRowLayout for Option<f64> {
|
|||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
"Option<f64>".to_string()
|
"Option<f64>".to_string()
|
||||||
}
|
}
|
||||||
fn cell_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
||||||
TextLabel::new(format!("{self:?}")).narrow(true).widget_instance()
|
TextLabel::new(format!("{self:?}")).narrow(true).widget_instance()
|
||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0), _data)])]
|
vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -700,11 +700,11 @@ impl TableRowLayout for DVec2 {
|
|||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
"Vec2".to_string()
|
"Vec2".to_string()
|
||||||
}
|
}
|
||||||
fn cell_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
||||||
TextLabel::new(format_dvec2(*self)).narrow(true).widget_instance()
|
TextLabel::new(format_dvec2(*self)).narrow(true).widget_instance()
|
||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0), _data)])]
|
vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -715,11 +715,11 @@ impl TableRowLayout for Vec2 {
|
|||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
"Vec2".to_string()
|
"Vec2".to_string()
|
||||||
}
|
}
|
||||||
fn cell_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
||||||
TextLabel::new(format_dvec2(DVec2::new(self.x as f64, self.y as f64))).narrow(true).widget_instance()
|
TextLabel::new(format_dvec2(DVec2::new(self.x as f64, self.y as f64))).narrow(true).widget_instance()
|
||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0), _data)])]
|
vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -730,11 +730,11 @@ impl TableRowLayout for DAffine2 {
|
|||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
"Transform".to_string()
|
"Transform".to_string()
|
||||||
}
|
}
|
||||||
fn cell_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
||||||
TextLabel::new(format_transform_matrix(*self)).narrow(true).widget_instance()
|
TextLabel::new(format_transform_matrix(*self)).narrow(true).widget_instance()
|
||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0), _data)])]
|
vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -745,12 +745,12 @@ impl TableRowLayout for Affine2 {
|
|||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
"Transform".to_string()
|
"Transform".to_string()
|
||||||
}
|
}
|
||||||
fn cell_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
||||||
let matrix = DAffine2::from_cols_array(&self.to_cols_array().map(|x| x as f64));
|
let matrix = DAffine2::from_cols_array(&self.to_cols_array().map(|x| x as f64));
|
||||||
TextLabel::new(format_transform_matrix(matrix)).narrow(true).widget_instance()
|
TextLabel::new(format_transform_matrix(matrix)).narrow(true).widget_instance()
|
||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0), _data)])]
|
vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -761,15 +761,15 @@ impl TableRowLayout for AlphaBlending {
|
|||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
format_alpha_blending(*self)
|
format_alpha_blending(*self)
|
||||||
}
|
}
|
||||||
fn cell_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
||||||
TextLabel::new(format_alpha_blending(*self)).narrow(true).widget_instance()
|
TextLabel::new(format_alpha_blending(*self)).narrow(true).widget_instance()
|
||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0), _data)])]
|
vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolves the cell/breadcrumb label for a `NodeId` against `network_interface` at the given `network_path`,
|
/// Resolves the value/breadcrumb label for a `NodeId` against `network_interface` at the given `network_path`,
|
||||||
/// falling back to "Node {id}" if the node isn't present (e.g. an ID that no longer maps to a real node).
|
/// falling back to "Node {id}" if the node isn't present (e.g. an ID that no longer maps to a real node).
|
||||||
fn node_id_display_label(node_id: NodeId, network_interface: &NodeNetworkInterface, network_path: &[NodeId]) -> String {
|
fn node_id_display_label(node_id: NodeId, network_interface: &NodeNetworkInterface, network_path: &[NodeId]) -> String {
|
||||||
if network_interface.node_metadata(&node_id, network_path).is_some() {
|
if network_interface.node_metadata(&node_id, network_path).is_some() {
|
||||||
@@ -786,16 +786,16 @@ impl TableRowLayout for NodeId {
|
|||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
format!("Node {self}")
|
format!("Node {self}")
|
||||||
}
|
}
|
||||||
// Override so the breadcrumb uses the same resolved display name as the cell button, instead of the bare-ID fallback `identifier()` returns.
|
// Override so the breadcrumb uses the same resolved display name as the value button, instead of the bare-ID fallback `identifier()` returns.
|
||||||
fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
data.breadcrumbs.push(node_id_display_label(*self, data.network_interface, &data.node_lookup_network_path));
|
data.breadcrumbs.push(node_id_display_label(*self, data.network_interface, &data.node_lookup_network_path));
|
||||||
self.element_page(data)
|
self.value_page(data)
|
||||||
}
|
}
|
||||||
// Cell label resolves the node's display name via the network interface so the button reads as the name shown
|
// The value's label resolves the node's display name via the network interface so the button reads as the name shown
|
||||||
// in the Node Graph / Layers panels. The lookup uses `data.node_lookup_network_path` (set by the enclosing
|
// in the Node Graph / Layers panels. The lookup uses `data.node_lookup_network_path` (set by the enclosing
|
||||||
// `Table<NodeId>` if rendering a path) so the resolution succeeds at any nesting depth. The button's icon
|
// `Table<NodeId>` if rendering a path) so the resolution succeeds at any nesting depth. The button's icon
|
||||||
// signals layer-vs-node kind. Falls back to "Node {id}" with no icon if the lookup misses.
|
// signals layer-vs-node kind. Falls back to "Node {id}" with no icon if the lookup misses.
|
||||||
fn cell_widget(&self, target: PathStep, data: &LayoutData) -> WidgetInstance {
|
fn value_widget(&self, target: PathStep, data: &LayoutData) -> WidgetInstance {
|
||||||
let label = node_id_display_label(*self, data.network_interface, &data.node_lookup_network_path);
|
let label = node_id_display_label(*self, data.network_interface, &data.node_lookup_network_path);
|
||||||
let mut button = TextButton::new(label)
|
let mut button = TextButton::new(label)
|
||||||
.on_update(move |_| DataPanelMessage::PushToElementPath { step: target.clone() }.into())
|
.on_update(move |_| DataPanelMessage::PushToElementPath { step: target.clone() }.into())
|
||||||
@@ -806,8 +806,8 @@ impl TableRowLayout for NodeId {
|
|||||||
}
|
}
|
||||||
button.widget_instance()
|
button.widget_instance()
|
||||||
}
|
}
|
||||||
// The leaf page shows the node's kind, name (editable), lock/visibility toggles, and a "Select Layer/Node" action button.
|
// The value page shows the node's kind, name (editable), lock/visibility toggles, and a "Select Layer/Node" action button.
|
||||||
fn element_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn value_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
let node_id = *self;
|
let node_id = *self;
|
||||||
let network_path = data.node_lookup_network_path.clone();
|
let network_path = data.node_lookup_network_path.clone();
|
||||||
let known = data.network_interface.node_metadata(&node_id, &network_path).is_some();
|
let known = data.network_interface.node_metadata(&node_id, &network_path).is_some();
|
||||||
@@ -892,7 +892,7 @@ impl TableRowLayout for NodeId {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Invokes another macro with the full list of `TableRowLayout`-implementing types whose values may appear
|
/// Invokes another macro with the full list of `TableRowLayout`-implementing types whose values may appear
|
||||||
/// as attribute cell values. Both the cell-rendering and drilldown-navigation dispatchers iterate this list,
|
/// as attribute values. Both the value-rendering and drilldown-navigation dispatchers iterate this list,
|
||||||
/// so adding a new attribute-displayable type is a single edit here.
|
/// so adding a new attribute-displayable type is a single edit here.
|
||||||
macro_rules! known_table_row_types {
|
macro_rules! known_table_row_types {
|
||||||
($apply:ident) => {
|
($apply:ident) => {
|
||||||
@@ -932,16 +932,16 @@ macro_rules! known_table_row_types {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Type-dispatched widget for displaying an attribute cell in a `Table<T>` row.
|
/// Type-dispatched widget for displaying an attribute value in a `Table<T>` item.
|
||||||
/// Delegates to [`TableRowLayout::cell_widget`] so the same widget code is shared between
|
/// Delegates to [`TableRowLayout::value_widget`] so the same widget code is shared between
|
||||||
/// element-column rendering and attribute-column rendering. Returns `None` for unrecognized types so the
|
/// element-column rendering and attribute-column rendering. Returns `None` for unrecognized types so the
|
||||||
/// caller can fall back to a debug-formatted [`TextLabel`].
|
/// caller can fall back to a debug-formatted [`TextLabel`].
|
||||||
fn dispatch_cell_widget(any: &dyn Any, target: PathStep, data: &LayoutData) -> Option<WidgetInstance> {
|
fn dispatch_value_widget(any: &dyn Any, target: PathStep, data: &LayoutData) -> Option<WidgetInstance> {
|
||||||
macro_rules! check {
|
macro_rules! check {
|
||||||
( $($ty:ty),* $(,)? ) => {
|
( $($ty:ty),* $(,)? ) => {
|
||||||
$(
|
$(
|
||||||
if let Some(value) = any.downcast_ref::<$ty>() {
|
if let Some(value) = any.downcast_ref::<$ty>() {
|
||||||
return Some(value.cell_widget(target, data));
|
return Some(value.value_widget(target, data));
|
||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
};
|
};
|
||||||
@@ -950,10 +950,10 @@ fn dispatch_cell_widget(any: &dyn Any, target: PathStep, data: &LayoutData) -> O
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Renders a `Table<NodeId>` as a path: the standard table view, but each row's `NodeId` cell is resolved
|
/// Renders a `Table<NodeId>` as a path: the standard table view, but each item's `NodeId` value is resolved
|
||||||
/// against the network path made up of all preceding rows. So for a path `[outer, middle, leaf]`, row 0
|
/// against the network path made up of all preceding items. So for a path `[outer, middle, leaf]`, item 0
|
||||||
/// resolves at root, row 1 resolves at `[outer]`, and row 2 resolves at `[outer, middle]` — letting deeply
|
/// resolves at root, item 1 resolves at `[outer]`, and item 2 resolves at `[outer, middle]` — letting deeply
|
||||||
/// nested layers display each step's correct name. Drilling into a row drops into that node's leaf page
|
/// nested layers display each step's correct name. Drilling into an item drops into that node's value page
|
||||||
/// using the same prefix as `network_path`.
|
/// using the same prefix as `network_path`.
|
||||||
fn table_node_id_path_layout_with_breadcrumb(path: &Table<NodeId>, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn table_node_id_path_layout_with_breadcrumb(path: &Table<NodeId>, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
data.breadcrumbs.push(path.identifier());
|
data.breadcrumbs.push(path.identifier());
|
||||||
@@ -979,7 +979,7 @@ fn table_node_id_path_layout_with_breadcrumb(path: &Table<NodeId>, data: &mut La
|
|||||||
let node_id = path.element(index).unwrap();
|
let node_id = path.element(index).unwrap();
|
||||||
let prefix: Vec<NodeId> = path.iter_element_values().take(index).copied().collect();
|
let prefix: Vec<NodeId> = path.iter_element_values().take(index).copied().collect();
|
||||||
let saved = std::mem::replace(&mut data.node_lookup_network_path, prefix);
|
let saved = std::mem::replace(&mut data.node_lookup_network_path, prefix);
|
||||||
let widget = node_id.cell_widget(PathStep::Element(index), data);
|
let widget = node_id.value_widget(PathStep::Element(index), data);
|
||||||
data.node_lookup_network_path = saved;
|
data.node_lookup_network_path = saved;
|
||||||
vec![TextLabel::new(format!("{index}")).narrow(true).widget_instance(), widget]
|
vec![TextLabel::new(format!("{index}")).narrow(true).widget_instance(), widget]
|
||||||
})
|
})
|
||||||
@@ -989,12 +989,12 @@ fn table_node_id_path_layout_with_breadcrumb(path: &Table<NodeId>, data: &mut La
|
|||||||
vec![LayoutGroup::table(rows, false)]
|
vec![LayoutGroup::table(rows, false)]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Type-dispatched recursion into an attribute value for the data panel breadcrumb navigation.
|
/// Type-dispatched recursion into an attribute value for the Data panel breadcrumb navigation.
|
||||||
/// Mirrors [`dispatch_cell_widget`] but routes to [`TableRowLayout::layout_with_breadcrumb`].
|
/// Mirrors [`dispatch_value_widget`] but routes to [`TableRowLayout::layout_with_breadcrumb`].
|
||||||
/// Returns `None` for unrecognized types.
|
/// Returns `None` for unrecognized types.
|
||||||
fn drilldown_attribute_layout(any: &dyn Any, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> {
|
fn drilldown_attribute_layout(any: &dyn Any, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> {
|
||||||
// `Table<NodeId>` is interpreted as a path (e.g. the `editor:layer` attribute), so each row's NodeId cell
|
// `Table<NodeId>` is interpreted as a path (e.g. the `editor:layer` attribute), so each item's NodeId value
|
||||||
// resolves against the prefix made up of preceding rows. Handled before the generic `Table<T>` blanket impl.
|
// resolves against the prefix made up of preceding items. Handled before the generic `Table<T>` blanket impl.
|
||||||
if let Some(path) = any.downcast_ref::<Table<NodeId>>() {
|
if let Some(path) = any.downcast_ref::<Table<NodeId>>() {
|
||||||
return Some(table_node_id_path_layout_with_breadcrumb(path, data));
|
return Some(table_node_id_path_layout_with_breadcrumb(path, data));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ pub enum DocumentMessage {
|
|||||||
undo_count: usize,
|
undo_count: usize,
|
||||||
},
|
},
|
||||||
ToggleLayerExpansion {
|
ToggleLayerExpansion {
|
||||||
instance_path: Vec<NodeId>,
|
tree_path: Vec<NodeId>,
|
||||||
recursive: bool,
|
recursive: bool,
|
||||||
},
|
},
|
||||||
ToggleSelectedVisibility,
|
ToggleSelectedVisibility,
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ pub struct DocumentMessageHandler {
|
|||||||
//
|
//
|
||||||
// Contains the NodeNetwork and acts an an interface to manipulate the NodeNetwork with custom setters in order to keep NetworkMetadata in sync
|
// Contains the NodeNetwork and acts an an interface to manipulate the NodeNetwork with custom setters in order to keep NetworkMetadata in sync
|
||||||
pub network_interface: NodeNetworkInterface,
|
pub network_interface: NodeNetworkInterface,
|
||||||
/// Tracks which layer instances are collapsed in the Layers panel, keyed by instance path.
|
/// Tracks which layer occurrences are collapsed in the Layers panel, keyed by tree path.
|
||||||
#[serde(deserialize_with = "deserialize_collapsed_layers", default)]
|
#[serde(deserialize_with = "deserialize_collapsed_layers", default)]
|
||||||
pub collapsed: CollapsedLayers,
|
pub collapsed: CollapsedLayers,
|
||||||
/// The full Git commit hash of the Graphite repository that was used to build the editor.
|
/// The full Git commit hash of the Graphite repository that was used to build the editor.
|
||||||
@@ -1183,27 +1183,27 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
|
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
|
||||||
}
|
}
|
||||||
DocumentMessage::ToggleLayerExpansion { instance_path, recursive } => {
|
DocumentMessage::ToggleLayerExpansion { tree_path, recursive } => {
|
||||||
let is_collapsed = self.collapsed.0.contains(&instance_path);
|
let is_collapsed = self.collapsed.0.contains(&tree_path);
|
||||||
|
|
||||||
if is_collapsed {
|
if is_collapsed {
|
||||||
if recursive {
|
if recursive {
|
||||||
// Remove this path and all descendant paths (paths that start with this one)
|
// Remove this path and all descendant paths (paths that start with this one)
|
||||||
self.collapsed.0.retain(|path| !path.starts_with(&instance_path));
|
self.collapsed.0.retain(|path| !path.starts_with(&tree_path));
|
||||||
} else {
|
} else {
|
||||||
self.collapsed.0.retain(|path| *path != instance_path);
|
self.collapsed.0.retain(|path| *path != tree_path);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if recursive {
|
if recursive {
|
||||||
// Collapse all expanded descendant instances by collecting their paths from the structure tree
|
// Collapse all expanded descendant occurrences by collecting their tree paths from the structure tree
|
||||||
let descendant_paths = self.collect_descendant_instance_paths(&instance_path);
|
let descendant_paths = self.collect_descendant_tree_paths(&tree_path);
|
||||||
for path in descendant_paths {
|
for path in descendant_paths {
|
||||||
if !self.collapsed.0.contains(&path) {
|
if !self.collapsed.0.contains(&path) {
|
||||||
self.collapsed.0.push(path);
|
self.collapsed.0.push(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.collapsed.0.push(instance_path);
|
self.collapsed.0.push(tree_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
responses.add(NodeGraphMessage::SendGraph);
|
responses.add(NodeGraphMessage::SendGraph);
|
||||||
@@ -1782,11 +1782,11 @@ impl DocumentMessageHandler {
|
|||||||
let selected_layers: HashSet<NodeId> = network.selected_nodes().selected_layers(self.metadata()).map(LayerNodeIdentifier::to_node).collect();
|
let selected_layers: HashSet<NodeId> = network.selected_nodes().selected_layers(self.metadata()).map(LayerNodeIdentifier::to_node).collect();
|
||||||
|
|
||||||
let ancestors = HashSet::new();
|
let ancestors = HashSet::new();
|
||||||
let instance_path = Vec::new();
|
let tree_path = Vec::new();
|
||||||
let mut root_entries = Vec::new();
|
let mut root_entries = Vec::new();
|
||||||
|
|
||||||
// The first root layer is the topmost entry
|
// The first root layer is the topmost entry
|
||||||
root_entries.push(self.build_layer_entry(first_root_layer_id, &ancestors, &selected_layers, &instance_path));
|
root_entries.push(self.build_layer_entry(first_root_layer_id, &ancestors, &selected_layers, &tree_path));
|
||||||
|
|
||||||
// Layers in the primary flow (input[0] chain) from the first root layer are root-level siblings
|
// Layers in the primary flow (input[0] chain) from the first root layer are root-level siblings
|
||||||
let mut root_ancestors = HashSet::new();
|
let mut root_ancestors = HashSet::new();
|
||||||
@@ -1794,7 +1794,7 @@ impl DocumentMessageHandler {
|
|||||||
|
|
||||||
for sibling_id in network.upstream_flow_back_from_nodes(vec![first_root_layer_id], &[], FlowType::PrimaryFlow).skip(1) {
|
for sibling_id in network.upstream_flow_back_from_nodes(vec![first_root_layer_id], &[], FlowType::PrimaryFlow).skip(1) {
|
||||||
if network.is_layer(&sibling_id, &[]) && !root_ancestors.contains(&sibling_id) {
|
if network.is_layer(&sibling_id, &[]) && !root_ancestors.contains(&sibling_id) {
|
||||||
root_entries.push(self.build_layer_entry(sibling_id, &root_ancestors, &selected_layers, &instance_path));
|
root_entries.push(self.build_layer_entry(sibling_id, &root_ancestors, &selected_layers, &tree_path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1803,19 +1803,19 @@ impl DocumentMessageHandler {
|
|||||||
|
|
||||||
/// Builds a single `LayerStructureEntry` for the given layer, including its `children_present` flag,
|
/// Builds a single `LayerStructureEntry` for the given layer, including its `children_present` flag,
|
||||||
/// `descendant_selected` flag, and (if expanded) its children collected from the graph.
|
/// `descendant_selected` flag, and (if expanded) its children collected from the graph.
|
||||||
fn build_layer_entry(&self, layer_id: NodeId, ancestors: &HashSet<NodeId>, selected_layers: &HashSet<NodeId>, parent_instance_path: &[NodeId]) -> LayerStructureEntry {
|
fn build_layer_entry(&self, layer_id: NodeId, ancestors: &HashSet<NodeId>, selected_layers: &HashSet<NodeId>, parent_tree_path: &[NodeId]) -> LayerStructureEntry {
|
||||||
let mut instance_path = parent_instance_path.to_vec();
|
let mut tree_path = parent_tree_path.to_vec();
|
||||||
instance_path.push(layer_id);
|
tree_path.push(layer_id);
|
||||||
|
|
||||||
let mut child_ancestors = ancestors.clone();
|
let mut child_ancestors = ancestors.clone();
|
||||||
child_ancestors.insert(layer_id);
|
child_ancestors.insert(layer_id);
|
||||||
|
|
||||||
let children_present = self.has_layer_children_in_graph(layer_id, &child_ancestors);
|
let children_present = self.has_layer_children_in_graph(layer_id, &child_ancestors);
|
||||||
|
|
||||||
let collapsed = self.collapsed.0.contains(&instance_path);
|
let collapsed = self.collapsed.0.contains(&tree_path);
|
||||||
|
|
||||||
let children = if children_present && !collapsed {
|
let children = if children_present && !collapsed {
|
||||||
self.collect_layer_children(layer_id, &child_ancestors, selected_layers, &instance_path)
|
self.collect_layer_children(layer_id, &child_ancestors, selected_layers, &tree_path)
|
||||||
} else {
|
} else {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
};
|
};
|
||||||
@@ -1896,7 +1896,7 @@ impl DocumentMessageHandler {
|
|||||||
/// The horizontal flow (a layer's secondary input chain) finds nested content layers, and the
|
/// The horizontal flow (a layer's secondary input chain) finds nested content layers, and the
|
||||||
/// primary flow from those (their stack's top output) finds stacked siblings at the same depth.
|
/// primary flow from those (their stack's top output) finds stacked siblings at the same depth.
|
||||||
/// `ancestors` contains layer IDs in the current path from root, used for cycle prevention.
|
/// `ancestors` contains layer IDs in the current path from root, used for cycle prevention.
|
||||||
fn collect_layer_children(&self, layer_id: NodeId, ancestors: &HashSet<NodeId>, selected_layers: &HashSet<NodeId>, instance_path: &[NodeId]) -> Vec<LayerStructureEntry> {
|
fn collect_layer_children(&self, layer_id: NodeId, ancestors: &HashSet<NodeId>, selected_layers: &HashSet<NodeId>, tree_path: &[NodeId]) -> Vec<LayerStructureEntry> {
|
||||||
let network = &self.network_interface;
|
let network = &self.network_interface;
|
||||||
|
|
||||||
// Find the first nested layer via horizontal flow (content inside this layer)
|
// Find the first nested layer via horizontal flow (content inside this layer)
|
||||||
@@ -1914,22 +1914,22 @@ impl DocumentMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The nested layer is the first child at this depth level
|
// The nested layer is the first child at this depth level
|
||||||
let mut children = vec![self.build_layer_entry(nested_id, ancestors, selected_layers, instance_path)];
|
let mut children = vec![self.build_layer_entry(nested_id, ancestors, selected_layers, tree_path)];
|
||||||
|
|
||||||
// Primary flow from the nested layer finds stacked siblings (more children of this layer)
|
// Primary flow from the nested layer finds stacked siblings (more children of this layer)
|
||||||
for sibling_id in network.upstream_flow_back_from_nodes(vec![nested_id], &[], FlowType::PrimaryFlow).skip(1) {
|
for sibling_id in network.upstream_flow_back_from_nodes(vec![nested_id], &[], FlowType::PrimaryFlow).skip(1) {
|
||||||
if network.is_layer(&sibling_id, &[]) && !ancestors.contains(&sibling_id) {
|
if network.is_layer(&sibling_id, &[]) && !ancestors.contains(&sibling_id) {
|
||||||
children.push(self.build_layer_entry(sibling_id, ancestors, selected_layers, instance_path));
|
children.push(self.build_layer_entry(sibling_id, ancestors, selected_layers, tree_path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
children
|
children
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collects instance paths for all descendant layers of the given instance path by traversing the graph.
|
/// Collects tree paths for all descendant layers of the given tree path by traversing the graph.
|
||||||
/// Used for recursive collapse to find all expandable descendants.
|
/// Used for recursive collapse to find all expandable descendants.
|
||||||
fn collect_descendant_instance_paths(&self, instance_path: &[NodeId]) -> Vec<Vec<NodeId>> {
|
fn collect_descendant_tree_paths(&self, tree_path: &[NodeId]) -> Vec<Vec<NodeId>> {
|
||||||
let Some(&layer_id) = instance_path.last() else { return Vec::new() };
|
let Some(&layer_id) = tree_path.last() else { return Vec::new() };
|
||||||
let network = &self.network_interface;
|
let network = &self.network_interface;
|
||||||
|
|
||||||
let mut paths = Vec::new();
|
let mut paths = Vec::new();
|
||||||
@@ -1938,7 +1938,7 @@ impl DocumentMessageHandler {
|
|||||||
// Seed with child layers via horizontal flow
|
// Seed with child layers via horizontal flow
|
||||||
for node_id in network.upstream_flow_back_from_nodes(vec![layer_id], &[], FlowType::HorizontalFlow).skip(1) {
|
for node_id in network.upstream_flow_back_from_nodes(vec![layer_id], &[], FlowType::HorizontalFlow).skip(1) {
|
||||||
if network.is_layer(&node_id, &[]) {
|
if network.is_layer(&node_id, &[]) {
|
||||||
let mut child_path = instance_path.to_vec();
|
let mut child_path = tree_path.to_vec();
|
||||||
child_path.push(node_id);
|
child_path.push(node_id);
|
||||||
stack.push((node_id, child_path));
|
stack.push((node_id, child_path));
|
||||||
}
|
}
|
||||||
@@ -1946,14 +1946,14 @@ impl DocumentMessageHandler {
|
|||||||
|
|
||||||
let mut visited = HashSet::new();
|
let mut visited = HashSet::new();
|
||||||
|
|
||||||
// Depth-first traversal collecting all unique descendant instance paths
|
// Depth-first traversal collecting all unique descendant tree paths
|
||||||
while let Some((current_id, current_path)) = stack.pop() {
|
while let Some((current_id, current_path)) = stack.pop() {
|
||||||
// Skip paths we've already visited to prevent cycles
|
// Skip paths we've already visited to prevent cycles
|
||||||
if !visited.insert(current_path.clone()) {
|
if !visited.insert(current_path.clone()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Record this descendant's instance path for collapsing
|
// Record this descendant's tree path for collapsing
|
||||||
paths.push(current_path.clone());
|
paths.push(current_path.clone());
|
||||||
|
|
||||||
// Add nested content layers found via horizontal flow
|
// Add nested content layers found via horizontal flow
|
||||||
@@ -2218,18 +2218,18 @@ impl DocumentMessageHandler {
|
|||||||
responses.add(NodeGraphMessage::SendGraph);
|
responses.add(NodeGraphMessage::SendGraph);
|
||||||
|
|
||||||
// The control path layer (Blend Path / Morph Path) should start collapsed.
|
// The control path layer (Blend Path / Morph Path) should start collapsed.
|
||||||
let instance_path = {
|
let tree_path = {
|
||||||
// Build instance path from root down to the control path layer, which is a sibling of the main layer under `parent`.
|
// Build tree path from root down to the control path layer, which is a sibling of the main layer under `parent`.
|
||||||
let mut instance_path: Vec<NodeId> = parent
|
let mut tree_path: Vec<NodeId> = parent
|
||||||
.ancestors(network_interface.document_metadata())
|
.ancestors(network_interface.document_metadata())
|
||||||
.take_while(|&ancestor| ancestor != LayerNodeIdentifier::ROOT_PARENT)
|
.take_while(|&ancestor| ancestor != LayerNodeIdentifier::ROOT_PARENT)
|
||||||
.map(LayerNodeIdentifier::to_node)
|
.map(LayerNodeIdentifier::to_node)
|
||||||
.collect();
|
.collect();
|
||||||
instance_path.reverse();
|
tree_path.reverse();
|
||||||
instance_path.push(control_path_id);
|
tree_path.push(control_path_id);
|
||||||
instance_path
|
tree_path
|
||||||
};
|
};
|
||||||
responses.add(DocumentMessage::ToggleLayerExpansion { instance_path, recursive: false });
|
responses.add(DocumentMessage::ToggleLayerExpansion { tree_path, recursive: false });
|
||||||
|
|
||||||
return folder_id;
|
return folder_id;
|
||||||
}
|
}
|
||||||
@@ -2375,7 +2375,7 @@ impl DocumentMessageHandler {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if layer_to_move.parent(self.metadata()) != Some(parent) {
|
if layer_to_move.parent(self.metadata()) != Some(parent) {
|
||||||
// TODO: Fix this so it works when dragging a layer into a group parent which has a Transform node, which used to work before #2689 caused this regression by removing the empty vector table row.
|
// TODO: Fix this so it works when dragging a layer into a group parent which has a Transform node, which used to work before #2689 caused this regression by removing the empty `Table<Vector>` item.
|
||||||
// TODO: See #2688 for this issue.
|
// TODO: See #2688 for this issue.
|
||||||
let layer_local_transform = self.network_interface.document_metadata().transform_to_viewport(layer_to_move);
|
let layer_local_transform = self.network_interface.document_metadata().transform_to_viewport(layer_to_move);
|
||||||
let undo_transform = self.network_interface.document_metadata().transform_to_viewport(parent).inverse();
|
let undo_transform = self.network_interface.document_metadata().transform_to_viewport(parent).inverse();
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@ pub struct GraphOperationMessageContext<'a> {
|
|||||||
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize, ExtractField)]
|
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize, ExtractField)]
|
||||||
pub struct GraphOperationMessageHandler {}
|
pub struct GraphOperationMessageHandler {}
|
||||||
|
|
||||||
// GraphOperationMessageHandler always modified the document network. This is so changes to the layers panel will only affect the document network.
|
// GraphOperationMessageHandler always modified the document network. This is so changes to the Layers panel will only affect the document network.
|
||||||
// For changes to the selected network, use NodeGraphMessageHandler. No NodeGraphMessage's should be added here, since they will affect the selected nested network.
|
// For changes to the selected network, use NodeGraphMessageHandler. No NodeGraphMessage's should be added here, since they will affect the selected nested network.
|
||||||
#[message_handler_data]
|
#[message_handler_data]
|
||||||
impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for GraphOperationMessageHandler {
|
impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for GraphOperationMessageHandler {
|
||||||
|
|||||||
@@ -392,7 +392,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// If inserting a 'Path' node, insert a 'Flatten Path' node if the type is `Graphic`.
|
// If inserting a 'Path' node, insert a 'Flatten Path' node if the type is `Graphic`.
|
||||||
// TODO: Allow the 'Path' node to operate on table data by utilizing the reference (index or ID?) for each row.
|
// TODO: Allow the 'Path' node to operate on `Table` data by utilizing the reference (index or ID?) for each item.
|
||||||
if node_definition.identifier == "Path" {
|
if node_definition.identifier == "Path" {
|
||||||
let layer_input_type = self.network_interface.input_type(&InputConnector::node(output_layer.to_node(), 1), &[]);
|
let layer_input_type = self.network_interface.input_type(&InputConnector::node(output_layer.to_node(), 1), &[]);
|
||||||
if layer_input_type.compiled_nested_type() == Some(&concrete!(Table<Graphic>)) {
|
if layer_input_type.compiled_nested_type() == Some(&concrete!(Table<Graphic>)) {
|
||||||
@@ -604,7 +604,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
self.set_input_with_refresh(InputConnector::node(artboard_node_id, 3), NodeInput::value(TaggedValue::DVec2(dimensions.into()), false), false);
|
self.set_input_with_refresh(InputConnector::node(artboard_node_id, 3), NodeInput::value(TaggedValue::DVec2(dimensions.into()), false), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the input, refresh the properties panel, and run the document graph if skip_rerender is false
|
/// Set the input, refresh the Properties panel, and run the document graph if skip_rerender is false
|
||||||
pub fn set_input_with_refresh(&mut self, input_connector: InputConnector, input: NodeInput, skip_rerender: bool) {
|
pub fn set_input_with_refresh(&mut self, input_connector: InputConnector, input: NodeInput, skip_rerender: bool) {
|
||||||
self.network_interface.set_input(&input_connector, input, &[]);
|
self.network_interface.set_input(&input_connector, input, &[]);
|
||||||
self.responses.add(PropertiesPanelMessage::Refresh);
|
self.responses.add(PropertiesPanelMessage::Refresh);
|
||||||
|
|||||||
@@ -239,7 +239,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::path_of_subgraph::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(graphic::path_of_subgraph::IDENTIFIER),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
// Stamp each row of the content with the parent layer's NodeId via the `editor:layer` attribute,
|
// Stamp each item of the content with the parent layer's NodeId via the `editor:layer` attribute,
|
||||||
// so editor tools (e.g. selection, click target routing) can trace data back to its owning layer.
|
// so editor tools (e.g. selection, click target routing) can trace data back to its owning layer.
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
@@ -343,7 +343,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
description: Cow::Borrowed("Merges the provided content as a new element in the graphic table that represents a layer compositing stack."),
|
description: Cow::Borrowed("Merges the provided content as a new item in the layer's compositing stack."),
|
||||||
properties: None,
|
properties: None,
|
||||||
},
|
},
|
||||||
DocumentNodeDefinition {
|
DocumentNodeDefinition {
|
||||||
@@ -374,7 +374,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::path_of_subgraph::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(graphic::path_of_subgraph::IDENTIFIER),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
// Stamp each row of the content with the parent layer's NodeId via the `editor:layer` attribute,
|
// Stamp each item of the content with the parent layer's NodeId via the `editor:layer` attribute,
|
||||||
// so editor tools (e.g. selection, click target routing) can trace data back to its owning layer.
|
// so editor tools (e.g. selection, click target routing) can trace data back to its owning layer.
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
@@ -697,7 +697,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
// 4: Multiply (total instances)
|
// 4: Multiply (total items)
|
||||||
DocumentNodeMetadata {
|
DocumentNodeMetadata {
|
||||||
persistent_metadata: DocumentNodePersistentMetadata {
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(16, 1)),
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(16, 1)),
|
||||||
@@ -1545,7 +1545,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
exports: vec![
|
exports: vec![
|
||||||
// Primary output: the whole match (String)
|
// Primary output: the whole match (String)
|
||||||
NodeInput::node(NodeId(1), 0),
|
NodeInput::node(NodeId(1), 0),
|
||||||
// Secondary output: capture groups (Table<String>), each row carries `start`/`end`/`name` attributes from `regex_find`
|
// Secondary output: capture groups (Table<String>), each item carries `start`/`end`/`name` attributes from `regex_find`
|
||||||
NodeInput::node(NodeId(2), 0),
|
NodeInput::node(NodeId(2), 0),
|
||||||
],
|
],
|
||||||
nodes: [
|
nodes: [
|
||||||
@@ -1561,13 +1561,13 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
implementation: DocumentNodeImplementation::ProtoNode(text_nodes::regex::regex_find::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(text_nodes::regex::regex_find::IDENTIFIER),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
// Node 1: extract_element at index 0, extracts the whole match as a bare String (drops the row's start/end/name attributes since the unwrapped String can't carry them)
|
// Node 1: extract_element at index 0, extracts the whole match as a bare String (drops the item's start/end/name attributes since the unwrapped String can't carry them)
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::value(TaggedValue::F64(0.), false)],
|
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::value(TaggedValue::F64(0.), false)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::extract_element::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(graphic::extract_element::IDENTIFIER),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
// Node 2: omit_element at index 0, returns the capture group rows as a Table<String>, preserving each row's start/end/name attributes
|
// Node 2: omit_element at index 0, returns the capture group items as a Table<String>, preserving each item's start/end/name attributes
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::value(TaggedValue::F64(0.), false)],
|
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::value(TaggedValue::F64(0.), false)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::omit_element::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(graphic::omit_element::IDENTIFIER),
|
||||||
@@ -1880,7 +1880,7 @@ type NodeProperties = HashMap<String, Box<dyn Fn(NodeId, &mut NodePropertiesCont
|
|||||||
// TODO: Replace with `core::cell::LazyCell` (<https://doc.rust-lang.org/core/cell/struct.LazyCell.html>) or similar
|
// TODO: Replace with `core::cell::LazyCell` (<https://doc.rust-lang.org/core/cell/struct.LazyCell.html>) or similar
|
||||||
pub static NODE_OVERRIDES: once_cell::sync::Lazy<NodeProperties> = once_cell::sync::Lazy::new(static_node_properties);
|
pub static NODE_OVERRIDES: once_cell::sync::Lazy<NodeProperties> = once_cell::sync::Lazy::new(static_node_properties);
|
||||||
|
|
||||||
/// Defines the logic for inputs to display a custom properties panel widget.
|
/// Defines the logic for inputs to display a custom Properties panel widget.
|
||||||
fn static_node_properties() -> NodeProperties {
|
fn static_node_properties() -> NodeProperties {
|
||||||
let mut map: NodeProperties = HashMap::new();
|
let mut map: NodeProperties = HashMap::new();
|
||||||
map.insert("brightness_contrast_properties".to_string(), Box::new(node_properties::brightness_contrast_properties));
|
map.insert("brightness_contrast_properties".to_string(), Box::new(node_properties::brightness_contrast_properties));
|
||||||
@@ -1909,7 +1909,7 @@ type InputProperties = HashMap<String, Box<dyn Fn(NodeId, usize, &mut NodeProper
|
|||||||
// TODO: Replace with `core::cell::LazyCell` (<https://doc.rust-lang.org/core/cell/struct.LazyCell.html>) or similar
|
// TODO: Replace with `core::cell::LazyCell` (<https://doc.rust-lang.org/core/cell/struct.LazyCell.html>) or similar
|
||||||
static INPUT_OVERRIDES: once_cell::sync::Lazy<InputProperties> = once_cell::sync::Lazy::new(static_input_properties);
|
static INPUT_OVERRIDES: once_cell::sync::Lazy<InputProperties> = once_cell::sync::Lazy::new(static_input_properties);
|
||||||
|
|
||||||
/// Defines the logic for inputs to display a custom properties panel widget.
|
/// Defines the logic for inputs to display a custom Properties panel widget.
|
||||||
fn static_input_properties() -> InputProperties {
|
fn static_input_properties() -> InputProperties {
|
||||||
let mut map: InputProperties = HashMap::new();
|
let mut map: InputProperties = HashMap::new();
|
||||||
map.insert("hidden".to_string(), Box::new(|_node_id, _index, _context| Ok(Vec::new())));
|
map.insert("hidden".to_string(), Box::new(|_node_id, _index, _context| Ok(Vec::new())));
|
||||||
|
|||||||
@@ -2402,7 +2402,7 @@ impl NodeGraphMessageHandler {
|
|||||||
self.widgets[1] = LayoutGroup::row(widgets);
|
self.widgets[1] = LayoutGroup::row(widgets);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collate the properties panel sections for a node graph
|
/// Collate the Properties panel sections for a node graph
|
||||||
pub fn collate_properties(context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
pub fn collate_properties(context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
// If the selected nodes are in the document network, use the document network. Otherwise, use the nested network
|
// If the selected nodes are in the document network, use the document network. Otherwise, use the nested network
|
||||||
let Some(selected_nodes) = context.network_interface.selected_nodes_in_nested_network(context.selection_network_path) else {
|
let Some(selected_nodes) = context.network_interface.selected_nodes_in_nested_network(context.selection_network_path) else {
|
||||||
@@ -2468,7 +2468,7 @@ impl NodeGraphMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Display properties for encapsulating node when no nodes are selected in a nested network
|
// TODO: Display properties for encapsulating node when no nodes are selected in a nested network
|
||||||
// This may require store a separate path for the properties panel
|
// This may require store a separate path for the Properties panel
|
||||||
let mut properties = vec![LayoutGroup::row(vec![
|
let mut properties = vec![LayoutGroup::row(vec![
|
||||||
Separator::new(SeparatorStyle::Related).widget_instance(),
|
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||||
IconLabel::new("File").tooltip_description("Name of the current document.").widget_instance(),
|
IconLabel::new("File").tooltip_description("Name of the current document.").widget_instance(),
|
||||||
|
|||||||
@@ -1531,10 +1531,10 @@ pub(crate) fn spiral_properties(node_id: NodeId, context: &mut NodePropertiesCon
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) const SAMPLE_POLYLINE_DESCRIPTION_SPACING: &str = "Use a point sampling density controlled by a distance between, or specific number of, points.";
|
pub(crate) const SAMPLE_POLYLINE_DESCRIPTION_SPACING: &str = "Use a point sampling density controlled by a distance between, or specific number of, points.";
|
||||||
pub(crate) const SAMPLE_POLYLINE_DESCRIPTION_SEPARATION: &str = "Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).";
|
pub(crate) const SAMPLE_POLYLINE_DESCRIPTION_SEPARATION: &str = "Distance between each point (exact if 'Adaptive Spacing' is disabled, approximate if enabled).";
|
||||||
pub(crate) const SAMPLE_POLYLINE_DESCRIPTION_QUANTITY: &str = "Number of points to place along the path.";
|
pub(crate) const SAMPLE_POLYLINE_DESCRIPTION_QUANTITY: &str = "Number of points to place along the path.";
|
||||||
pub(crate) const SAMPLE_POLYLINE_DESCRIPTION_START_OFFSET: &str = "Exclude some distance from the start of the path before the first instance.";
|
pub(crate) const SAMPLE_POLYLINE_DESCRIPTION_START_OFFSET: &str = "Exclude some distance from the start of the path before the first point.";
|
||||||
pub(crate) const SAMPLE_POLYLINE_DESCRIPTION_STOP_OFFSET: &str = "Exclude some distance from the end of the path after the last instance.";
|
pub(crate) const SAMPLE_POLYLINE_DESCRIPTION_STOP_OFFSET: &str = "Exclude some distance from the end of the path after the last point.";
|
||||||
pub(crate) const SAMPLE_POLYLINE_DESCRIPTION_ADAPTIVE_SPACING: &str = "Round 'Separation' to a nearby value that divides into the path length evenly.";
|
pub(crate) const SAMPLE_POLYLINE_DESCRIPTION_ADAPTIVE_SPACING: &str = "Round 'Separation' to a nearby value that divides into the path length evenly.";
|
||||||
|
|
||||||
pub(crate) fn sample_polyline_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
pub(crate) fn sample_polyline_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
|
|||||||
@@ -3284,7 +3284,7 @@ impl NodeNetworkInterface {
|
|||||||
self.document_metadata.local_transforms = local_transforms;
|
self.document_metadata.local_transforms = local_transforms;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the cached first instance source id of the layers
|
/// Update the cached first item's source id of the layers
|
||||||
pub fn update_first_element_source_id(&mut self, new: HashMap<NodeId, Option<NodeId>>) {
|
pub fn update_first_element_source_id(&mut self, new: HashMap<NodeId, Option<NodeId>>) {
|
||||||
self.document_metadata.first_element_source_ids = new;
|
self.document_metadata.first_element_source_ids = new;
|
||||||
}
|
}
|
||||||
@@ -6636,11 +6636,11 @@ pub enum NodeTypeTransientMetadata {
|
|||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct LayerTransientMetadata {
|
pub struct LayerTransientMetadata {
|
||||||
// Stores the width in grid cell units for layer nodes from the left edge of the thumbnail (+12px padding since thumbnail ends between grid spaces) to the left end of the node
|
// Stores the width in grid units for layer nodes from the left edge of the thumbnail (+12px padding since thumbnail ends between grid spaces) to the left end of the node
|
||||||
/// This is necessary since calculating the layer width through web_sys is very slow
|
/// This is necessary since calculating the layer width through web_sys is very slow
|
||||||
pub layer_width: TransientMetadata<u32>,
|
pub layer_width: TransientMetadata<u32>,
|
||||||
// Should not be a performance concern to calculate when needed with chain_width.
|
// Should not be a performance concern to calculate when needed with chain_width.
|
||||||
// Stores the width in grid cell units for layer nodes from the left edge of the thumbnail to the end of the chain
|
// Stores the width in grid units for layer nodes from the left edge of the thumbnail to the end of the chain
|
||||||
// chain_width: u32,
|
// chain_width: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ impl SelectedNodes {
|
|||||||
|
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
|
||||||
/// Tracks which layer instances are collapsed in the Layers panel. Each entry is an "instance path":
|
/// Tracks which layer occurrences are collapsed in the Layers panel. Each entry is a "tree path":
|
||||||
/// the sequence of ancestor node IDs from the root down to the collapsed layer. This allows the same
|
/// the sequence of ancestor node IDs from the root down to the collapsed layer. This allows the same
|
||||||
/// layer appearing under multiple parents to have independent expand/collapse state per instance.
|
/// layer appearing under multiple parents to have independent expand/collapse state per occurrence.
|
||||||
pub struct CollapsedLayers(pub Vec<Vec<NodeId>>);
|
pub struct CollapsedLayers(pub Vec<Vec<NodeId>>);
|
||||||
|
|||||||
@@ -1599,10 +1599,10 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
|||||||
.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::U32(0), false), network_path);
|
.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::U32(0), false), network_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate from the old source/target v1 "Morph" node to the new vector table based v2 "Morph" node.
|
// Migrate from the old source/target v1 "Morph" node to the new `Table<Vector>`-based v2 "Morph" node.
|
||||||
// This doesn't produce exactly equivalent results in cases involving input vector tables with multiple rows.
|
// This doesn't produce exactly equivalent results in cases involving input `Table<Vector>` values with multiple items.
|
||||||
// The old version would zip the source and target table rows, interpoleating each pair together.
|
// The old version would zip the source and target items, interpolating each pair together.
|
||||||
// The migrated version will instead deeply flatten both merged tables and morph sequentially between all source vectors and all target vector elements.
|
// The migrated version will instead deeply flatten both merged `Table`s and morph sequentially between all source vectors and all target vector elements.
|
||||||
// This migration assumes most usages didn't involve multiple parallel vector elements, and instead morphed from a single source to a single target vector element.
|
// This migration assumes most usages didn't involve multiple parallel vector elements, and instead morphed from a single source to a single target vector element.
|
||||||
if reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::morph::IDENTIFIER) && (inputs_count == 3 || inputs_count == 4) {
|
if reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::morph::IDENTIFIER) && (inputs_count == 3 || inputs_count == 4) {
|
||||||
// 3 inputs - old signature (#3405):
|
// 3 inputs - old signature (#3405):
|
||||||
@@ -1669,7 +1669,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
|||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create Count Elements node: counts content table rows → N
|
// Create Count Elements node: counts content `Table` items → N
|
||||||
let Some(count_elements_def) = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::vector::count_elements::IDENTIFIER)) else {
|
let Some(count_elements_def) = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::vector::count_elements::IDENTIFIER)) else {
|
||||||
log::error!("Could not get count_elements node from definition when upgrading morph");
|
log::error!("Could not get count_elements node from definition when upgrading morph");
|
||||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||||
|
|||||||
@@ -417,7 +417,7 @@ pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInter
|
|||||||
let Some(&TaggedValue::TextAlign(align)) = inputs[graphene_std::text::text::AlignInput::INDEX].as_value() else {
|
let Some(&TaggedValue::TextAlign(align)) = inputs[graphene_std::text::text::AlignInput::INDEX].as_value() else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
let Some(&TaggedValue::Bool(per_glyph_instances)) = inputs[graphene_std::text::text::SeparateGlyphElementsInput::INDEX].as_value() else {
|
let Some(&TaggedValue::Bool(per_glyph_items)) = inputs[graphene_std::text::text::SeparateGlyphElementsInput::INDEX].as_value() else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -430,7 +430,7 @@ pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInter
|
|||||||
tilt,
|
tilt,
|
||||||
align,
|
align,
|
||||||
};
|
};
|
||||||
Some((text, font, typesetting, per_glyph_instances))
|
Some((text, font, typesetting, per_glyph_items))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_stroke_width(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<f64> {
|
pub fn get_stroke_width(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<f64> {
|
||||||
|
|||||||
@@ -1281,7 +1281,7 @@ impl ShapeState {
|
|||||||
};
|
};
|
||||||
let delta = delta_transform.inverse().transform_vector2(delta);
|
let delta = delta_transform.inverse().transform_vector2(delta);
|
||||||
|
|
||||||
// Make a new collection of anchor points which needs to be moved
|
// Make a new set of anchor points which needs to be moved
|
||||||
let mut affected_points = state.selected_points.clone();
|
let mut affected_points = state.selected_points.clone();
|
||||||
|
|
||||||
for (segment_id, _, start, end) in vector.segment_bezier_iter() {
|
for (segment_id, _, start, end) in vector.segment_bezier_iter() {
|
||||||
|
|||||||
@@ -64,15 +64,15 @@ where
|
|||||||
|
|
||||||
/// Calculates the bounding box of the layer's text, based on the settings for max width and height specified in the typesetting config.
|
/// Calculates the bounding box of the layer's text, based on the settings for max width and height specified in the typesetting config.
|
||||||
pub fn text_bounding_box(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, font_cache: &FontCache) -> Quad {
|
pub fn text_bounding_box(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, font_cache: &FontCache) -> Quad {
|
||||||
let Some((text, font, typesetting, per_glyph_instances)) = get_text(layer, &document.network_interface) else {
|
let Some((text, font, typesetting, per_glyph_items)) = get_text(layer, &document.network_interface) else {
|
||||||
return Quad::from_box([DVec2::ZERO, DVec2::ZERO]);
|
return Quad::from_box([DVec2::ZERO, DVec2::ZERO]);
|
||||||
};
|
};
|
||||||
|
|
||||||
let far = graphene_std::text::bounding_box(text, font, font_cache, typesetting, false);
|
let far = graphene_std::text::bounding_box(text, font, font_cache, typesetting, false);
|
||||||
|
|
||||||
// TODO: Once the instance tables refactor is complete and per_glyph_instances can be removed (since it'll be the default),
|
// TODO: Once the `Table` refactor is complete and per_glyph_items can be removed (since it'll be the default),
|
||||||
// TODO: remove this because the top of the dashed bounding overlay should no longer be based on the first line's baseline.
|
// TODO: remove this because the top of the dashed bounding overlay should no longer be based on the first line's baseline.
|
||||||
let vertical_offset = if per_glyph_instances {
|
let vertical_offset = if per_glyph_items {
|
||||||
DVec2::NEG_Y * typesetting.font_size * (1. + (typesetting.line_height_ratio - 1.) / 2.)
|
DVec2::NEG_Y * typesetting.font_size * (1. + (typesetting.line_height_ratio - 1.) / 2.)
|
||||||
} else {
|
} else {
|
||||||
DVec2::ZERO
|
DVec2::ZERO
|
||||||
|
|||||||
@@ -450,7 +450,7 @@ mod test_freehand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn verify_path_points(vector_and_transform_list: &[(Vector, DAffine2)], expected_captured_points: &[DVec2], tolerance: f64) -> Result<(), String> {
|
fn verify_path_points(vector_and_transform_list: &[(Vector, DAffine2)], expected_captured_points: &[DVec2], tolerance: f64) -> Result<(), String> {
|
||||||
assert_eq!(vector_and_transform_list.len(), 1, "There should be one row of Vector geometry");
|
assert_eq!(vector_and_transform_list.len(), 1, "There should be one item of Vector geometry");
|
||||||
|
|
||||||
let (vector, transform) = vector_and_transform_list
|
let (vector, transform) = vector_and_transform_list
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
@@ -191,7 +191,7 @@
|
|||||||
const isViewport = placeholder.hasAttribute("data-is-viewport");
|
const isViewport = placeholder.hasAttribute("data-is-viewport");
|
||||||
if (isViewport && canvas.isConnected && canvas.parentElement?.closest("[data-viewport]")) return;
|
if (isViewport && canvas.isConnected && canvas.parentElement?.closest("[data-viewport]")) return;
|
||||||
|
|
||||||
// Clone canvas for repeated instances (layers that appear multiple times)
|
// Clone canvas for repeated occurrences (layers that appear multiple times)
|
||||||
if (!isViewport && canvas.parentElement) {
|
if (!isViewport && canvas.parentElement) {
|
||||||
const newCanvas = window.document.createElement("canvas");
|
const newCanvas = window.document.createElement("canvas");
|
||||||
const context = newCanvas.getContext("2d");
|
const context = newCanvas.getContext("2d");
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
ancestorOfSelected: boolean;
|
ancestorOfSelected: boolean;
|
||||||
parentsVisible: boolean;
|
parentsVisible: boolean;
|
||||||
parentsUnlocked: boolean;
|
parentsUnlocked: boolean;
|
||||||
instancePath: bigint[];
|
treePath: bigint[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type DraggingData = {
|
type DraggingData = {
|
||||||
@@ -140,10 +140,10 @@
|
|||||||
editor.toggleLayerLock(id);
|
editor.toggleLayerLock(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleExpandArrowClickWithModifiers(e: MouseEvent, instancePath: bigint[]) {
|
function handleExpandArrowClickWithModifiers(e: MouseEvent, treePath: bigint[]) {
|
||||||
const accel = operatingSystem() === "Mac" ? e.metaKey : e.ctrlKey;
|
const accel = operatingSystem() === "Mac" ? e.metaKey : e.ctrlKey;
|
||||||
const collapseRecursive = e.altKey || accel;
|
const collapseRecursive = e.altKey || accel;
|
||||||
editor.toggleLayerExpansion(BigUint64Array.from(instancePath), collapseRecursive);
|
editor.toggleLayerExpansion(BigUint64Array.from(treePath), collapseRecursive);
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -514,7 +514,7 @@
|
|||||||
// Build the new layer hierarchy
|
// Build the new layer hierarchy
|
||||||
const recurse = (children: LayerStructureEntry[], depth: number, parentId: bigint | undefined, parentPath: bigint[], parentsVisible: boolean, parentsUnlocked: boolean) => {
|
const recurse = (children: LayerStructureEntry[], depth: number, parentId: bigint | undefined, parentPath: bigint[], parentsVisible: boolean, parentsUnlocked: boolean) => {
|
||||||
children.forEach((item, index) => {
|
children.forEach((item, index) => {
|
||||||
const instancePath = [...parentPath, item.layerId];
|
const treePath = [...parentPath, item.layerId];
|
||||||
const mapping = layerCache.get(String(item.layerId));
|
const mapping = layerCache.get(String(item.layerId));
|
||||||
|
|
||||||
if (mapping) {
|
if (mapping) {
|
||||||
@@ -531,14 +531,14 @@
|
|||||||
ancestorOfSelected: item.descendantSelected,
|
ancestorOfSelected: item.descendantSelected,
|
||||||
parentsVisible,
|
parentsVisible,
|
||||||
parentsUnlocked,
|
parentsUnlocked,
|
||||||
instancePath,
|
treePath,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call self recursively, propagating this layer's visibility/lock state to its children
|
// Call self recursively, propagating this layer's visibility/lock state to its children
|
||||||
const childParentsVisible = parentsVisible && (mapping?.visible ?? true);
|
const childParentsVisible = parentsVisible && (mapping?.visible ?? true);
|
||||||
const childParentsUnlocked = parentsUnlocked && (mapping?.unlocked ?? true);
|
const childParentsUnlocked = parentsUnlocked && (mapping?.unlocked ?? true);
|
||||||
if (item.children.length >= 1) recurse(item.children, depth + 1, item.layerId, instancePath, childParentsVisible, childParentsUnlocked);
|
if (item.children.length >= 1) recurse(item.children, depth + 1, item.layerId, treePath, childParentsVisible, childParentsUnlocked);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
recurse(layerStructure, 1, undefined, [], true, true);
|
recurse(layerStructure, 1, undefined, [], true, true);
|
||||||
@@ -605,7 +605,7 @@
|
|||||||
: "Show this layer's children. (To recursively expand all descendants, perform the shortcut shown.)") +
|
: "Show this layer's children. (To recursively expand all descendants, perform the shortcut shown.)") +
|
||||||
(listing.ancestorOfSelected && !listing.expanded ? "\n\nA selected layer is currently contained within.\n" : "")}
|
(listing.ancestorOfSelected && !listing.expanded ? "\n\nA selected layer is currently contained within.\n" : "")}
|
||||||
data-tooltip-shortcut={$tooltip.altClickShortcut?.shortcut ? JSON.stringify($tooltip.altClickShortcut.shortcut) : undefined}
|
data-tooltip-shortcut={$tooltip.altClickShortcut?.shortcut ? JSON.stringify($tooltip.altClickShortcut.shortcut) : undefined}
|
||||||
on:click={(e) => handleExpandArrowClickWithModifiers(e, listing.instancePath)}
|
on:click={(e) => handleExpandArrowClickWithModifiers(e, listing.treePath)}
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
></button>
|
></button>
|
||||||
{:else}
|
{:else}
|
||||||
|
|||||||
@@ -12,9 +12,9 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{#each widgetData.tableWidgets as row}
|
{#each widgetData.tableWidgets as row}
|
||||||
<tr>
|
<tr>
|
||||||
{#each row as cell}
|
{#each row as widget}
|
||||||
<td colspan={row.length < columns ? columns - row.length + 1 : undefined}>
|
<td colspan={row.length < columns ? columns - row.length + 1 : undefined}>
|
||||||
<WidgetSpan direction="row" widgets={[cell]} {layoutTarget} narrow={true} />
|
<WidgetSpan direction="row" widgets={[widget]} {layoutTarget} narrow={true} />
|
||||||
</td>
|
</td>
|
||||||
{/each}
|
{/each}
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -948,9 +948,9 @@ impl EditorWrapper {
|
|||||||
|
|
||||||
/// Toggle expansions state of a layer from the layer list
|
/// Toggle expansions state of a layer from the layer list
|
||||||
#[wasm_bindgen(js_name = toggleLayerExpansion)]
|
#[wasm_bindgen(js_name = toggleLayerExpansion)]
|
||||||
pub fn toggle_layer_expansion(&self, instance_path: &[u64], recursive: bool) {
|
pub fn toggle_layer_expansion(&self, tree_path: &[u64], recursive: bool) {
|
||||||
let instance_path = instance_path.iter().map(|&id| NodeId(id)).collect();
|
let tree_path = tree_path.iter().map(|&id| NodeId(id)).collect();
|
||||||
let message = DocumentMessage::ToggleLayerExpansion { instance_path, recursive };
|
let message = DocumentMessage::ToggleLayerExpansion { tree_path, recursive };
|
||||||
self.dispatch(message);
|
self.dispatch(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ macro_rules! tagged_value {
|
|||||||
// Tries using the default for the tagged value type. If it not implemented, then uses the default used in document_node_types. If it is not used there, then TaggedValue::None is returned.
|
// Tries using the default for the tagged value type. If it not implemented, then uses the default used in document_node_types. If it is not used there, then TaggedValue::None is returned.
|
||||||
Some(match concrete_type.id? {
|
Some(match concrete_type.id? {
|
||||||
x if x == TypeId::of::<()>() => TaggedValue::None,
|
x if x == TypeId::of::<()>() => TaggedValue::None,
|
||||||
// Table-wrapped types need a single-row default with the element's default, not an empty table
|
// Table-wrapped types need a single-item default with the element's default, not an empty table
|
||||||
x if x == TypeId::of::<Table<Color>>() => TaggedValue::Color(Table::new_from_element(Color::default())),
|
x if x == TypeId::of::<Table<Color>>() => TaggedValue::Color(Table::new_from_element(Color::default())),
|
||||||
x if x == TypeId::of::<Table<GradientStops>>() => TaggedValue::GradientTable(Table::new_from_element(GradientStops::default())),
|
x if x == TypeId::of::<Table<GradientStops>>() => TaggedValue::GradientTable(Table::new_from_element(GradientStops::default())),
|
||||||
$( x if x == TypeId::of::<$ty>() => TaggedValue::$identifier(Default::default()), )*
|
$( x if x == TypeId::of::<$ty>() => TaggedValue::$identifier(Default::default()), )*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//! A collection of utilities for working with HTML canvases.
|
//! Utilities for working with HTML canvases.
|
||||||
//! This library is designed to be used in a WebAssembly context.
|
//! This library is designed to be used in a WebAssembly context.
|
||||||
//! It doesn't expose any functionality when compiled for non-WebAssembly targets
|
//! It doesn't expose any functionality when compiled for non-WebAssembly targets
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ trait AttributeColumn: std::any::Any + Send + Sync {
|
|||||||
fn display_at(&self, index: usize) -> Option<String>;
|
fn display_at(&self, index: usize) -> Option<String>;
|
||||||
|
|
||||||
/// Clones a single value from this column into a boxed scalar attribute value.
|
/// Clones a single value from this column into a boxed scalar attribute value.
|
||||||
fn clone_cell(&self, index: usize) -> Option<Box<dyn AttributeValue>>;
|
fn clone_value(&self, index: usize) -> Option<Box<dyn AttributeValue>>;
|
||||||
|
|
||||||
/// Drains all values out of this column into a Vec of scalar attribute values.
|
/// Drains all values out of this column into a Vec of scalar attribute values.
|
||||||
fn drain(self: Box<Self>) -> Vec<Box<dyn AttributeValue>>;
|
fn drain(self: Box<Self>) -> Vec<Box<dyn AttributeValue>>;
|
||||||
@@ -191,7 +191,7 @@ impl<T: Clone + Send + Sync + Default + Debug + 'static> AttributeColumn for Col
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Clones the value at the given index into a boxed scalar attribute value.
|
/// Clones the value at the given index into a boxed scalar attribute value.
|
||||||
fn clone_cell(&self, index: usize) -> Option<Box<dyn AttributeValue>> {
|
fn clone_value(&self, index: usize) -> Option<Box<dyn AttributeValue>> {
|
||||||
self.0.get(index).map(|v| Box::new(v.clone()) as Box<dyn AttributeValue>)
|
self.0.get(index).map(|v| Box::new(v.clone()) as Box<dyn AttributeValue>)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,8 +346,8 @@ impl AttributeColumns {
|
|||||||
// Push values into existing columns, or a default if the row lacks that attribute
|
// Push values into existing columns, or a default if the row lacks that attribute
|
||||||
for (column_key, column) in &mut self.columns {
|
for (column_key, column) in &mut self.columns {
|
||||||
if let Some(position) = row_entries.iter().position(|(k, _)| k == column_key) {
|
if let Some(position) = row_entries.iter().position(|(k, _)| k == column_key) {
|
||||||
let (_, cell_value) = row_entries.swap_remove(position);
|
let (_, value) = row_entries.swap_remove(position);
|
||||||
column.push(cell_value);
|
column.push(value);
|
||||||
} else {
|
} else {
|
||||||
column.push_default();
|
column.push_default();
|
||||||
}
|
}
|
||||||
@@ -389,8 +389,8 @@ impl AttributeColumns {
|
|||||||
self.len += other_len;
|
self.len += other_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a reference to a cell value at the given index from the column for the given key.
|
/// Gets a reference to the value at the given index from the column for the given key.
|
||||||
fn get_cell<T: 'static>(&self, key: &str, index: usize) -> Option<&T> {
|
fn get_value<T: 'static>(&self, key: &str, index: usize) -> Option<&T> {
|
||||||
self.columns.iter().find_map(|(k, column)| if k == key { column.get_any(index)?.downcast_ref::<T>() } else { None })
|
self.columns.iter().find_map(|(k, column)| if k == key { column.get_any(index)?.downcast_ref::<T>() } else { None })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,28 +415,28 @@ impl AttributeColumns {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a mutable reference to a cell value at the given index, creating the column if it doesn't exist or has the wrong type.
|
/// Gets a mutable reference to the value at the given index, creating the column if it doesn't exist or has the wrong type.
|
||||||
fn get_or_insert_default_cell<T: Clone + Send + Sync + Default + Debug + 'static>(&mut self, key: &str, index: usize) -> &mut T {
|
fn get_or_insert_default_value<T: Clone + Send + Sync + Default + Debug + 'static>(&mut self, key: &str, index: usize) -> &mut T {
|
||||||
let column_position = self.find_or_create_column::<T>(key);
|
let column_position = self.find_or_create_column::<T>(key);
|
||||||
let column = (*self.columns[column_position].1).as_any_mut().downcast_mut::<Column<T>>().unwrap();
|
let column = (*self.columns[column_position].1).as_any_mut().downcast_mut::<Column<T>>().unwrap();
|
||||||
&mut column.0[index]
|
&mut column.0[index]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets a cell value at the given index in the column for the given key.
|
/// Sets the value at the given index in the column for the given key.
|
||||||
/// Creates the column with defaults if it doesn't exist.
|
/// Creates the column with defaults if it doesn't exist.
|
||||||
fn set_cell<T: Clone + Send + Sync + Default + Debug + 'static>(&mut self, key: impl Into<String>, index: usize, value: T) {
|
fn set_value<T: Clone + Send + Sync + Default + Debug + 'static>(&mut self, key: impl Into<String>, index: usize, value: T) {
|
||||||
let key = key.into();
|
let key = key.into();
|
||||||
let column_position = self.find_or_create_column::<T>(&key);
|
let column_position = self.find_or_create_column::<T>(&key);
|
||||||
let column = (*self.columns[column_position].1).as_any_mut().downcast_mut::<Column<T>>().unwrap();
|
let column = (*self.columns[column_position].1).as_any_mut().downcast_mut::<Column<T>>().unwrap();
|
||||||
column.0[index] = value;
|
column.0[index] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a debug-formatted string for a cell at the given index in the column for the given key.
|
/// Returns a debug-formatted string for the value at the given index in the column for the given key.
|
||||||
fn display_cell_value(&self, key: &str, index: usize, overrides: fn(&dyn std::any::Any) -> Option<String>) -> Option<String> {
|
fn display_value(&self, key: &str, index: usize, overrides: fn(&dyn std::any::Any) -> Option<String>) -> Option<String> {
|
||||||
self.columns.iter().find_map(|(k, column)| {
|
self.columns.iter().find_map(|(k, column)| {
|
||||||
if k == key {
|
if k == key {
|
||||||
if let Some(cell) = column.get_any(index)
|
if let Some(value) = column.get_any(index)
|
||||||
&& let Some(text) = overrides(cell)
|
&& let Some(text) = overrides(value)
|
||||||
{
|
{
|
||||||
return Some(text);
|
return Some(text);
|
||||||
}
|
}
|
||||||
@@ -447,8 +447,8 @@ impl AttributeColumns {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a type-erased reference to the cell value at the given index in the column for the given key.
|
/// Returns a type-erased reference to the value at the given index in the column for the given key.
|
||||||
fn get_any_cell(&self, key: &str, index: usize) -> Option<&dyn std::any::Any> {
|
fn get_any_value(&self, key: &str, index: usize) -> Option<&dyn std::any::Any> {
|
||||||
self.columns.iter().find_map(|(k, column)| if k == key { column.get_any(index) } else { None })
|
self.columns.iter().find_map(|(k, column)| if k == key { column.get_any(index) } else { None })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -487,8 +487,8 @@ impl AttributeColumns {
|
|||||||
let mut attributes = AttributeValues::new();
|
let mut attributes = AttributeValues::new();
|
||||||
|
|
||||||
for (key, column) in &self.columns {
|
for (key, column) in &self.columns {
|
||||||
if let Some(cell) = column.clone_cell(index) {
|
if let Some(value) = column.clone_value(index) {
|
||||||
attributes.0.push((key.clone(), cell));
|
attributes.0.push((key.clone(), value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -500,8 +500,8 @@ impl AttributeColumns {
|
|||||||
let mut rows: Vec<AttributeValues> = (0..self.len).map(|_| AttributeValues::new()).collect();
|
let mut rows: Vec<AttributeValues> = (0..self.len).map(|_| AttributeValues::new()).collect();
|
||||||
|
|
||||||
for (key, column) in self.columns {
|
for (key, column) in self.columns {
|
||||||
for (i, cell) in column.drain().into_iter().enumerate() {
|
for (i, value) in column.drain().into_iter().enumerate() {
|
||||||
rows[i].0.push((key.clone(), cell));
|
rows[i].0.push((key.clone(), value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -640,38 +640,38 @@ impl<T> Table<T> {
|
|||||||
|
|
||||||
/// Returns a shared reference to the attribute value at the given row index and key, if it exists and can be downcast to the requested type.
|
/// Returns a shared reference to the attribute value at the given row index and key, if it exists and can be downcast to the requested type.
|
||||||
pub fn attribute<U: 'static>(&self, key: &str, index: usize) -> Option<&U> {
|
pub fn attribute<U: 'static>(&self, key: &str, index: usize) -> Option<&U> {
|
||||||
self.attributes.get_cell(key, index)
|
self.attributes.get_value(key, index)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a clone of the attribute value at the given row index and key, or `U::default()` if absent or of a different type.
|
/// Returns a clone of the attribute value at the given row index and key, or `U::default()` if absent or of a different type.
|
||||||
pub fn attribute_cloned_or_default<U: Clone + Default + 'static>(&self, key: &str, index: usize) -> U {
|
pub fn attribute_cloned_or_default<U: Clone + Default + 'static>(&self, key: &str, index: usize) -> U {
|
||||||
self.attributes.get_cell::<U>(key, index).cloned().unwrap_or_default()
|
self.attributes.get_value::<U>(key, index).cloned().unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a clone of the attribute value at the given row index and key, or the provided default if absent or of a different type.
|
/// Returns a clone of the attribute value at the given row index and key, or the provided default if absent or of a different type.
|
||||||
pub fn attribute_cloned_or<U: Clone + 'static>(&self, key: &str, index: usize, default: U) -> U {
|
pub fn attribute_cloned_or<U: Clone + 'static>(&self, key: &str, index: usize, default: U) -> U {
|
||||||
self.attributes.get_cell::<U>(key, index).cloned().unwrap_or(default)
|
self.attributes.get_value::<U>(key, index).cloned().unwrap_or(default)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the attribute value at the given row index and key, creating the column with defaults if it doesn't exist.
|
/// Sets the attribute value at the given row index and key, creating the column with defaults if it doesn't exist.
|
||||||
pub fn set_attribute<U: Clone + Send + Sync + Default + Debug + 'static>(&mut self, key: impl Into<String>, index: usize, value: U) {
|
pub fn set_attribute<U: Clone + Send + Sync + Default + Debug + 'static>(&mut self, key: impl Into<String>, index: usize, value: U) {
|
||||||
self.attributes.set_cell(key, index, value);
|
self.attributes.set_value(key, index, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs the given closure on a mutable reference to the attribute value at the given row index,
|
/// Runs the given closure on a mutable reference to the attribute value at the given row index,
|
||||||
/// creating the column with defaults if it doesn't exist, and returns the closure's result.
|
/// creating the column with defaults if it doesn't exist, and returns the closure's result.
|
||||||
pub fn with_attribute_mut_or_default<U: Clone + Send + Sync + Default + Debug + 'static, R, F: FnOnce(&mut U) -> R>(&mut self, key: &str, index: usize, f: F) -> R {
|
pub fn with_attribute_mut_or_default<U: Clone + Send + Sync + Default + Debug + 'static, R, F: FnOnce(&mut U) -> R>(&mut self, key: &str, index: usize, f: F) -> R {
|
||||||
f(self.attributes.get_or_insert_default_cell::<U>(key, index))
|
f(self.attributes.get_or_insert_default_value::<U>(key, index))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a debug-formatted display string for the attribute at the given row index and key.
|
/// Returns a debug-formatted display string for the attribute at the given row index and key.
|
||||||
pub fn attribute_display_value(&self, key: &str, index: usize, overrides: fn(&dyn std::any::Any) -> Option<String>) -> Option<String> {
|
pub fn attribute_display_value(&self, key: &str, index: usize, overrides: fn(&dyn std::any::Any) -> Option<String>) -> Option<String> {
|
||||||
self.attributes.display_cell_value(key, index, overrides)
|
self.attributes.display_value(key, index, overrides)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a type-erased reference to the attribute value at the given row index and key, or `None` if absent.
|
/// Returns a type-erased reference to the attribute value at the given row index and key, or `None` if absent.
|
||||||
pub fn attribute_any(&self, key: &str, index: usize) -> Option<&dyn std::any::Any> {
|
pub fn attribute_any(&self, key: &str, index: usize) -> Option<&dyn std::any::Any> {
|
||||||
self.attributes.get_any_cell(key, index)
|
self.attributes.get_any_value(key, index)
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================
|
// =====================
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ pub fn migrate_artboard<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Re
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Attributes (transform, alpha_blending, editor:layer) are not serialized, so migration only needs
|
// Attributes (transform, alpha_blending, editor:layer) are not serialized, so migration only needs
|
||||||
// to recover the elements. Per-row attribute values are populated at runtime by the node graph.
|
// to recover the elements. Per-item attribute values are populated at runtime by the node graph.
|
||||||
Ok(match ArtboardFormat::deserialize(deserializer)? {
|
Ok(match ArtboardFormat::deserialize(deserializer)? {
|
||||||
ArtboardFormat::ArtboardGroup(artboard_group) => artboard_group.artboards.into_iter().map(|(artboard, _)| TableRow::new_from_element(artboard)).collect(),
|
ArtboardFormat::ArtboardGroup(artboard_group) => artboard_group.artboards.into_iter().map(|(artboard, _)| TableRow::new_from_element(artboard)).collect(),
|
||||||
ArtboardFormat::OldArtboardTable(old_table) => old_table.element.into_iter().map(TableRow::new_from_element).collect(),
|
ArtboardFormat::OldArtboardTable(old_table) => old_table.element.into_iter().map(TableRow::new_from_element).collect(),
|
||||||
|
|||||||
@@ -127,8 +127,8 @@ impl From<Table<GradientStops>> for Graphic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deeply flattens a graphic table, collecting only elements matching a specific variant (extracted by `extract_variant`)
|
/// Deeply flattens a `Table<Graphic>`, collecting only elements matching a specific variant (extracted by `extract_variant`)
|
||||||
/// and discarding all other non-matching content. Recursion through `Graphic::Graphic` sub-tables composes transforms and opacity.
|
/// and discarding all other non-matching content. Recursion through `Graphic::Graphic` sub-`Table`s composes transforms and opacity.
|
||||||
fn flatten_graphic_table<T>(content: Table<Graphic>, extract_variant: fn(Graphic) -> Option<Table<T>>) -> Table<T> {
|
fn flatten_graphic_table<T>(content: Table<Graphic>, extract_variant: fn(Graphic) -> Option<Table<T>>) -> Table<T> {
|
||||||
fn compose_alpha_blending(parent: AlphaBlending, child: AlphaBlending) -> AlphaBlending {
|
fn compose_alpha_blending(parent: AlphaBlending, child: AlphaBlending) -> AlphaBlending {
|
||||||
AlphaBlending {
|
AlphaBlending {
|
||||||
@@ -146,7 +146,7 @@ fn flatten_graphic_table<T>(content: Table<Graphic>, extract_variant: fn(Graphic
|
|||||||
let current_alpha_blending: AlphaBlending = current_graphic_row.attribute_cloned_or_default("alpha_blending");
|
let current_alpha_blending: AlphaBlending = current_graphic_row.attribute_cloned_or_default("alpha_blending");
|
||||||
|
|
||||||
match current_graphic_row.into_element() {
|
match current_graphic_row.into_element() {
|
||||||
// Recurse into nested graphic tables, composing the parent's transform onto each child
|
// Recurse into nested `Table<Graphic>` items, composing the parent's transform onto each child
|
||||||
Graphic::Graphic(mut sub_table) => {
|
Graphic::Graphic(mut sub_table) => {
|
||||||
for index in 0..sub_table.len() {
|
for index in 0..sub_table.len() {
|
||||||
let child_transform: DAffine2 = sub_table.attribute_cloned_or_default("transform", index);
|
let child_transform: DAffine2 = sub_table.attribute_cloned_or_default("transform", index);
|
||||||
@@ -158,7 +158,7 @@ fn flatten_graphic_table<T>(content: Table<Graphic>, extract_variant: fn(Graphic
|
|||||||
|
|
||||||
flatten_recursive(output, sub_table, extract_variant);
|
flatten_recursive(output, sub_table, extract_variant);
|
||||||
}
|
}
|
||||||
// Try to extract the target variant; if it matches, push its rows with composed transform and opacity
|
// Try to extract the target variant; if it matches, push its items with composed transform and opacity
|
||||||
other => {
|
other => {
|
||||||
if let Some(typed_table) = extract_variant(other) {
|
if let Some(typed_table) = extract_variant(other) {
|
||||||
for row in typed_table.into_iter() {
|
for row in typed_table.into_iter() {
|
||||||
@@ -184,7 +184,7 @@ fn flatten_graphic_table<T>(content: Table<Graphic>, extract_variant: fn(Graphic
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Maps from a concrete element type to its corresponding `Graphic` enum variant,
|
/// Maps from a concrete element type to its corresponding `Graphic` enum variant,
|
||||||
/// enabling type-directed casting of typed tables from a `Graphic` value.
|
/// enabling type-directed casting of typed `Table`s from a `Graphic` value.
|
||||||
pub trait TryFromGraphic: Clone + Sized {
|
pub trait TryFromGraphic: Clone + Sized {
|
||||||
fn try_from_graphic(graphic: Graphic) -> Option<Table<Self>>;
|
fn try_from_graphic(graphic: Graphic) -> Option<Table<Self>>;
|
||||||
}
|
}
|
||||||
@@ -217,7 +217,7 @@ impl TryFromGraphic for GradientStops {
|
|||||||
pub trait IntoGraphicTable {
|
pub trait IntoGraphicTable {
|
||||||
fn into_graphic_table(self) -> Table<Graphic>;
|
fn into_graphic_table(self) -> Table<Graphic>;
|
||||||
|
|
||||||
/// Deeply flattens any content of type `T` within a graphic table, discarding all other content, and returning a flat table of only `T` elements.
|
/// Deeply flattens any content of type `T` within a `Table<Graphic>`, discarding all other content, and returning a flat `Table<T>`.
|
||||||
fn into_flattened_table<T: TryFromGraphic>(self) -> Table<T>
|
fn into_flattened_table<T: TryFromGraphic>(self) -> Table<T>
|
||||||
where
|
where
|
||||||
Self: std::marker::Sized,
|
Self: std::marker::Sized,
|
||||||
@@ -505,7 +505,7 @@ pub fn migrate_graphic<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Res
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Attributes (transform, alpha_blending, editor:layer) are not serialized, so migration only needs
|
// Attributes (transform, alpha_blending, editor:layer) are not serialized, so migration only needs
|
||||||
// to recover the elements. Per-row attribute values are populated at runtime by the node graph.
|
// to recover the elements. Per-item attribute values are populated at runtime by the node graph.
|
||||||
Ok(match GraphicFormat::deserialize(deserializer)? {
|
Ok(match GraphicFormat::deserialize(deserializer)? {
|
||||||
GraphicFormat::OldGraphicGroup(old) => old.elements.into_iter().map(|(graphic, _)| TableRow::new_from_element(graphic)).collect(),
|
GraphicFormat::OldGraphicGroup(old) => old.elements.into_iter().map(|(graphic, _)| TableRow::new_from_element(graphic)).collect(),
|
||||||
GraphicFormat::OlderTableOldGraphicGroup(old) => old
|
GraphicFormat::OlderTableOldGraphicGroup(old) => old
|
||||||
@@ -524,7 +524,7 @@ pub fn migrate_graphic<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Res
|
|||||||
.flat_map(|element| element.elements.into_iter().map(|(graphic, _)| TableRow::new_from_element(graphic)))
|
.flat_map(|element| element.elements.into_iter().map(|(graphic, _)| TableRow::new_from_element(graphic)))
|
||||||
.collect(),
|
.collect(),
|
||||||
GraphicFormat::Table(value) => {
|
GraphicFormat::Table(value) => {
|
||||||
// Try to deserialize as either table format
|
// Try to deserialize as either `Table` format
|
||||||
if let Ok(old_table) = serde_json::from_value::<Table<GraphicGroup>>(value.clone()) {
|
if let Ok(old_table) = serde_json::from_value::<Table<GraphicGroup>>(value.clone()) {
|
||||||
let mut graphic_table = Table::new();
|
let mut graphic_table = Table::new();
|
||||||
for index in 0..old_table.len() {
|
for index in 0..old_table.len() {
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ pub mod migrations {
|
|||||||
Ok(match VectorFormat::deserialize(deserializer)? {
|
Ok(match VectorFormat::deserialize(deserializer)? {
|
||||||
VectorFormat::Vector(vector) => Table::new_from_element(vector),
|
VectorFormat::Vector(vector) => Table::new_from_element(vector),
|
||||||
// Attributes (transform, alpha_blending, editor:layer) are not serialized, so migration only needs
|
// Attributes (transform, alpha_blending, editor:layer) are not serialized, so migration only needs
|
||||||
// to recover the elements. Per-row attribute values are populated at runtime by the node graph.
|
// to recover the elements. Per-item attribute values are populated at runtime by the node graph.
|
||||||
VectorFormat::OldVectorData(old) => Table::new_from_element(Vector {
|
VectorFormat::OldVectorData(old) => Table::new_from_element(Vector {
|
||||||
style: old.style,
|
style: old.style,
|
||||||
colinear_manipulators: old.colinear_manipulators,
|
colinear_manipulators: old.colinear_manipulators,
|
||||||
|
|||||||
@@ -320,7 +320,7 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Attributes (transform, alpha_blending, editor:layer) are not serialized, so migration only needs
|
// Attributes (transform, alpha_blending, editor:layer) are not serialized, so migration only needs
|
||||||
// to recover the elements. Per-row attribute values are populated at runtime by the node graph.
|
// to recover the elements. Per-item attribute values are populated at runtime by the node graph.
|
||||||
fn old_table_to_new_table<T>(old_table: OldTable<T>) -> Table<T> {
|
fn old_table_to_new_table<T>(old_table: OldTable<T>) -> Table<T> {
|
||||||
old_table.element.into_iter().map(TableRow::new_from_element).collect()
|
old_table.element.into_iter().map(TableRow::new_from_element).collect()
|
||||||
}
|
}
|
||||||
@@ -431,7 +431,7 @@ pub fn migrate_image_frame_row<'de, D: serde::Deserializer<'de>>(deserializer: D
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Attributes (transform, alpha_blending, editor:layer) are not serialized, so migration only needs
|
// Attributes (transform, alpha_blending, editor:layer) are not serialized, so migration only needs
|
||||||
// to recover the element. Per-row attribute values are populated at runtime by the node graph.
|
// to recover the element. Per-item attribute values are populated at runtime by the node graph.
|
||||||
Ok(match FormatVersions::deserialize(deserializer)? {
|
Ok(match FormatVersions::deserialize(deserializer)? {
|
||||||
FormatVersions::Image(image) => TableRow::new_from_element(Raster::new_cpu(image)),
|
FormatVersions::Image(image) => TableRow::new_from_element(Raster::new_cpu(image)),
|
||||||
FormatVersions::OldImageFrame(old) => TableRow::new_from_element(Raster::new_cpu(old.image)),
|
FormatVersions::OldImageFrame(old) => TableRow::new_from_element(Raster::new_cpu(old.image)),
|
||||||
|
|||||||
@@ -410,7 +410,7 @@ impl Render for Graphic {
|
|||||||
}
|
}
|
||||||
Graphic::Vector(table) => {
|
Graphic::Vector(table) => {
|
||||||
metadata.upstream_footprints.insert(element_id, footprint);
|
metadata.upstream_footprints.insert(element_id, footprint);
|
||||||
// TODO: Find a way to handle more than the first row
|
// TODO: Find a way to handle more than the first item
|
||||||
if !table.is_empty() {
|
if !table.is_empty() {
|
||||||
let layer_path: Table<NodeId> = table.attribute_cloned_or_default("editor:layer", 0);
|
let layer_path: Table<NodeId> = table.attribute_cloned_or_default("editor:layer", 0);
|
||||||
let layer = layer_path.iter_element_values().next_back().copied();
|
let layer = layer_path.iter_element_values().next_back().copied();
|
||||||
@@ -423,7 +423,7 @@ impl Render for Graphic {
|
|||||||
Graphic::RasterCPU(table) => {
|
Graphic::RasterCPU(table) => {
|
||||||
metadata.upstream_footprints.insert(element_id, footprint);
|
metadata.upstream_footprints.insert(element_id, footprint);
|
||||||
|
|
||||||
// TODO: Find a way to handle more than the first row
|
// TODO: Find a way to handle more than the first item
|
||||||
if !table.is_empty() {
|
if !table.is_empty() {
|
||||||
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default("transform", 0));
|
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default("transform", 0));
|
||||||
}
|
}
|
||||||
@@ -431,7 +431,7 @@ impl Render for Graphic {
|
|||||||
Graphic::RasterGPU(table) => {
|
Graphic::RasterGPU(table) => {
|
||||||
metadata.upstream_footprints.insert(element_id, footprint);
|
metadata.upstream_footprints.insert(element_id, footprint);
|
||||||
|
|
||||||
// TODO: Find a way to handle more than the first row
|
// TODO: Find a way to handle more than the first item
|
||||||
if !table.is_empty() {
|
if !table.is_empty() {
|
||||||
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default("transform", 0));
|
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default("transform", 0));
|
||||||
}
|
}
|
||||||
@@ -439,7 +439,7 @@ impl Render for Graphic {
|
|||||||
Graphic::Color(table) => {
|
Graphic::Color(table) => {
|
||||||
metadata.upstream_footprints.insert(element_id, footprint);
|
metadata.upstream_footprints.insert(element_id, footprint);
|
||||||
|
|
||||||
// TODO: Find a way to handle more than the first row
|
// TODO: Find a way to handle more than the first item
|
||||||
if !table.is_empty() {
|
if !table.is_empty() {
|
||||||
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default("transform", 0));
|
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default("transform", 0));
|
||||||
}
|
}
|
||||||
@@ -447,7 +447,7 @@ impl Render for Graphic {
|
|||||||
Graphic::Gradient(table) => {
|
Graphic::Gradient(table) => {
|
||||||
metadata.upstream_footprints.insert(element_id, footprint);
|
metadata.upstream_footprints.insert(element_id, footprint);
|
||||||
|
|
||||||
// TODO: Find a way to handle more than the first row
|
// TODO: Find a way to handle more than the first item
|
||||||
if !table.is_empty() {
|
if !table.is_empty() {
|
||||||
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default("transform", 0));
|
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default("transform", 0));
|
||||||
}
|
}
|
||||||
@@ -817,7 +817,7 @@ impl Render for Table<Graphic> {
|
|||||||
if let Some(element_id) = layer {
|
if let Some(element_id) = layer {
|
||||||
element.collect_metadata(metadata, footprint, Some(element_id));
|
element.collect_metadata(metadata, footprint, Some(element_id));
|
||||||
} else {
|
} else {
|
||||||
// Recurse through anonymous wrapper rows to reach nested content with editor:layer tags
|
// Recurse through anonymous wrapper items to reach nested content with editor:layer tags
|
||||||
element.collect_metadata(metadata, footprint, None);
|
element.collect_metadata(metadata, footprint, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1334,7 +1334,7 @@ impl Render for Table<Vector> {
|
|||||||
let layer = layer_path.iter_element_values().next_back().copied();
|
let layer = layer_path.iter_element_values().next_back().copied();
|
||||||
|
|
||||||
if let Some(element_id) = caller_element_id.or(layer) {
|
if let Some(element_id) = caller_element_id.or(layer) {
|
||||||
// When recovering element_id from the row's editor:layer tag (because the caller
|
// When recovering element_id from the item's editor:layer tag (because the caller
|
||||||
// passed None), also store the transform metadata that Graphic::collect_metadata
|
// passed None), also store the transform metadata that Graphic::collect_metadata
|
||||||
// normally provides but skipped due to the None element_id.
|
// normally provides but skipped due to the None element_id.
|
||||||
if caller_element_id.is_none() {
|
if caller_element_id.is_none() {
|
||||||
@@ -1375,7 +1375,7 @@ impl Render for Table<Vector> {
|
|||||||
metadata.vector_data.entry(element_id).or_insert_with(|| Arc::new(vector.clone()));
|
metadata.vector_data.entry(element_id).or_insert_with(|| Arc::new(vector.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this row carries a snapshot of upstream graphic content (e.g. it was produced by Boolean Operation,
|
// If this item carries a snapshot of upstream graphic content (e.g. it was produced by Boolean Operation,
|
||||||
// Flatten Path, Morph, or any other destructive merge), recurse into that snapshot so the editor can
|
// Flatten Path, Morph, or any other destructive merge), recurse into that snapshot so the editor can
|
||||||
// surface the original child layers' click targets.
|
// surface the original child layers' click targets.
|
||||||
let upstream_nested_layers = self.attribute_cloned_or_default::<Table<Graphic>>("editor:merged_layers", index);
|
let upstream_nested_layers = self.attribute_cloned_or_default::<Table<Graphic>>("editor:merged_layers", index);
|
||||||
@@ -1575,7 +1575,7 @@ impl Render for Table<Raster<CPU>> {
|
|||||||
|
|
||||||
metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.).into()]);
|
metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.).into()]);
|
||||||
metadata.upstream_footprints.insert(element_id, footprint);
|
metadata.upstream_footprints.insert(element_id, footprint);
|
||||||
// TODO: Find a way to handle more than one row of the raster table
|
// TODO: Find a way to handle more than one item of the `Table<Raster<...>>`
|
||||||
if !self.is_empty() {
|
if !self.is_empty() {
|
||||||
let transform: DAffine2 = self.attribute_cloned_or_default("transform", 0);
|
let transform: DAffine2 = self.attribute_cloned_or_default("transform", 0);
|
||||||
metadata.local_transforms.insert(element_id, transform);
|
metadata.local_transforms.insert(element_id, transform);
|
||||||
@@ -1665,7 +1665,7 @@ impl Render for Table<Raster<GPU>> {
|
|||||||
|
|
||||||
metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.).into()]);
|
metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.).into()]);
|
||||||
metadata.upstream_footprints.insert(element_id, footprint);
|
metadata.upstream_footprints.insert(element_id, footprint);
|
||||||
// TODO: Find a way to handle more than one row of the raster table
|
// TODO: Find a way to handle more than one item of the `Table<Raster<...>>`
|
||||||
if !self.is_empty() {
|
if !self.is_empty() {
|
||||||
let transform: DAffine2 = self.attribute_cloned_or_default("transform", 0);
|
let transform: DAffine2 = self.attribute_cloned_or_default("transform", 0);
|
||||||
metadata.local_transforms.insert(element_id, transform);
|
metadata.local_transforms.insert(element_id, transform);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ impl<PointId: Identifier> Subpath<PointId> {
|
|||||||
let mut intersections_vec = Vec::new();
|
let mut intersections_vec = Vec::new();
|
||||||
let err = accuracy.unwrap_or(MAX_ABSOLUTE_DIFFERENCE);
|
let err = accuracy.unwrap_or(MAX_ABSOLUTE_DIFFERENCE);
|
||||||
let num_curves = self.len();
|
let num_curves = self.len();
|
||||||
// TODO: optimization opportunity - this for-loop currently compares all intersections with all curve-segments in the subpath collection
|
// TODO: optimization opportunity - this for-loop currently compares all intersections with all curve-segments in the subpath list
|
||||||
self.iter_closed().enumerate().for_each(|(i, other)| {
|
self.iter_closed().enumerate().for_each(|(i, other)| {
|
||||||
intersections_vec.extend(pathseg_self_intersections(other, accuracy, minimum_separation).iter().flat_map(|value| [(i, value.0), (i, value.1)]));
|
intersections_vec.extend(pathseg_self_intersections(other, accuracy, minimum_separation).iter().flat_map(|value| [(i, value.0), (i, value.1)]));
|
||||||
self.iter_closed().enumerate().skip(i + 1).for_each(|(j, curve)| {
|
self.iter_closed().enumerate().skip(i + 1).for_each(|(j, curve)| {
|
||||||
|
|||||||
@@ -137,14 +137,14 @@ impl RasterGpuToRasterCpuConverter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Passthrough conversion for GPU tables - no conversion needed
|
/// Passthrough conversion for GPU `Table`s - no conversion needed
|
||||||
impl<'i> Convert<Table<Raster<GPU>>, &'i WgpuExecutor> for Table<Raster<GPU>> {
|
impl<'i> Convert<Table<Raster<GPU>>, &'i WgpuExecutor> for Table<Raster<GPU>> {
|
||||||
async fn convert(self, _: Footprint, _converter: &'i WgpuExecutor) -> Table<Raster<GPU>> {
|
async fn convert(self, _: Footprint, _converter: &'i WgpuExecutor) -> Table<Raster<GPU>> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts CPU raster table to GPU by uploading each image to a texture
|
/// Converts a `Table<Raster<CPU>>` to `Table<Raster<GPU>>` by uploading each image to a texture
|
||||||
impl<'i> Convert<Table<Raster<GPU>>, &'i WgpuExecutor> for Table<Raster<CPU>> {
|
impl<'i> Convert<Table<Raster<GPU>>, &'i WgpuExecutor> for Table<Raster<CPU>> {
|
||||||
async fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> Table<Raster<GPU>> {
|
async fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> Table<Raster<GPU>> {
|
||||||
let device = &executor.context.device;
|
let device = &executor.context.device;
|
||||||
@@ -176,16 +176,14 @@ impl<'i> Convert<Raster<GPU>, &'i WgpuExecutor> for Raster<CPU> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Passthrough conversion for CPU tables - no conversion needed
|
/// Passthrough conversion for CPU `Table`s - no conversion needed
|
||||||
impl<'i> Convert<Table<Raster<CPU>>, &'i WgpuExecutor> for Table<Raster<CPU>> {
|
impl<'i> Convert<Table<Raster<CPU>>, &'i WgpuExecutor> for Table<Raster<CPU>> {
|
||||||
async fn convert(self, _: Footprint, _converter: &'i WgpuExecutor) -> Table<Raster<CPU>> {
|
async fn convert(self, _: Footprint, _converter: &'i WgpuExecutor) -> Table<Raster<CPU>> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts GPU raster table to CPU by downloading texture data in one go
|
/// Converts a `Table<Raster<GPU>>` to `Table<Raster<CPU>>` by downloading texture data in one go then asynchronously maps all buffers and processes the results.
|
||||||
///
|
|
||||||
/// then asynchronously maps all buffers and processes the results.
|
|
||||||
impl<'i> Convert<Table<Raster<CPU>>, &'i WgpuExecutor> for Table<Raster<GPU>> {
|
impl<'i> Convert<Table<Raster<CPU>>, &'i WgpuExecutor> for Table<Raster<GPU>> {
|
||||||
async fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> Table<Raster<CPU>> {
|
async fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> Table<Raster<CPU>> {
|
||||||
let device = &executor.context.device;
|
let device = &executor.context.device;
|
||||||
@@ -218,7 +216,7 @@ impl<'i> Convert<Table<Raster<CPU>>, &'i WgpuExecutor> for Table<Raster<GPU>> {
|
|||||||
|
|
||||||
map_results
|
map_results
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(rows_meta.into_iter())
|
.zip(rows_meta)
|
||||||
.map(|(element, row)| {
|
.map(|(element, row)| {
|
||||||
let (_, attributes) = row.into_parts();
|
let (_, attributes) = row.into_parts();
|
||||||
TableRow::from_parts(element, attributes)
|
TableRow::from_parts(element, attributes)
|
||||||
@@ -247,7 +245,7 @@ impl<'i> Convert<Raster<CPU>, &'i WgpuExecutor> for Raster<GPU> {
|
|||||||
|
|
||||||
/// Uploads an raster texture from the CPU to the GPU. This is now deprecated and the Convert node should be used in the future.
|
/// Uploads an raster texture from the CPU to the GPU. This is now deprecated and the Convert node should be used in the future.
|
||||||
///
|
///
|
||||||
/// Accepts either individual raster data or a table of raster elements and converts it to the GPU format using the WgpuExecutor's device and queue.
|
/// Accepts either individual raster data or a `Table` of raster elements and converts it to the GPU format using the WgpuExecutor's device and queue.
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
pub async fn upload_texture<'a: 'n, T: Convert<Table<Raster<GPU>>, &'a WgpuExecutor>>(
|
pub async fn upload_texture<'a: 'n, T: Convert<Table<Raster<GPU>>, &'a WgpuExecutor>>(
|
||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ fn blend_mode<T: SetBlendMode>(
|
|||||||
/// The choice of equation that controls how brightness and color blends between overlapping pixels.
|
/// The choice of equation that controls how brightness and color blends between overlapping pixels.
|
||||||
blend_mode: BlendMode,
|
blend_mode: BlendMode,
|
||||||
) -> T {
|
) -> T {
|
||||||
// TODO: Find a way to make this apply once to the table's parent (i.e. its row in its parent table or TableRow<T>) rather than applying to each row in its own table, which produces the undesired result
|
// TODO: Find a way to make this apply once to the table's parent (i.e. its item in its parent table or TableRow<T>) rather than applying to each item in its own table, which produces the undesired result
|
||||||
content.set_blend_mode(blend_mode);
|
content.set_blend_mode(blend_mode);
|
||||||
content
|
content
|
||||||
}
|
}
|
||||||
@@ -216,7 +216,7 @@ fn opacity<T: MultiplyAlpha>(
|
|||||||
#[default(100.)]
|
#[default(100.)]
|
||||||
opacity: Percentage,
|
opacity: Percentage,
|
||||||
) -> T {
|
) -> T {
|
||||||
// TODO: Find a way to make this apply once to the table's parent (i.e. its row in its parent table or TableRow<T>) rather than applying to each row in its own table, which produces the undesired result
|
// TODO: Find a way to make this apply once to the table's parent (i.e. its item in its parent table or TableRow<T>) rather than applying to each item in its own table, which produces the undesired result
|
||||||
content.multiply_alpha(opacity / 100.);
|
content.multiply_alpha(opacity / 100.);
|
||||||
content
|
content
|
||||||
}
|
}
|
||||||
@@ -247,7 +247,7 @@ fn blending<T: SetBlendMode + MultiplyAlpha + MultiplyFill + SetClip>(
|
|||||||
/// Whether the content inherits the alpha of the content beneath it.
|
/// Whether the content inherits the alpha of the content beneath it.
|
||||||
clip: bool,
|
clip: bool,
|
||||||
) -> T {
|
) -> T {
|
||||||
// TODO: Find a way to make this apply once to the table's parent (i.e. its row in its parent table or TableRow<T>) rather than applying to each row in its own table, which produces the undesired result
|
// TODO: Find a way to make this apply once to the table's parent (i.e. its item in its parent table or TableRow<T>) rather than applying to each item in its own table, which produces the undesired result
|
||||||
content.set_blend_mode(blend_mode);
|
content.set_blend_mode(blend_mode);
|
||||||
content.multiply_alpha(opacity / 100.);
|
content.multiply_alpha(opacity / 100.);
|
||||||
content.multiply_fill(fill / 100.);
|
content.multiply_fill(fill / 100.);
|
||||||
|
|||||||
@@ -199,8 +199,8 @@ async fn brush(
|
|||||||
if image.is_empty() {
|
if image.is_empty() {
|
||||||
image.push(TableRow::default());
|
image.push(TableRow::default());
|
||||||
}
|
}
|
||||||
// TODO: Find a way to handle more than one row
|
// TODO: Find a way to handle more than one item
|
||||||
let table_row = image.clone_row(0).expect("Expected the one row we just pushed");
|
let table_row = image.clone_row(0).expect("Expected the one item we just pushed");
|
||||||
|
|
||||||
let bounds = Table::new_from_row(table_row.clone()).bounding_box(DAffine2::IDENTITY, false);
|
let bounds = Table::new_from_row(table_row.clone()).bounding_box(DAffine2::IDENTITY, false);
|
||||||
let [start, end] = if let RenderBoundingBox::Rectangle(rect) = bounds { rect } else { [DVec2::ZERO, DVec2::ZERO] };
|
let [start, end] = if let RenderBoundingBox::Rectangle(rect) = bounds { rect } else { [DVec2::ZERO, DVec2::ZERO] };
|
||||||
@@ -217,7 +217,7 @@ async fn brush(
|
|||||||
|
|
||||||
let mut brush_plan = cache.compute_brush_plan(table_row, &draw_strokes);
|
let mut brush_plan = cache.compute_brush_plan(table_row, &draw_strokes);
|
||||||
|
|
||||||
// TODO: Find a way to handle more than one row
|
// TODO: Find a way to handle more than one item
|
||||||
let Some(mut actual_image) = extend_image_to_bounds((), Table::new_from_row(brush_plan.background), background_bounds).into_iter().next() else {
|
let Some(mut actual_image) = extend_image_to_bounds((), Table::new_from_row(brush_plan.background), background_bounds).into_iter().next() else {
|
||||||
return Table::new();
|
return Table::new();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ impl BrushCacheImpl {
|
|||||||
background = std::mem::take(&mut self.blended_image);
|
background = std::mem::take(&mut self.blended_image);
|
||||||
|
|
||||||
// Check if the first non-blended stroke is an extension of the last one.
|
// Check if the first non-blended stroke is an extension of the last one.
|
||||||
// Transform is set to ZERO (not the default IDENTITY) as a sentinel to mark this row as uninitialized.
|
// Transform is set to ZERO (not the default IDENTITY) as a sentinel to mark this item as uninitialized.
|
||||||
let mut first_stroke_texture = TableRow::new_from_element(Raster::<CPU>::default()).with_attribute("transform", glam::DAffine2::ZERO);
|
let mut first_stroke_texture = TableRow::new_from_element(Raster::<CPU>::default()).with_attribute("transform", glam::DAffine2::ZERO);
|
||||||
let mut first_stroke_point_skip = 0;
|
let mut first_stroke_point_skip = 0;
|
||||||
let strokes = input[num_blended_strokes..].to_vec();
|
let strokes = input[num_blended_strokes..].to_vec();
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ async fn read_position(
|
|||||||
|
|
||||||
// TODO: Return u32, u64, or usize instead of f64 after #1621 is resolved and has allowed us to implement automatic type conversion in the node graph for nodes with generic type inputs.
|
// TODO: Return u32, u64, or usize instead of f64 after #1621 is resolved and has allowed us to implement automatic type conversion in the node graph for nodes with generic type inputs.
|
||||||
// TODO: (Currently automatic type conversion only works for concrete types, via the Graphene preprocessor and not the full Graphene type system.)
|
// TODO: (Currently automatic type conversion only works for concrete types, via the Graphene preprocessor and not the full Graphene type system.)
|
||||||
/// Produces the index of the current iteration of a loop by reading from the evaluation context, which is supplied by downstream nodes such as *Instance Repeat*.
|
/// Produces the index of the current iteration of a loop by reading from the evaluation context, which is supplied by downstream nodes such as *Repeat*.
|
||||||
///
|
///
|
||||||
/// Nested loops can enable 2D or higher-dimensional iteration by using the *Loop Level* parameter to read the index from outer levels of loops.
|
/// Nested loops can enable 2D or higher-dimensional iteration by using the *Loop Level* parameter to read the index from outer levels of loops.
|
||||||
#[node_macro::node(category("Context"), path(core_types::vector))]
|
#[node_macro::node(category("Context"), path(core_types::vector))]
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use graphic_types::{
|
|||||||
use raster_types::{CPU, GPU, Raster};
|
use raster_types::{CPU, GPU, Raster};
|
||||||
use vector_types::GradientStops;
|
use vector_types::GradientStops;
|
||||||
|
|
||||||
/// Constructs a new single artboard table with the chosen properties.
|
/// Constructs a new single-item `Table<Artboard>` with the chosen properties.
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
pub async fn create_artboard<T: IntoGraphicTable + 'n>(
|
pub async fn create_artboard<T: IntoGraphicTable + 'n>(
|
||||||
ctx: impl ExtractAll + CloneVarArgs + Ctx,
|
ctx: impl ExtractAll + CloneVarArgs + Ctx,
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ use graphic_types::{Artboard, Vector};
|
|||||||
use raster_types::{CPU, GPU, Raster};
|
use raster_types::{CPU, GPU, Raster};
|
||||||
use vector_types::{GradientStop, GradientStops, ReferencePoint};
|
use vector_types::{GradientStop, GradientStops, ReferencePoint};
|
||||||
|
|
||||||
/// Returns the value at the specified index in the collection.
|
/// Returns the value at the specified index in the list.
|
||||||
/// If no value exists at that index, the type's default value is returned.
|
/// If no value exists at that index, the type's default value is returned.
|
||||||
#[node_macro::node(category("General"))]
|
#[node_macro::node(category("General"))]
|
||||||
pub fn index_elements<T: graphic_types::graphic::AtIndex + Clone + Default>(
|
pub fn index_elements<T: graphic_types::graphic::AtIndex + Clone + Default>(
|
||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
/// The collection of data, such as a list or table.
|
/// The list of data.
|
||||||
#[implementations(
|
#[implementations(
|
||||||
Table<Artboard>,
|
Table<Artboard>,
|
||||||
Table<Graphic>,
|
Table<Graphic>,
|
||||||
@@ -28,8 +28,8 @@ pub fn index_elements<T: graphic_types::graphic::AtIndex + Clone + Default>(
|
|||||||
Table<u8>,
|
Table<u8>,
|
||||||
Table<NodeId>,
|
Table<NodeId>,
|
||||||
)]
|
)]
|
||||||
collection: T,
|
list: T,
|
||||||
/// 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.
|
/// The index of the item to retrieve, starting from 0 for the first item. Negative indices count backwards from the end of the list, starting from -1 for the last item.
|
||||||
index: SignedInteger,
|
index: SignedInteger,
|
||||||
) -> T::Output
|
) -> T::Output
|
||||||
where
|
where
|
||||||
@@ -37,20 +37,15 @@ where
|
|||||||
{
|
{
|
||||||
let index = index as i32;
|
let index = index as i32;
|
||||||
|
|
||||||
if index < 0 {
|
if index < 0 { list.at_index_from_end(-index as usize) } else { list.at_index(index as usize) }.unwrap_or_default()
|
||||||
collection.at_index_from_end(-index as usize)
|
|
||||||
} else {
|
|
||||||
collection.at_index(index as usize)
|
|
||||||
}
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the collection with the element at the specified index removed.
|
/// Returns the list with the element at the specified index removed.
|
||||||
/// If no value exists at that index, the collection is returned unchanged.
|
/// If no value exists at that index, the list is returned unchanged.
|
||||||
#[node_macro::node(category("General"))]
|
#[node_macro::node(category("General"))]
|
||||||
pub fn omit_element<T: graphic_types::graphic::OmitIndex + Clone + Default>(
|
pub fn omit_element<T: graphic_types::graphic::OmitIndex + Clone + Default>(
|
||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
/// The collection of data, such as a list or table.
|
/// The list of data.
|
||||||
#[implementations(
|
#[implementations(
|
||||||
Table<String>,
|
Table<String>,
|
||||||
Table<Artboard>,
|
Table<Artboard>,
|
||||||
@@ -61,26 +56,26 @@ pub fn omit_element<T: graphic_types::graphic::OmitIndex + Clone + Default>(
|
|||||||
Table<Color>,
|
Table<Color>,
|
||||||
Table<GradientStops>,
|
Table<GradientStops>,
|
||||||
)]
|
)]
|
||||||
collection: T,
|
list: T,
|
||||||
/// The index of the item to remove, starting from 0 for the first item. Negative indices count backwards from the end of the collection, starting from -1 for the last item.
|
/// The index of the item to remove, starting from 0 for the first item. Negative indices count backwards from the end of the list, starting from -1 for the last item.
|
||||||
index: SignedInteger,
|
index: SignedInteger,
|
||||||
) -> T {
|
) -> T {
|
||||||
let index = index as i32;
|
let index = index as i32;
|
||||||
|
|
||||||
if index < 0 {
|
if index < 0 {
|
||||||
collection.omit_index_from_end(index.unsigned_abs() as usize)
|
list.omit_index_from_end(index.unsigned_abs() as usize)
|
||||||
} else {
|
} else {
|
||||||
collection.omit_index(index as usize)
|
list.omit_index(index as usize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the bare element (without its row attributes) at the specified index in a table.
|
/// Returns the bare element (without the item's attributes) at the specified index in a `Table`.
|
||||||
/// Use this when downstream nodes want just the inner value rather than a single-row table.
|
/// Use this when downstream nodes want just the inner value rather than a `Table` containing a single item.
|
||||||
/// If no value exists at that index, the element type's default is returned.
|
/// If no value exists at that index, the element type's default is returned.
|
||||||
#[node_macro::node(category("General"))]
|
#[node_macro::node(category("General"))]
|
||||||
pub fn extract_element<T: Clone + Default + Send + Sync + 'static>(
|
pub fn extract_element<T: Clone + Default + Send + Sync + 'static>(
|
||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
/// The table of data to extract from.
|
/// The `Table` of data to extract from.
|
||||||
#[implementations(
|
#[implementations(
|
||||||
Table<String>,
|
Table<String>,
|
||||||
Table<f64>,
|
Table<f64>,
|
||||||
@@ -94,7 +89,7 @@ pub fn extract_element<T: Clone + Default + Send + Sync + 'static>(
|
|||||||
Table<Artboard>,
|
Table<Artboard>,
|
||||||
)]
|
)]
|
||||||
table: Table<T>,
|
table: Table<T>,
|
||||||
/// 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.
|
/// The index of the item to retrieve, starting from 0 for the first item. Negative indices count backwards from the end of the list, starting from -1 for the last item.
|
||||||
index: SignedInteger,
|
index: SignedInteger,
|
||||||
) -> T {
|
) -> T {
|
||||||
let len = table.len();
|
let len = table.len();
|
||||||
@@ -192,14 +187,14 @@ where
|
|||||||
|
|
||||||
let mut result_table = Table::new();
|
let mut result_table = Table::new();
|
||||||
|
|
||||||
// Add original instance depending on the keep_original flag
|
// Add original items depending on the keep_original flag
|
||||||
if keep_original {
|
if keep_original {
|
||||||
for instance in content.clone().into_iter() {
|
for item in content.clone().into_iter() {
|
||||||
result_table.push(instance);
|
result_table.push(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and add mirrored instance
|
// Create and add mirrored items
|
||||||
for mut row in content.into_iter() {
|
for mut row in content.into_iter() {
|
||||||
let current_transform: DAffine2 = row.attribute_cloned_or_default("transform");
|
let current_transform: DAffine2 = row.attribute_cloned_or_default("transform");
|
||||||
row.set_attribute("transform", reflected_transform * current_transform);
|
row.set_attribute("transform", reflected_transform * current_transform);
|
||||||
@@ -212,7 +207,7 @@ where
|
|||||||
/// Returns the path identifying the subgraph (network) that contains this proto node — i.e. the input `node_path`
|
/// Returns the path identifying the subgraph (network) that contains this proto node — i.e. the input `node_path`
|
||||||
/// with its own trailing entry dropped. The terminating element of the returned path is the document node whose
|
/// with its own trailing entry dropped. The terminating element of the returned path is the document node whose
|
||||||
/// encapsulated network we live in, so the path doubles as a unique reference to that node at any nesting depth.
|
/// encapsulated network we live in, so the path doubles as a unique reference to that node at any nesting depth.
|
||||||
/// Used as the value source for stamping the `editor:layer` attribute on each row of a layer's output, which lets
|
/// Used as the value source for stamping the `editor:layer` attribute on each item of a layer's output, which lets
|
||||||
/// editor tools (e.g. selection, click target routing) trace data back to its owning layer regardless of whether
|
/// editor tools (e.g. selection, click target routing) trace data back to its owning layer regardless of whether
|
||||||
/// the layer is at the root document network or nested inside a custom subgraph.
|
/// the layer is at the root document network or nested inside a custom subgraph.
|
||||||
#[node_macro::node(name("Path of Subgraph"), category(""))]
|
#[node_macro::node(name("Path of Subgraph"), category(""))]
|
||||||
@@ -221,14 +216,14 @@ pub fn path_of_subgraph(_: impl Ctx, node_path: Table<NodeId>) -> Table<NodeId>
|
|||||||
node_path.into_iter().take(len.saturating_sub(1)).collect()
|
node_path.into_iter().take(len.saturating_sub(1)).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes a per-row attribute column on the input table. The value-producing input is evaluated once per row,
|
/// Writes a named attribute on each item of the input `Table`. The value-producing input is evaluated once per item,
|
||||||
/// with the row's element index and the row itself (as a single-row table vararg) passed via context, so the
|
/// with the item's index and the item itself (as a `Table` containing only that item, passed as a vararg) provided via
|
||||||
/// upstream pipeline can return a different value per row that may be derived from the row's own data.
|
/// context, so the upstream pipeline can return a different value per item that may be derived from the item's own data.
|
||||||
/// If the column already exists, its values are replaced; if not, the column is created.
|
/// If the attribute already exists, its values are replaced; if not, the attribute is added.
|
||||||
#[node_macro::node(category("General"))]
|
#[node_macro::node(category("General"))]
|
||||||
async fn write_attribute<T: AnyHash + Clone + Send + Sync + core_types::CacheHash, U: Clone + Send + Sync + Default + std::fmt::Debug + 'static>(
|
async fn write_attribute<T: AnyHash + Clone + Send + Sync + core_types::CacheHash, U: Clone + Send + Sync + Default + std::fmt::Debug + 'static>(
|
||||||
ctx: impl ExtractAll + CloneVarArgs + Ctx,
|
ctx: impl ExtractAll + CloneVarArgs + Ctx,
|
||||||
/// The table whose rows will gain or replace the named attribute column.
|
/// The `Table` whose items will gain or have replaced the named attribute.
|
||||||
#[implementations(
|
#[implementations(
|
||||||
Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>,
|
Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>,
|
||||||
Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>,
|
Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>,
|
||||||
@@ -239,9 +234,9 @@ async fn write_attribute<T: AnyHash + Clone + Send + Sync + core_types::CacheHas
|
|||||||
Table<GradientStops>, Table<GradientStops>, Table<GradientStops>, Table<GradientStops>, Table<GradientStops>, Table<GradientStops>, Table<GradientStops>, Table<GradientStops>, Table<GradientStops>, Table<GradientStops>,
|
Table<GradientStops>, Table<GradientStops>, Table<GradientStops>, Table<GradientStops>, Table<GradientStops>, Table<GradientStops>, Table<GradientStops>, Table<GradientStops>, Table<GradientStops>, Table<GradientStops>,
|
||||||
)]
|
)]
|
||||||
mut content: Table<T>,
|
mut content: Table<T>,
|
||||||
/// The attribute name (column key) to write or replace.
|
/// The attribute name (key) to write or replace.
|
||||||
name: String,
|
name: String,
|
||||||
/// The node that produces the per-row value. Called once per row with the row index in context.
|
/// The node that produces the attribute value for each item. Called once per item with the item's index in context.
|
||||||
#[implementations(
|
#[implementations(
|
||||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Table<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Table<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Table<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Table<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||||
@@ -262,14 +257,14 @@ async fn write_attribute<T: AnyHash + Clone + Send + Sync + core_types::CacheHas
|
|||||||
content
|
content
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Joins two tables of the same type, extending the base table with the rows of the new table.
|
/// Joins two `Table`s of the same type, extending the base `Table` with the items from the new `Table`.
|
||||||
#[node_macro::node(category("General"))]
|
#[node_macro::node(category("General"))]
|
||||||
pub async fn extend<T: 'n + Send + Clone>(
|
pub async fn extend<T: 'n + Send + Clone>(
|
||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
/// The table whose rows will appear at the start of the extended table.
|
/// The `Table` whose items will appear at the start of the extended `Table`.
|
||||||
#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)]
|
#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)]
|
||||||
base: Table<T>,
|
base: Table<T>,
|
||||||
/// The table whose rows will appear at the end of the extended table.
|
/// The `Table` whose items will appear at the end of the extended `Table`.
|
||||||
#[expose]
|
#[expose]
|
||||||
#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)]
|
#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)]
|
||||||
new: Table<T>,
|
new: Table<T>,
|
||||||
@@ -327,8 +322,8 @@ pub async fn wrap_graphic<T: Into<Graphic> + 'n>(
|
|||||||
Table::new_from_element(content.into())
|
Table::new_from_element(content.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts a table of graphical content into a graphic table by placing it into an element of a new wrapper graphic table.
|
/// Converts a `Table` of graphical content into a `Table<Graphic>` by placing it into an element of a new wrapper `Table<Graphic>`.
|
||||||
/// If it is already a graphic table, it is not wrapped again. Use the 'Wrap Graphic' node if wrapping is always desired.
|
/// If it is already a `Table<Graphic>`, it is not wrapped again. Use the 'Wrap Graphic' node if wrapping is always desired.
|
||||||
#[node_macro::node(category("General"))]
|
#[node_macro::node(category("General"))]
|
||||||
pub async fn to_graphic<T: IntoGraphicTable + 'n>(
|
pub async fn to_graphic<T: IntoGraphicTable + 'n>(
|
||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
@@ -345,7 +340,7 @@ pub async fn to_graphic<T: IntoGraphicTable + 'n>(
|
|||||||
content.into_graphic_table()
|
content.into_graphic_table()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes a level of nesting from a graphic table, or all nesting if "Fully Flatten" is enabled.
|
/// Removes a level of nesting from a `Table<Graphic>`, or all nesting if "Fully Flatten" is enabled.
|
||||||
#[node_macro::node(category("General"))]
|
#[node_macro::node(category("General"))]
|
||||||
pub async fn flatten_graphic(_: impl Ctx, content: Table<Graphic>, fully_flatten: bool) -> Table<Graphic> {
|
pub async fn flatten_graphic(_: impl Ctx, content: Table<Graphic>, fully_flatten: bool) -> Table<Graphic> {
|
||||||
// TODO: Avoid mutable reference, instead return a new Table<Graphic>?
|
// TODO: Avoid mutable reference, instead return a new Table<Graphic>?
|
||||||
@@ -367,7 +362,7 @@ pub async fn flatten_graphic(_: impl Ctx, content: Table<Graphic>, fully_flatten
|
|||||||
|
|
||||||
flatten_table(output_graphic_table, current_element, fully_flatten, recursion_depth + 1);
|
flatten_table(output_graphic_table, current_element, fully_flatten, recursion_depth + 1);
|
||||||
}
|
}
|
||||||
// Push any leaf Graphic elements we encounter, which can be either Graphic table elements beyond the recursion depth, or table elements other than Graphic tables
|
// Push any leaf elements we encounter: either `Graphic::Graphic(...)` values beyond the recursion depth, or non-`Graphic::Graphic` variants (e.g. `Graphic::Vector`, `Graphic::Raster*`, `Graphic::Color`, `Graphic::Gradient`)
|
||||||
_ => {
|
_ => {
|
||||||
let attributes = current_graphic_table.clone_row_attributes(index);
|
let attributes = current_graphic_table.clone_row_attributes(index);
|
||||||
output_graphic_table.push(TableRow::from_parts(current_element, attributes));
|
output_graphic_table.push(TableRow::from_parts(current_element, attributes));
|
||||||
@@ -382,31 +377,31 @@ pub async fn flatten_graphic(_: impl Ctx, content: Table<Graphic>, fully_flatten
|
|||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts a graphic table into a vector table by deeply flattening any vector content it contains, and discarding any non-vector content.
|
/// Converts a `Table<Graphic>` into a `Table<Vector>` by deeply flattening any vector content it contains, and discarding any non-vector content.
|
||||||
#[node_macro::node(category("Vector"))]
|
#[node_macro::node(category("Vector"))]
|
||||||
pub async fn flatten_vector<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Vector>)] content: T) -> Table<Vector> {
|
pub async fn flatten_vector<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Vector>)] content: T) -> Table<Vector> {
|
||||||
content.into_flattened_table()
|
content.into_flattened_table()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts a graphic table into a raster table by deeply flattening any raster content it contains, and discarding any non-raster content.
|
/// Converts a `Table<Graphic>` into a `Table<Raster>` by deeply flattening any raster content it contains, and discarding any non-raster content.
|
||||||
#[node_macro::node(category("Raster"))]
|
#[node_macro::node(category("Raster"))]
|
||||||
pub async fn flatten_raster<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Raster<CPU>>)] content: T) -> Table<Raster<CPU>> {
|
pub async fn flatten_raster<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Raster<CPU>>)] content: T) -> Table<Raster<CPU>> {
|
||||||
content.into_flattened_table()
|
content.into_flattened_table()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts a graphic table into a color table by deeply flattening any color content it contains, and discarding any non-color content.
|
/// Converts a `Table<Graphic>` into a `Table<Color>` by deeply flattening any color content it contains, and discarding any non-color content.
|
||||||
#[node_macro::node(category("General"))]
|
#[node_macro::node(category("General"))]
|
||||||
pub async fn flatten_color<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Color>)] content: T) -> Table<Color> {
|
pub async fn flatten_color<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Color>)] content: T) -> Table<Color> {
|
||||||
content.into_flattened_table()
|
content.into_flattened_table()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts a graphic table into a gradient table by deeply flattening any gradient content it contains, and discarding any non-gradient content.
|
/// Converts a `Table<Graphic>` into a `Table<GradientStops>` by deeply flattening any gradient content it contains, and discarding any non-gradient content.
|
||||||
#[node_macro::node(category("General"))]
|
#[node_macro::node(category("General"))]
|
||||||
pub async fn flatten_gradient<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<GradientStops>)] content: T) -> Table<GradientStops> {
|
pub async fn flatten_gradient<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<GradientStops>)] content: T) -> Table<GradientStops> {
|
||||||
content.into_flattened_table()
|
content.into_flattened_table()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a gradient from a table of colors, where the colors are evenly distributed as gradient stops across the range from 0 to 1.
|
/// Constructs a gradient from a `Table<Color>`, where the colors are evenly distributed as gradient stops across the range from 0 to 1.
|
||||||
#[node_macro::node(category("Color"))]
|
#[node_macro::node(category("Color"))]
|
||||||
fn colors_to_gradient<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Color>)] colors: T) -> Table<GradientStops> {
|
fn colors_to_gradient<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Color>)] colors: T) -> Table<GradientStops> {
|
||||||
let colors = colors.into_flattened_table::<Color>();
|
let colors = colors.into_flattened_table::<Color>();
|
||||||
|
|||||||
@@ -14,14 +14,14 @@ use vector_types::kurbo::{Affine, BezPath, CubicBez, Line, ParamCurve, PathSeg,
|
|||||||
pub use vector_types::vector::misc::BooleanOperation;
|
pub use vector_types::vector::misc::BooleanOperation;
|
||||||
|
|
||||||
// TODO: Fix boolean ops to work by removing .transform() and .one_instance_*() calls,
|
// TODO: Fix boolean ops to work by removing .transform() and .one_instance_*() calls,
|
||||||
// TODO: since before we used a Vec of single-row tables and now we use a single table
|
// TODO: since before we used a Vec of single-item `Table`s and now we use a single `Table`
|
||||||
// TODO: with multiple rows while still assuming a single row for the boolean operations.
|
// TODO: with multiple items while still assuming a single item for the boolean operations.
|
||||||
|
|
||||||
/// Combines the geometric forms of one or more closed paths into a new vector path that results from cutting or joining the paths by the chosen method.
|
/// Combines the geometric forms of one or more closed paths into a new vector path that results from cutting or joining the paths by the chosen method.
|
||||||
#[node_macro::node(category("Vector: Modifier"), memoize)]
|
#[node_macro::node(category("Vector: Modifier"), memoize)]
|
||||||
async fn boolean_operation<I: graphic_types::IntoGraphicTable + 'n + Send + Clone>(
|
async fn boolean_operation<I: graphic_types::IntoGraphicTable + 'n + Send + Clone>(
|
||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
/// The table of vector paths to perform the boolean operation on. Nested tables are automatically flattened.
|
/// The `Table` of vector paths to perform the boolean operation on. Nested `Table`s are automatically flattened.
|
||||||
#[implementations(Table<Graphic>, Table<Vector>)]
|
#[implementations(Table<Graphic>, Table<Vector>)]
|
||||||
content: I,
|
content: I,
|
||||||
/// Which boolean operation to perform on the paths.
|
/// Which boolean operation to perform on the paths.
|
||||||
@@ -47,7 +47,7 @@ async fn boolean_operation<I: graphic_types::IntoGraphicTable + 'n + Send + Clon
|
|||||||
Vector::transform(result_vector, transform);
|
Vector::transform(result_vector, transform);
|
||||||
result_vector.style.set_stroke_transform(DAffine2::IDENTITY);
|
result_vector.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||||
|
|
||||||
// Snapshot the input layers as the `editor:merged_layers` row attribute so the renderer can recurse into them
|
// Snapshot the input layers as the `editor:merged_layers` attribute so the renderer can recurse into them
|
||||||
// for editor click-target preservation.
|
// for editor click-target preservation.
|
||||||
result_vector_table.set_attribute("editor:merged_layers", 0, content.clone());
|
result_vector_table.set_attribute("editor:merged_layers", 0, content.clone());
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@ fn boolean_operation_on_vector_table(vector: &Table<Vector>, boolean_operation:
|
|||||||
};
|
};
|
||||||
let mut row = if let Some(index) = copy_from_index {
|
let mut row = if let Some(index) = copy_from_index {
|
||||||
let mut attributes = vector.clone_row_attributes(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
|
// The boolean op bakes input transforms into the output geometry, so the result item carries no transform of its own
|
||||||
attributes.insert("transform", DAffine2::IDENTITY);
|
attributes.insert("transform", DAffine2::IDENTITY);
|
||||||
let copy_from = vector.element(index).unwrap();
|
let copy_from = vector.element(index).unwrap();
|
||||||
let element = Vector {
|
let element = Vector {
|
||||||
@@ -166,7 +166,7 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
|
|||||||
let graphic = graphic_table.element(index).unwrap();
|
let graphic = graphic_table.element(index).unwrap();
|
||||||
match graphic.clone() {
|
match graphic.clone() {
|
||||||
Graphic::Vector(vector) => {
|
Graphic::Vector(vector) => {
|
||||||
// Apply the parent graphic's transform to each element of the vector table
|
// Apply the parent graphic's transform to each element of the `Table<Vector>`
|
||||||
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default("transform", index);
|
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default("transform", index);
|
||||||
vector
|
vector
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -191,7 +191,7 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
|
|||||||
.with_attribute("editor:layer", layer)
|
.with_attribute("editor:layer", layer)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Apply the parent graphic's transform to each raster element, preserving each row's layer
|
// Apply the parent graphic's transform to each raster element, preserving each item's layer
|
||||||
// and alpha_blending so the boolean op downstream can route clicks (and inherit blending state)
|
// and alpha_blending so the boolean op downstream can route clicks (and inherit blending state)
|
||||||
// back to the originating raster layer
|
// back to the originating raster layer
|
||||||
(0..image.len())
|
(0..image.len())
|
||||||
@@ -217,7 +217,7 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
|
|||||||
.with_attribute("editor:layer", layer)
|
.with_attribute("editor:layer", layer)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Apply the parent graphic's transform to each raster element, preserving each row's layer
|
// Apply the parent graphic's transform to each raster element, preserving each item's layer
|
||||||
// and alpha_blending so the boolean op downstream can route clicks (and inherit blending state)
|
// and alpha_blending so the boolean op downstream can route clicks (and inherit blending state)
|
||||||
// back to the originating raster layer
|
// back to the originating raster layer
|
||||||
(0..image.len())
|
(0..image.len())
|
||||||
@@ -231,12 +231,12 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
|
|||||||
}
|
}
|
||||||
Graphic::Graphic(mut graphic) => {
|
Graphic::Graphic(mut graphic) => {
|
||||||
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default("transform", index);
|
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default("transform", index);
|
||||||
// Apply the parent graphic's transform to each element of inner table
|
// Apply the parent graphic's transform to each element of the inner `Table`
|
||||||
for transform in graphic.iter_attribute_values_mut_or_default::<DAffine2>("transform") {
|
for transform in graphic.iter_attribute_values_mut_or_default::<DAffine2>("transform") {
|
||||||
*transform = parent_transform * *transform;
|
*transform = parent_transform * *transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recursively flatten the inner table into the output vector table
|
// Recursively flatten the inner `Table` into the output `Table<Vector>`
|
||||||
let flattened = flatten_vector(&graphic);
|
let flattened = flatten_vector(&graphic);
|
||||||
let unioned = boolean_operation_on_vector_table(&flattened, BooleanOperation::Union);
|
let unioned = boolean_operation_on_vector_table(&flattened, BooleanOperation::Union);
|
||||||
|
|
||||||
|
|||||||
@@ -112,13 +112,13 @@ pub fn combine_channels(
|
|||||||
.zip(blue)
|
.zip(blue)
|
||||||
.zip(alpha)
|
.zip(alpha)
|
||||||
.filter_map(|(((red, green), blue), alpha)| {
|
.filter_map(|(((red, green), blue), alpha)| {
|
||||||
// Turn any default zero-sized image rows into None
|
// Turn any default zero-sized image items into None
|
||||||
let red = red.filter(|i| i.element().width > 0 && i.element().height > 0);
|
let red = red.filter(|i| i.element().width > 0 && i.element().height > 0);
|
||||||
let green = green.filter(|i| i.element().width > 0 && i.element().height > 0);
|
let green = green.filter(|i| i.element().width > 0 && i.element().height > 0);
|
||||||
let blue = blue.filter(|i| i.element().width > 0 && i.element().height > 0);
|
let blue = blue.filter(|i| i.element().width > 0 && i.element().height > 0);
|
||||||
let alpha = alpha.filter(|i| i.element().width > 0 && i.element().height > 0);
|
let alpha = alpha.filter(|i| i.element().width > 0 && i.element().height > 0);
|
||||||
|
|
||||||
// Get this row's transform and alpha blending mode from the first non-empty channel
|
// Get this item's transform and alpha blending mode from the first non-empty channel
|
||||||
let attributes = [&red, &green, &blue, &alpha].iter().find_map(|i| i.as_ref()).map(|i| i.attributes().clone())?;
|
let attributes = [&red, &green, &blue, &alpha].iter().find_map(|i| i.as_ref()).map(|i| i.attributes().clone())?;
|
||||||
|
|
||||||
// Get the common width and height of the channels, which must have equal dimensions
|
// Get the common width and height of the channels, which must have equal dimensions
|
||||||
@@ -183,7 +183,7 @@ pub fn mask(
|
|||||||
#[expose]
|
#[expose]
|
||||||
stencil: Table<Raster<CPU>>,
|
stencil: Table<Raster<CPU>>,
|
||||||
) -> Table<Raster<CPU>> {
|
) -> Table<Raster<CPU>> {
|
||||||
// TODO: Figure out what it means to support multiple stencil rows?
|
// TODO: Figure out what it means to support multiple stencil items?
|
||||||
let Some(stencil) = stencil.into_iter().next() else {
|
let Some(stencil) = stencil.into_iter().next() else {
|
||||||
// No stencil provided so we return the original image
|
// No stencil provided so we return the original image
|
||||||
return image;
|
return image;
|
||||||
@@ -285,7 +285,7 @@ pub fn empty_image(_: impl Ctx, transform: DAffine2, color: Table<Color>) -> Tab
|
|||||||
result_table.set_attribute("transform", 0, transform);
|
result_table.set_attribute("transform", 0, transform);
|
||||||
result_table.set_attribute("alpha_blending", 0, AlphaBlending::default());
|
result_table.set_attribute("alpha_blending", 0, AlphaBlending::default());
|
||||||
|
|
||||||
// Callers of empty_image can safely unwrap on returned table
|
// Callers of empty_image can safely unwrap on returned `Table`
|
||||||
result_table
|
result_table
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ async fn repeat<T: Into<Graphic> + Default + Send + Clone + 'static>(
|
|||||||
Context -> Table<Color>,
|
Context -> Table<Color>,
|
||||||
Context -> Table<GradientStops>,
|
Context -> Table<GradientStops>,
|
||||||
)]
|
)]
|
||||||
instance: impl Node<'n, Context<'static>, Output = Table<T>>,
|
content: impl Node<'n, Context<'static>, Output = Table<T>>,
|
||||||
#[default(1)]
|
#[default(1)]
|
||||||
#[hard_min(1)]
|
#[hard_min(1)]
|
||||||
count: u32,
|
count: u32,
|
||||||
@@ -34,9 +34,9 @@ async fn repeat<T: Into<Graphic> + Default + Send + Clone + 'static>(
|
|||||||
let index = if reverse { count - index - 1 } else { index };
|
let index = if reverse { count - index - 1 } else { index };
|
||||||
|
|
||||||
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index);
|
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index);
|
||||||
let generated_instance = instance.eval(new_ctx.into_context()).await;
|
let generated_content = content.eval(new_ctx.into_context()).await;
|
||||||
|
|
||||||
for generated_row in generated_instance.into_iter() {
|
for generated_row in generated_content.into_iter() {
|
||||||
result_table.push(generated_row);
|
result_table.push(generated_row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,7 +54,7 @@ pub async fn repeat_array<T: Into<Graphic> + Default + Send + Clone + 'static>(
|
|||||||
Context -> Table<Color>,
|
Context -> Table<Color>,
|
||||||
Context -> Table<GradientStops>,
|
Context -> Table<GradientStops>,
|
||||||
)]
|
)]
|
||||||
instance: impl Node<'n, Context<'static>, Output = Table<T>>,
|
content: impl Node<'n, Context<'static>, Output = Table<T>>,
|
||||||
#[default(100., 100.)]
|
#[default(100., 100.)]
|
||||||
// TODO: When using a custom Properties panel layout in document_node_definitions.rs and this default is set, the widget weirdly doesn't show up in the Properties panel. Investigation is needed.
|
// TODO: When using a custom Properties panel layout in document_node_definitions.rs and this default is set, the widget weirdly doesn't show up in the Properties panel. Investigation is needed.
|
||||||
direction: PixelSize,
|
direction: PixelSize,
|
||||||
@@ -75,10 +75,10 @@ pub async fn repeat_array<T: Into<Graphic> + Default + Send + Clone + 'static>(
|
|||||||
let transform = DAffine2::from_angle(angle) * DAffine2::from_translation(translation);
|
let transform = DAffine2::from_angle(angle) * DAffine2::from_translation(translation);
|
||||||
|
|
||||||
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index as usize);
|
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index as usize);
|
||||||
let generated_instance = instance.eval(new_ctx.into_context()).await;
|
let generated_content = content.eval(new_ctx.into_context()).await;
|
||||||
|
|
||||||
for row_index in 0..generated_instance.len() {
|
for row_index in 0..generated_content.len() {
|
||||||
let Some(mut row) = generated_instance.clone_row(row_index) else { continue };
|
let Some(mut row) = generated_content.clone_row(row_index) else { continue };
|
||||||
|
|
||||||
let local_transform: DAffine2 = row.attribute_cloned_or_default("transform");
|
let local_transform: DAffine2 = row.attribute_cloned_or_default("transform");
|
||||||
let local_translation = DAffine2::from_translation(local_transform.translation);
|
let local_translation = DAffine2::from_translation(local_transform.translation);
|
||||||
@@ -102,7 +102,7 @@ async fn repeat_radial<T: Into<Graphic> + Default + Send + Clone + 'static>(
|
|||||||
Context -> Table<Color>,
|
Context -> Table<Color>,
|
||||||
Context -> Table<GradientStops>,
|
Context -> Table<GradientStops>,
|
||||||
)]
|
)]
|
||||||
instance: impl Node<'n, Context<'static>, Output = Table<T>>,
|
content: impl Node<'n, Context<'static>, Output = Table<T>>,
|
||||||
start_angle: Angle,
|
start_angle: Angle,
|
||||||
#[unit(" px")]
|
#[unit(" px")]
|
||||||
#[default(5)]
|
#[default(5)]
|
||||||
@@ -121,10 +121,10 @@ async fn repeat_radial<T: Into<Graphic> + Default + Send + Clone + 'static>(
|
|||||||
let transform = angle * translation;
|
let transform = angle * translation;
|
||||||
|
|
||||||
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index as usize);
|
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index as usize);
|
||||||
let generated_instance = instance.eval(new_ctx.into_context()).await;
|
let generated_content = content.eval(new_ctx.into_context()).await;
|
||||||
|
|
||||||
for row_index in 0..generated_instance.len() {
|
for row_index in 0..generated_content.len() {
|
||||||
let Some(mut row) = generated_instance.clone_row(row_index) else { continue };
|
let Some(mut row) = generated_content.clone_row(row_index) else { continue };
|
||||||
|
|
||||||
let local_transform: DAffine2 = row.attribute_cloned_or_default("transform");
|
let local_transform: DAffine2 = row.attribute_cloned_or_default("transform");
|
||||||
let local_translation = DAffine2::from_translation(local_transform.translation);
|
let local_translation = DAffine2::from_translation(local_transform.translation);
|
||||||
@@ -149,7 +149,7 @@ async fn repeat_on_points<T: Into<Graphic> + Default + Send + Clone + 'static>(
|
|||||||
Context -> Table<Color>,
|
Context -> Table<Color>,
|
||||||
Context -> Table<GradientStops>,
|
Context -> Table<GradientStops>,
|
||||||
)]
|
)]
|
||||||
instance: impl Node<'n, Context<'static>, Output = Table<T>>,
|
content: impl Node<'n, Context<'static>, Output = Table<T>>,
|
||||||
reverse: bool,
|
reverse: bool,
|
||||||
) -> Table<T> {
|
) -> Table<T> {
|
||||||
let mut result_table = Table::new();
|
let mut result_table = Table::new();
|
||||||
@@ -162,9 +162,9 @@ async fn repeat_on_points<T: Into<Graphic> + Default + Send + Clone + 'static>(
|
|||||||
let transformed_point = transform.transform_point2(point);
|
let transformed_point = transform.transform_point2(point);
|
||||||
|
|
||||||
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index).with_position(transformed_point);
|
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index).with_position(transformed_point);
|
||||||
let generated_instance = instance.eval(new_ctx.into_context()).await;
|
let generated_content = content.eval(new_ctx.into_context()).await;
|
||||||
|
|
||||||
for mut generated_row in generated_instance.into_iter() {
|
for mut generated_row in generated_content.into_iter() {
|
||||||
generated_row.attribute_mut_or_insert_default::<DAffine2>("transform").translation = transformed_point;
|
generated_row.attribute_mut_or_insert_default::<DAffine2>("transform").translation = transformed_point;
|
||||||
result_table.push(generated_row);
|
result_table.push(generated_row);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ fn query_json(
|
|||||||
|
|
||||||
/// Extracts every matched value from a JSON string using a path expression (see that parameter's description for its syntax). A list of zero or more resultant strings is produced. The `[]` path accessor is used to read more than one value.
|
/// Extracts every matched value from a JSON string using a path expression (see that parameter's description for its syntax). A list of zero or more resultant strings is produced. The `[]` path accessor is used to read more than one value.
|
||||||
///
|
///
|
||||||
/// Each row carries a `type` attribute holding the matched value's JSON type (`"string"`, `"number"`, `"bool"`, `"null"`, `"object"`, or `"array"`).
|
/// Each item carries a `type` attribute holding the matched value's JSON type (`"string"`, `"number"`, `"bool"`, `"null"`, `"object"`, or `"array"`).
|
||||||
///
|
///
|
||||||
/// This is useful in conjunction with the nodes:
|
/// This is useful in conjunction with the nodes:
|
||||||
/// • **Index Elements**: access the `N`th query result.
|
/// • **Index Elements**: access the `N`th query result.
|
||||||
|
|||||||
@@ -19,11 +19,11 @@ pub struct PathBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PathBuilder {
|
impl PathBuilder {
|
||||||
pub fn new(per_glyph_instances: bool, scale: f64) -> Self {
|
pub fn new(per_glyph_items: bool, scale: f64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
current_subpath: Subpath::new(Vec::new(), false),
|
current_subpath: Subpath::new(Vec::new(), false),
|
||||||
glyph_subpaths: Vec::new(),
|
glyph_subpaths: Vec::new(),
|
||||||
vector_table: if per_glyph_instances { Table::new() } else { Table::new_from_element(Vector::default()) },
|
vector_table: if per_glyph_items { Table::new() } else { Table::new_from_element(Vector::default()) },
|
||||||
scale,
|
scale,
|
||||||
id: PointId::ZERO,
|
id: PointId::ZERO,
|
||||||
origin: DVec2::default(),
|
origin: DVec2::default(),
|
||||||
@@ -35,7 +35,7 @@ impl PathBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn draw_glyph(&mut self, glyph: &OutlineGlyph<'_>, size: f32, normalized_coords: &[NormalizedCoord], glyph_offset: DVec2, style_skew: Option<DAffine2>, skew: DAffine2, per_glyph_instances: bool) {
|
fn draw_glyph(&mut self, glyph: &OutlineGlyph<'_>, size: f32, normalized_coords: &[NormalizedCoord], glyph_offset: DVec2, style_skew: Option<DAffine2>, skew: DAffine2, per_glyph_items: bool) {
|
||||||
let location_ref = LocationRef::new(normalized_coords);
|
let location_ref = LocationRef::new(normalized_coords);
|
||||||
let settings = DrawSettings::unhinted(Size::new(size), location_ref);
|
let settings = DrawSettings::unhinted(Size::new(size), location_ref);
|
||||||
glyph.draw(settings, self).unwrap();
|
glyph.draw(settings, self).unwrap();
|
||||||
@@ -50,18 +50,18 @@ impl PathBuilder {
|
|||||||
glyph_subpath.apply_transform(skew);
|
glyph_subpath.apply_transform(skew);
|
||||||
}
|
}
|
||||||
|
|
||||||
if per_glyph_instances {
|
if per_glyph_items {
|
||||||
self.vector_table
|
self.vector_table
|
||||||
.push(TableRow::new_from_element(Vector::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false)).with_attribute("transform", DAffine2::from_translation(glyph_offset)));
|
.push(TableRow::new_from_element(Vector::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false)).with_attribute("transform", DAffine2::from_translation(glyph_offset)));
|
||||||
} else {
|
} else {
|
||||||
for subpath in self.glyph_subpaths.drain(..) {
|
for subpath in self.glyph_subpaths.drain(..) {
|
||||||
// Unwrapping here is ok because `self.vector_table` is initialized with a single `Vector` table element
|
// Unwrapping here is ok because `self.vector_table` is initialized with a single `Table<Vector>` item
|
||||||
self.vector_table.element_mut(0).unwrap().append_subpath(subpath, false);
|
self.vector_table.element_mut(0).unwrap().append_subpath(subpath, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_glyph_run(&mut self, glyph_run: &GlyphRun<'_, ()>, tilt: f64, per_glyph_instances: bool) {
|
pub fn render_glyph_run(&mut self, glyph_run: &GlyphRun<'_, ()>, tilt: f64, per_glyph_items: bool) {
|
||||||
let mut run_x = glyph_run.offset();
|
let mut run_x = glyph_run.offset();
|
||||||
let run_y = glyph_run.baseline();
|
let run_y = glyph_run.baseline();
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ impl PathBuilder {
|
|||||||
|
|
||||||
// User-requested tilt applied around baseline to avoid vertical displacement
|
// User-requested tilt applied around baseline to avoid vertical displacement
|
||||||
// Translation ensures rotation point is at the baseline, not origin
|
// Translation ensures rotation point is at the baseline, not origin
|
||||||
let skew = if per_glyph_instances {
|
let skew = if per_glyph_items {
|
||||||
DAffine2::from_cols_array(&[1., 0., -tilt.to_radians().tan(), 1., 0., 0.])
|
DAffine2::from_cols_array(&[1., 0., -tilt.to_radians().tan(), 1., 0., 0.])
|
||||||
} else {
|
} else {
|
||||||
DAffine2::from_translation(DVec2::new(0., run_y as f64))
|
DAffine2::from_translation(DVec2::new(0., run_y as f64))
|
||||||
@@ -82,7 +82,7 @@ impl PathBuilder {
|
|||||||
// Font synthesis (e.g., synthetic italic) applied separately from user transforms
|
// Font synthesis (e.g., synthetic italic) applied separately from user transforms
|
||||||
// This preserves the distinction between font styling and user transformations
|
// This preserves the distinction between font styling and user transformations
|
||||||
let style_skew = synthesis.skew().map(|angle| {
|
let style_skew = synthesis.skew().map(|angle| {
|
||||||
if per_glyph_instances {
|
if per_glyph_items {
|
||||||
DAffine2::from_cols_array(&[1., 0., -angle.to_radians().tan() as f64, 1., 0., 0.])
|
DAffine2::from_cols_array(&[1., 0., -angle.to_radians().tan() as f64, 1., 0., 0.])
|
||||||
} else {
|
} else {
|
||||||
DAffine2::from_translation(DVec2::new(0., run_y as f64))
|
DAffine2::from_translation(DVec2::new(0., run_y as f64))
|
||||||
@@ -107,10 +107,10 @@ impl PathBuilder {
|
|||||||
|
|
||||||
let glyph_id = GlyphId::from(glyph.id);
|
let glyph_id = GlyphId::from(glyph.id);
|
||||||
if let Some(glyph_outline) = outlines.get(glyph_id) {
|
if let Some(glyph_outline) = outlines.get(glyph_id) {
|
||||||
if !per_glyph_instances {
|
if !per_glyph_items {
|
||||||
self.origin = glyph_offset;
|
self.origin = glyph_offset;
|
||||||
}
|
}
|
||||||
self.draw_glyph(&glyph_outline, font_size, &normalized_coords, glyph_offset, style_skew, skew, per_glyph_instances);
|
self.draw_glyph(&glyph_outline, font_size, &normalized_coords, glyph_offset, style_skew, skew, per_glyph_items);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,11 +77,11 @@ fn regex_replace(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finds a regex match in the string and returns its components. The result is a list where the first element is the whole match (`$0`) and subsequent elements are the capture groups (`$1`, `$2`, etc., if any).
|
/// Finds a regex match in the string and returns its components. The result is a list where the first item is the whole match (`$0`) and subsequent items are the capture groups (`$1`, `$2`, etc., if any).
|
||||||
///
|
///
|
||||||
/// The match index selects which non-overlapping occurrence to return (0 for the first match). Returns an empty list if no match is found at the given index.
|
/// The match index selects which non-overlapping occurrence to return (0 for the first match). Returns an empty list if no match is found at the given index.
|
||||||
///
|
///
|
||||||
/// Each row carries `start` and `end` byte-offset attributes pointing into the original string, plus a `name` attribute holding
|
/// Each item carries `start` and `end` byte-offset attributes pointing into the original string, plus a `name` attribute holding
|
||||||
/// the capture group's name (empty for unnamed groups, and for index 0 which is the whole match).
|
/// the capture group's name (empty for unnamed groups, and for index 0 which is the whole match).
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
fn regex_find(
|
fn regex_find(
|
||||||
@@ -150,7 +150,7 @@ fn regex_find(
|
|||||||
|
|
||||||
/// Finds all non-overlapping matches of a regular expression pattern in the string, returning a list of the matched substrings.
|
/// Finds all non-overlapping matches of a regular expression pattern in the string, returning a list of the matched substrings.
|
||||||
///
|
///
|
||||||
/// Each row carries `start` and `end` byte-offset attributes pointing into the original string.
|
/// Each item carries `start` and `end` byte-offset attributes pointing into the original string.
|
||||||
#[node_macro::node(category("Text: Regex"))]
|
#[node_macro::node(category("Text: Regex"))]
|
||||||
fn regex_find_all(
|
fn regex_find_all(
|
||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
|
|||||||
@@ -87,19 +87,19 @@ impl TextContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Convert text to vector paths using the specified font and typesetting configuration
|
/// Convert text to vector paths using the specified font and typesetting configuration
|
||||||
pub fn to_path(&mut self, text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig, per_glyph_instances: bool) -> Table<Vector> {
|
pub fn to_path(&mut self, text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig, per_glyph_items: bool) -> Table<Vector> {
|
||||||
let Some(layout) = self.layout_text(text, font, font_cache, typesetting) else {
|
let Some(layout) = self.layout_text(text, font, font_cache, typesetting) else {
|
||||||
return Table::new_from_element(Vector::default());
|
return Table::new_from_element(Vector::default());
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut path_builder = PathBuilder::new(per_glyph_instances, layout.scale() as f64);
|
let mut path_builder = PathBuilder::new(per_glyph_items, layout.scale() as f64);
|
||||||
|
|
||||||
for line in layout.lines() {
|
for line in layout.lines() {
|
||||||
for item in line.items() {
|
for item in line.items() {
|
||||||
if let PositionedLayoutItem::GlyphRun(glyph_run) = item
|
if let PositionedLayoutItem::GlyphRun(glyph_run) = item
|
||||||
&& typesetting.max_height.filter(|&max_height| glyph_run.baseline() > max_height as f32).is_none()
|
&& typesetting.max_height.filter(|&max_height| glyph_run.baseline() > max_height as f32).is_none()
|
||||||
{
|
{
|
||||||
path_builder.render_glyph_run(&glyph_run, typesetting.tilt, per_glyph_instances);
|
path_builder.render_glyph_run(&glyph_run, typesetting.tilt, per_glyph_items);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ use parley::fontique::Blob;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use vector_types::Vector;
|
use vector_types::Vector;
|
||||||
|
|
||||||
pub fn to_path(text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig, per_glyph_instances: bool) -> Table<Vector> {
|
pub fn to_path(text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig, per_glyph_items: bool) -> Table<Vector> {
|
||||||
TextContext::with_thread_local(|ctx| ctx.to_path(text, font, font_cache, typesetting, per_glyph_instances))
|
TextContext::with_thread_local(|ctx| ctx.to_path(text, font, font_cache, typesetting, per_glyph_items))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bounding_box(text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig, for_clipping_test: bool) -> DVec2 {
|
pub fn bounding_box(text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig, for_clipping_test: bool) -> DVec2 {
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ fn reset_transform<T>(
|
|||||||
content
|
content
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Overwrites the transform of each element in the input table with the specified transform.
|
/// Overwrites the transform of each item in the input `Table` with the specified transform.
|
||||||
#[node_macro::node(category("Math: Transform"))]
|
#[node_macro::node(category("Math: Transform"))]
|
||||||
fn replace_transform<T>(
|
fn replace_transform<T>(
|
||||||
_: impl Ctx + InjectFootprint,
|
_: impl Ctx + InjectFootprint,
|
||||||
@@ -109,7 +109,7 @@ fn replace_transform<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Figure out how this node should behave once #2982 is implemented.
|
// TODO: Figure out how this node should behave once #2982 is implemented.
|
||||||
/// Obtains the transform of the first element in the input table, if present.
|
/// Obtains the transform of the first item in the input `Table`, if present.
|
||||||
#[node_macro::node(category("Math: Transform"), path(core_types::vector))]
|
#[node_macro::node(category("Math: Transform"), path(core_types::vector))]
|
||||||
async fn extract_transform<T>(
|
async fn extract_transform<T>(
|
||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ async fn path_modify(_ctx: impl Ctx, mut vector: Table<Vector>, modification: Bo
|
|||||||
vector.set_attribute("editor:layer", 0, if existing.is_empty() { subgraph_path } else { existing });
|
vector.set_attribute("editor:layer", 0, if existing.is_empty() { subgraph_path } else { existing });
|
||||||
|
|
||||||
if vector.len() > 1 {
|
if vector.len() > 1 {
|
||||||
warn!("The path modify ran on {} vector rows. Only the first can be modified.", vector.len());
|
warn!("The path modify ran on {} vector items. Only the first can be modified.", vector.len());
|
||||||
}
|
}
|
||||||
vector
|
vector
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ use vector_types::vector::misc::{
|
|||||||
use vector_types::vector::style::{Fill, Gradient, GradientStops, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
|
use vector_types::vector::style::{Fill, Gradient, GradientStops, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
|
||||||
use vector_types::vector::{FillId, PointId, RegionId, SegmentDomain, SegmentId, StrokeId, VectorExt};
|
use vector_types::vector::{FillId, PointId, RegionId, SegmentDomain, SegmentId, StrokeId, VectorExt};
|
||||||
|
|
||||||
/// Implemented for types that contain vector rows reachable via mutable access.
|
/// Implemented for types that contain vector items reachable via mutable access.
|
||||||
/// Used for the fill and stroke nodes so they can apply to either `Table<Graphic>` or `Table<Vector>`.
|
/// Used for the fill and stroke nodes so they can apply to either `Table<Graphic>` or `Table<Vector>`.
|
||||||
trait VectorTableIterMut {
|
trait VectorTableIterMut {
|
||||||
fn for_each_vector_mut(&mut self, f: impl FnMut(&mut Vector, DAffine2));
|
fn for_each_vector_mut(&mut self, f: impl FnMut(&mut Vector, DAffine2));
|
||||||
@@ -255,13 +255,13 @@ async fn copy_to_points<I: 'n + Send + Clone>(
|
|||||||
/// Artwork to be copied and placed at each point.
|
/// Artwork to be copied and placed at each point.
|
||||||
#[expose]
|
#[expose]
|
||||||
#[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Color>, Table<GradientStops>)]
|
#[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Color>, Table<GradientStops>)]
|
||||||
instance: Table<I>,
|
content: Table<I>,
|
||||||
/// Minimum range of randomized sizes given to each instance.
|
/// Minimum range of randomized sizes given to each placed copy.
|
||||||
#[default(1)]
|
#[default(1)]
|
||||||
#[range((0., 2.))]
|
#[range((0., 2.))]
|
||||||
#[unit("x")]
|
#[unit("x")]
|
||||||
random_scale_min: Multiplier,
|
random_scale_min: Multiplier,
|
||||||
/// Maximum range of randomized sizes given to each instance.
|
/// Maximum range of randomized sizes given to each placed copy.
|
||||||
#[default(1)]
|
#[default(1)]
|
||||||
#[range((0., 2.))]
|
#[range((0., 2.))]
|
||||||
#[unit("x")]
|
#[unit("x")]
|
||||||
@@ -269,12 +269,12 @@ async fn copy_to_points<I: 'n + Send + Clone>(
|
|||||||
/// Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).
|
/// Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).
|
||||||
#[range((-50., 50.))]
|
#[range((-50., 50.))]
|
||||||
random_scale_bias: f64,
|
random_scale_bias: f64,
|
||||||
/// Seed to determine unique variations on all the randomized instance sizes.
|
/// Seed to determine unique variations on all the randomized copy sizes.
|
||||||
random_scale_seed: SeedValue,
|
random_scale_seed: SeedValue,
|
||||||
/// Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.
|
/// Range of randomized angles given to each placed copy, in degrees ranging from furthest clockwise to counterclockwise.
|
||||||
#[range((0., 360.))]
|
#[range((0., 360.))]
|
||||||
random_rotation: Angle,
|
random_rotation: Angle,
|
||||||
/// Seed to determine unique variations on all the randomized instance angles.
|
/// Seed to determine unique variations on all the randomized copy angles.
|
||||||
random_rotation_seed: SeedValue,
|
random_rotation_seed: SeedValue,
|
||||||
) -> Table<I> {
|
) -> Table<I> {
|
||||||
let mut result_table = Table::new();
|
let mut result_table = Table::new();
|
||||||
@@ -315,8 +315,8 @@ async fn copy_to_points<I: 'n + Send + Clone>(
|
|||||||
|
|
||||||
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(scale), rotation, translation);
|
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(scale), rotation, translation);
|
||||||
|
|
||||||
for row_index in 0..instance.len() {
|
for row_index in 0..content.len() {
|
||||||
let Some(mut row) = instance.clone_row(row_index) else { continue };
|
let Some(mut row) = content.clone_row(row_index) else { continue };
|
||||||
let row_transform: DAffine2 = row.attribute_cloned_or_default("transform");
|
let row_transform: DAffine2 = row.attribute_cloned_or_default("transform");
|
||||||
row.set_attribute("transform", transform * row_transform);
|
row.set_attribute("transform", transform * row_transform);
|
||||||
|
|
||||||
@@ -741,7 +741,7 @@ async fn box_warp(_: impl Ctx, content: Table<Vector>, #[expose] rectangle: Tabl
|
|||||||
|
|
||||||
result.style.set_stroke_transform(DAffine2::IDENTITY);
|
result.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||||
|
|
||||||
// Add this to the table and reset the transform since we've applied it directly to the points
|
// Add this to the `Table` and reset the transform since we've applied it directly to the points
|
||||||
*row.element_mut() = result;
|
*row.element_mut() = result;
|
||||||
row.set_attribute("transform", DAffine2::IDENTITY);
|
row.set_attribute("transform", DAffine2::IDENTITY);
|
||||||
row
|
row
|
||||||
@@ -797,7 +797,7 @@ where
|
|||||||
let mut items: Vec<(f64, f64, DVec2, TableRow<T>)> = elements
|
let mut items: Vec<(f64, f64, DVec2, TableRow<T>)> = elements
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|row| {
|
.map(|row| {
|
||||||
// Single-element table to query its bounding box
|
// Single-item `Table` to query its bounding box
|
||||||
let single = Table::new_from_row(row.clone());
|
let single = Table::new_from_row(row.clone());
|
||||||
let (w, h, top_left) = match single.bounding_box(DAffine2::IDENTITY, false) {
|
let (w, h, top_left) = match single.bounding_box(DAffine2::IDENTITY, false) {
|
||||||
RenderBoundingBox::Rectangle([min, max]) => {
|
RenderBoundingBox::Rectangle([min, max]) => {
|
||||||
@@ -1210,7 +1210,7 @@ async fn solidify_stroke(_: impl Ctx, content: Table<Vector>) -> Table<Vector> {
|
|||||||
solidified_stroke.style.set_fill(Fill::solid_or_none(stroke.color));
|
solidified_stroke.style.set_fill(Fill::solid_or_none(stroke.color));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the original vector has a fill, preserve it as a separate row with the stroke cleared.
|
// If the original vector has a fill, preserve it as a separate item with the stroke cleared.
|
||||||
let has_fill = !vector.style.fill().is_none();
|
let has_fill = !vector.style.fill().is_none();
|
||||||
let fill_row = has_fill.then(|| {
|
let fill_row = has_fill.then(|| {
|
||||||
vector.style.clear_stroke();
|
vector.style.clear_stroke();
|
||||||
@@ -1219,7 +1219,7 @@ async fn solidify_stroke(_: impl Ctx, content: Table<Vector>) -> Table<Vector> {
|
|||||||
|
|
||||||
let stroke_row = TableRow::from_parts(solidified_stroke, attributes);
|
let stroke_row = TableRow::from_parts(solidified_stroke, attributes);
|
||||||
|
|
||||||
// Ordering based on the paint order. The first row in the table is rendered below the second.
|
// Ordering based on the paint order. The first item in the `Table` is rendered below the second.
|
||||||
match paint_order {
|
match paint_order {
|
||||||
PaintOrder::StrokeAbove => fill_row.into_iter().chain(std::iter::once(stroke_row)).collect::<Vec<_>>(),
|
PaintOrder::StrokeAbove => fill_row.into_iter().chain(std::iter::once(stroke_row)).collect::<Vec<_>>(),
|
||||||
PaintOrder::StrokeBelow => std::iter::once(stroke_row).chain(fill_row).collect::<Vec<_>>(),
|
PaintOrder::StrokeBelow => std::iter::once(stroke_row).chain(fill_row).collect::<Vec<_>>(),
|
||||||
@@ -1289,7 +1289,7 @@ pub async fn flatten_path<T: IntoGraphicTable + 'n + Send>(_: impl Ctx, #[implem
|
|||||||
let graphic_table = content.into_graphic_table();
|
let graphic_table = content.into_graphic_table();
|
||||||
let flattened = graphic_table.clone().into_flattened_table::<Vector>();
|
let flattened = graphic_table.clone().into_flattened_table::<Vector>();
|
||||||
|
|
||||||
// Create a table with one empty `Vector` element, then get a mutable reference to it which we append flattened subpaths to
|
// Create a `Table` with one empty `Vector` element, then get a mutable reference to it which we append flattened subpaths to
|
||||||
let mut output_table = Table::new_from_element(Vector::default());
|
let mut output_table = Table::new_from_element(Vector::default());
|
||||||
let output = output_table.element_mut(0).unwrap();
|
let output = output_table.element_mut(0).unwrap();
|
||||||
|
|
||||||
@@ -1310,12 +1310,12 @@ pub async fn flatten_path<T: IntoGraphicTable + 'n + Send>(_: impl Ctx, #[implem
|
|||||||
output.style = element.style.clone();
|
output.style = element.style.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preserve a reference to the original upstream graphic table so the renderer can recurse into it
|
// Preserve a reference to the original upstream `Table<Graphic>` so the renderer can recurse into it
|
||||||
// when collecting metadata, exposing the original child layers' click targets to editor tools.
|
// when collecting metadata, exposing the original child layers' click targets to editor tools.
|
||||||
// This is the same mechanism Boolean Operation uses to keep its inputs editable after the merge.
|
// This is the same mechanism Boolean Operation uses to keep its inputs editable after the merge.
|
||||||
output_table.set_attribute("editor:merged_layers", 0, graphic_table);
|
output_table.set_attribute("editor:merged_layers", 0, graphic_table);
|
||||||
|
|
||||||
// Adopt the last input row's layer so the editor can also bucket clicks under a contributing child layer
|
// Adopt the last input item's layer so the editor can also bucket clicks under a contributing child layer
|
||||||
if !flattened.is_empty() {
|
if !flattened.is_empty() {
|
||||||
let primary = flattened.len() - 1;
|
let primary = flattened.len() - 1;
|
||||||
let layer_path: Table<NodeId> = flattened.attribute_cloned_or_default("editor:layer", primary);
|
let layer_path: Table<NodeId> = flattened.attribute_cloned_or_default("editor:layer", primary);
|
||||||
@@ -2130,7 +2130,7 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preserve original graphic table as upstream data so this group layer's nested layers can be edited by the tools.
|
// Preserve original `Table<Graphic>` as upstream data so this group layer's nested layers can be edited by the tools.
|
||||||
let mut graphic_table_content = content.clone().into_graphic_table();
|
let mut graphic_table_content = content.clone().into_graphic_table();
|
||||||
|
|
||||||
// If the input isn't a Table<Vector>, we convert it into one by flattening any Table<Graphic> content.
|
// If the input isn't a Table<Vector>, we convert it into one by flattening any Table<Graphic> content.
|
||||||
@@ -2191,7 +2191,7 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
|
|||||||
let control_bezpath = &control_bezpaths[subpath_index];
|
let control_bezpath = &control_bezpaths[subpath_index];
|
||||||
let segment_count = control_bezpath.segments().count();
|
let segment_count = control_bezpath.segments().count();
|
||||||
|
|
||||||
// If the control path has no segments, return the first element
|
// If the control path has no segments, return the first item
|
||||||
if segment_count == 0 {
|
if segment_count == 0 {
|
||||||
return content.into_iter().next().into_iter().collect();
|
return content.into_iter().next().into_iter().collect();
|
||||||
}
|
}
|
||||||
@@ -2352,7 +2352,7 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Pre-compensate merged_layers transforms so that when collect_metadata applies
|
// Pre-compensate merged_layers transforms so that when collect_metadata applies
|
||||||
// the row transform (which will be group_transform * lerped_transform after the
|
// the item transform (which will be group_transform * lerped_transform after the
|
||||||
// pipeline's Transform node runs), the lerped_transform cancels out and children
|
// pipeline's Transform node runs), the lerped_transform cancels out and children
|
||||||
// get the correct footprint: parent * group_transform * child_transform.
|
// get the correct footprint: parent * group_transform * child_transform.
|
||||||
// Only pre-compensate if the lerped transform is invertible (non-zero determinant).
|
// Only pre-compensate if the lerped transform is invertible (non-zero determinant).
|
||||||
@@ -2878,7 +2878,7 @@ async fn count_points(_: impl Ctx, content: Table<Vector>) -> f64 {
|
|||||||
content.iter_element_values().map(|vector| vector.point_domain.positions().len() as f64).sum()
|
content.iter_element_values().map(|vector| vector.point_domain.positions().len() as f64).sum()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves the vec2 position (in local space) of the anchor point at the specified index in table of vector elements.
|
/// Retrieves the vec2 position (in local space) of the anchor point at the specified index in a `Table` of vector elements.
|
||||||
/// If no value exists at that index, the position (0, 0) is returned.
|
/// If no value exists at that index, the position (0, 0) is returned.
|
||||||
#[node_macro::node(category("Vector: Measure"), path(graphene_core::vector))]
|
#[node_macro::node(category("Vector: Measure"), path(graphene_core::vector))]
|
||||||
async fn index_points(
|
async fn index_points(
|
||||||
|
|||||||
Reference in New Issue
Block a user