Replace deprecated row/cell/instance terminology with "item" and "value" terms (#4075)
This commit is contained in:
parent
f6c73d1c20
commit
5774ec215d
|
|
@ -102,22 +102,22 @@ impl LayoutMessageHandler {
|
|||
}
|
||||
LayoutGroup::Table(WidgetTable { rows, .. }) => {
|
||||
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
|
||||
if cell.widget_id == widget_id {
|
||||
if value.widget_id == widget_id {
|
||||
widget_path.push(row_index);
|
||||
widget_path.push(cell_index);
|
||||
return Some((cell, widget_path));
|
||||
widget_path.push(value_index);
|
||||
return Some((value, widget_path));
|
||||
}
|
||||
|
||||
if let Widget::PopoverButton(popover) = &*cell.widget {
|
||||
if let Widget::PopoverButton(popover) = &*value.widget {
|
||||
stack.extend(
|
||||
popover
|
||||
.popover_layout
|
||||
.0
|
||||
.iter()
|
||||
.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!(
|
||||
/// 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,
|
||||
/// Contains the action buttons at the bottom of the dialog. Must be shown with the `FrontendMessage::DisplayDialog` message.
|
||||
DialogButtons,
|
||||
|
|
@ -785,7 +785,7 @@ pub enum Widget {
|
|||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct WidgetDiff {
|
||||
/// 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.
|
||||
#[serde(rename = "widgetPath")]
|
||||
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.
|
||||
/// Drilling into a row's element produces an `Element` step; drilling into one of a row's attributes produces an `Attribute` step.
|
||||
/// One hop in the breadcrumb path through nested data the Data panel is displaying.
|
||||
/// 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)]
|
||||
pub enum PathStep {
|
||||
Element(usize),
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ pub struct DataPanelMessageContext<'a> {
|
|||
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)]
|
||||
pub struct DataPanelMessageHandler {
|
||||
/// 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,
|
||||
desired_path: &'a mut Vec<PathStep>,
|
||||
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
|
||||
/// layers (e.g. inside a Ctrl+M-merged custom subgraph) resolve correctly.
|
||||
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.
|
||||
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 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>>>() {
|
||||
return Some(table_node_id_path_layout_with_breadcrumb(&io.output, data));
|
||||
}
|
||||
|
|
@ -218,22 +218,22 @@ trait TableRowLayout {
|
|||
fn identifier(&self) -> String;
|
||||
fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
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.
|
||||
/// `target` is the [`PathStep`] to push when the cell is clicked to drill into the value.
|
||||
/// Renders this value as a single inline widget inside an item of a Table.
|
||||
/// `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
|
||||
/// 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
|
||||
/// inline (colors, transforms, primitives, etc.) override this to ignore `target` and
|
||||
/// 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())
|
||||
.on_update(move |_| DataPanelMessage::PushToElementPath { step: target.clone() }.into())
|
||||
.narrow(true)
|
||||
.widget_instance()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
|
@ -243,9 +243,9 @@ impl<T: TableRowLayout> TableRowLayout for Table<T> {
|
|||
"Table"
|
||||
}
|
||||
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() {
|
||||
match step {
|
||||
PathStep::Element(index) => {
|
||||
|
|
@ -279,16 +279,16 @@ impl<T: TableRowLayout> TableRowLayout for Table<T> {
|
|||
let mut rows = (0..self.len())
|
||||
.map(|index| {
|
||||
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 {
|
||||
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());
|
||||
TextLabel::new(text).narrow(true).widget_instance()
|
||||
});
|
||||
cells.push(widget);
|
||||
values.push(widget);
|
||||
}
|
||||
cells
|
||||
values
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
|
@ -307,8 +307,8 @@ impl TableRowLayout for Artboard {
|
|||
fn identifier(&self) -> String {
|
||||
self.label.clone()
|
||||
}
|
||||
fn element_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
self.content.element_page(data)
|
||||
fn value_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
self.content.value_page(data)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -328,9 +328,9 @@ impl TableRowLayout for Graphic {
|
|||
}
|
||||
// Don't put a breadcrumb for Graphic
|
||||
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 {
|
||||
Self::Graphic(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" }
|
||||
)
|
||||
}
|
||||
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]
|
||||
.into_iter()
|
||||
.map(|tab| {
|
||||
|
|
@ -508,7 +508,7 @@ impl TableRowLayout for Raster<CPU> {
|
|||
fn identifier(&self) -> String {
|
||||
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();
|
||||
|
||||
if raster.width == 0 || raster.height == 0 {
|
||||
|
|
@ -539,7 +539,7 @@ impl TableRowLayout for Raster<GPU> {
|
|||
fn identifier(&self) -> String {
|
||||
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()];
|
||||
vec![LayoutGroup::row(widgets)]
|
||||
}
|
||||
|
|
@ -552,15 +552,15 @@ impl TableRowLayout for Color {
|
|||
fn identifier(&self) -> String {
|
||||
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))
|
||||
.disabled(true)
|
||||
.menu_direction(Some(MenuDirection::Top))
|
||||
.narrow(true)
|
||||
.widget_instance()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let widgets = vec![self.cell_widget(PathStep::Element(0), _data)];
|
||||
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let widgets = vec![self.value_widget(PathStep::Element(0), _data)];
|
||||
vec![LayoutGroup::row(widgets)]
|
||||
}
|
||||
}
|
||||
|
|
@ -572,15 +572,15 @@ impl TableRowLayout for GradientStops {
|
|||
fn identifier(&self) -> String {
|
||||
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()))
|
||||
.menu_direction(Some(MenuDirection::Top))
|
||||
.disabled(true)
|
||||
.narrow(true)
|
||||
.widget_instance()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let widgets = vec![self.cell_widget(PathStep::Element(0), _data)];
|
||||
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let widgets = vec![self.value_widget(PathStep::Element(0), _data)];
|
||||
vec![LayoutGroup::row(widgets)]
|
||||
}
|
||||
}
|
||||
|
|
@ -592,8 +592,8 @@ impl TableRowLayout for f64 {
|
|||
fn identifier(&self) -> String {
|
||||
format!("{self}")
|
||||
}
|
||||
// Cells fall back to the default drill-in button (labeled with the value via `identifier`); the leaf page shows the rich `NumberInput`.
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
// Values fall back to the default drill-in button (labeled via `identifier`); the value page shows the rich `NumberInput`.
|
||||
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
vec![LayoutGroup::row(vec![
|
||||
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 {
|
||||
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.
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
// 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 value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
vec![LayoutGroup::row(vec![TextLabel::new(self.identifier()).widget_instance()])]
|
||||
}
|
||||
}
|
||||
|
|
@ -620,8 +620,8 @@ impl TableRowLayout for u32 {
|
|||
fn identifier(&self) -> String {
|
||||
format!("{self}")
|
||||
}
|
||||
// Cells fall back to the default drill-in button (labeled with the value via `identifier`); the leaf page shows the rich `NumberInput`.
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
// Values fall back to the default drill-in button (labeled via `identifier`); the value page shows the rich `NumberInput`.
|
||||
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
vec![LayoutGroup::row(vec![
|
||||
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 {
|
||||
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.
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
vec![LayoutGroup::row(vec![
|
||||
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 {
|
||||
"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()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0), _data)])]
|
||||
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -672,8 +672,8 @@ impl TableRowLayout for String {
|
|||
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`.
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
// 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 value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
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 {
|
||||
"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()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0), _data)])]
|
||||
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -700,11 +700,11 @@ impl TableRowLayout for DVec2 {
|
|||
fn identifier(&self) -> 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()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0), _data)])]
|
||||
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -715,11 +715,11 @@ impl TableRowLayout for Vec2 {
|
|||
fn identifier(&self) -> 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()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0), _data)])]
|
||||
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -730,11 +730,11 @@ impl TableRowLayout for DAffine2 {
|
|||
fn identifier(&self) -> 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()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0), _data)])]
|
||||
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -745,12 +745,12 @@ impl TableRowLayout for Affine2 {
|
|||
fn identifier(&self) -> 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));
|
||||
TextLabel::new(format_transform_matrix(matrix)).narrow(true).widget_instance()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0), _data)])]
|
||||
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -761,15 +761,15 @@ impl TableRowLayout for AlphaBlending {
|
|||
fn identifier(&self) -> String {
|
||||
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()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0), _data)])]
|
||||
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
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).
|
||||
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() {
|
||||
|
|
@ -786,16 +786,16 @@ impl TableRowLayout for NodeId {
|
|||
fn identifier(&self) -> String {
|
||||
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> {
|
||||
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
|
||||
// `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.
|
||||
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 mut button = TextButton::new(label)
|
||||
.on_update(move |_| DataPanelMessage::PushToElementPath { step: target.clone() }.into())
|
||||
|
|
@ -806,8 +806,8 @@ impl TableRowLayout for NodeId {
|
|||
}
|
||||
button.widget_instance()
|
||||
}
|
||||
// The leaf 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> {
|
||||
// The value page shows the node's kind, name (editable), lock/visibility toggles, and a "Select Layer/Node" action button.
|
||||
fn value_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let node_id = *self;
|
||||
let network_path = data.node_lookup_network_path.clone();
|
||||
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
|
||||
/// 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.
|
||||
macro_rules! known_table_row_types {
|
||||
($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.
|
||||
/// Delegates to [`TableRowLayout::cell_widget`] so the same widget code is shared between
|
||||
/// Type-dispatched widget for displaying an attribute value in a `Table<T>` item.
|
||||
/// 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
|
||||
/// 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 {
|
||||
( $($ty: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
|
||||
}
|
||||
|
||||
/// Renders a `Table<NodeId>` as a path: the standard table view, but each row's `NodeId` cell is resolved
|
||||
/// against the network path made up of all preceding rows. So for a path `[outer, middle, leaf]`, row 0
|
||||
/// resolves at root, row 1 resolves at `[outer]`, and row 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
|
||||
/// 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 items. So for a path `[outer, middle, leaf]`, item 0
|
||||
/// 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 an item drops into that node's value page
|
||||
/// using the same prefix as `network_path`.
|
||||
fn table_node_id_path_layout_with_breadcrumb(path: &Table<NodeId>, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
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 prefix: Vec<NodeId> = path.iter_element_values().take(index).copied().collect();
|
||||
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;
|
||||
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)]
|
||||
}
|
||||
|
||||
/// Type-dispatched recursion into an attribute value for the data panel breadcrumb navigation.
|
||||
/// Mirrors [`dispatch_cell_widget`] but routes to [`TableRowLayout::layout_with_breadcrumb`].
|
||||
/// Type-dispatched recursion into an attribute value for the Data panel breadcrumb navigation.
|
||||
/// Mirrors [`dispatch_value_widget`] but routes to [`TableRowLayout::layout_with_breadcrumb`].
|
||||
/// Returns `None` for unrecognized types.
|
||||
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
|
||||
// resolves against the prefix made up of preceding rows. Handled before the generic `Table<T>` blanket impl.
|
||||
// `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 items. Handled before the generic `Table<T>` blanket impl.
|
||||
if let Some(path) = any.downcast_ref::<Table<NodeId>>() {
|
||||
return Some(table_node_id_path_layout_with_breadcrumb(path, data));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ pub enum DocumentMessage {
|
|||
undo_count: usize,
|
||||
},
|
||||
ToggleLayerExpansion {
|
||||
instance_path: Vec<NodeId>,
|
||||
tree_path: Vec<NodeId>,
|
||||
recursive: bool,
|
||||
},
|
||||
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
|
||||
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)]
|
||||
pub collapsed: CollapsedLayers,
|
||||
/// 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(PortfolioMessage::UpdateOpenDocumentsList);
|
||||
}
|
||||
DocumentMessage::ToggleLayerExpansion { instance_path, recursive } => {
|
||||
let is_collapsed = self.collapsed.0.contains(&instance_path);
|
||||
DocumentMessage::ToggleLayerExpansion { tree_path, recursive } => {
|
||||
let is_collapsed = self.collapsed.0.contains(&tree_path);
|
||||
|
||||
if is_collapsed {
|
||||
if recursive {
|
||||
// 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 {
|
||||
self.collapsed.0.retain(|path| *path != instance_path);
|
||||
self.collapsed.0.retain(|path| *path != tree_path);
|
||||
}
|
||||
} else {
|
||||
if recursive {
|
||||
// Collapse all expanded descendant instances by collecting their paths from the structure tree
|
||||
let descendant_paths = self.collect_descendant_instance_paths(&instance_path);
|
||||
// Collapse all expanded descendant occurrences by collecting their tree paths from the structure tree
|
||||
let descendant_paths = self.collect_descendant_tree_paths(&tree_path);
|
||||
for path in descendant_paths {
|
||||
if !self.collapsed.0.contains(&path) {
|
||||
self.collapsed.0.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.collapsed.0.push(instance_path);
|
||||
self.collapsed.0.push(tree_path);
|
||||
}
|
||||
|
||||
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 ancestors = HashSet::new();
|
||||
let instance_path = Vec::new();
|
||||
let tree_path = Vec::new();
|
||||
let mut root_entries = Vec::new();
|
||||
|
||||
// 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
|
||||
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) {
|
||||
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,
|
||||
/// `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 {
|
||||
let mut instance_path = parent_instance_path.to_vec();
|
||||
instance_path.push(layer_id);
|
||||
fn build_layer_entry(&self, layer_id: NodeId, ancestors: &HashSet<NodeId>, selected_layers: &HashSet<NodeId>, parent_tree_path: &[NodeId]) -> LayerStructureEntry {
|
||||
let mut tree_path = parent_tree_path.to_vec();
|
||||
tree_path.push(layer_id);
|
||||
|
||||
let mut child_ancestors = ancestors.clone();
|
||||
child_ancestors.insert(layer_id);
|
||||
|
||||
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 {
|
||||
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 {
|
||||
Vec::new()
|
||||
};
|
||||
|
|
@ -1896,7 +1896,7 @@ impl DocumentMessageHandler {
|
|||
/// 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.
|
||||
/// `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;
|
||||
|
||||
// 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
|
||||
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)
|
||||
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) {
|
||||
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
|
||||
}
|
||||
|
||||
/// 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.
|
||||
fn collect_descendant_instance_paths(&self, instance_path: &[NodeId]) -> Vec<Vec<NodeId>> {
|
||||
let Some(&layer_id) = instance_path.last() else { return Vec::new() };
|
||||
fn collect_descendant_tree_paths(&self, tree_path: &[NodeId]) -> Vec<Vec<NodeId>> {
|
||||
let Some(&layer_id) = tree_path.last() else { return Vec::new() };
|
||||
let network = &self.network_interface;
|
||||
|
||||
let mut paths = Vec::new();
|
||||
|
|
@ -1938,7 +1938,7 @@ impl DocumentMessageHandler {
|
|||
// Seed with child layers via horizontal flow
|
||||
for node_id in network.upstream_flow_back_from_nodes(vec![layer_id], &[], FlowType::HorizontalFlow).skip(1) {
|
||||
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);
|
||||
stack.push((node_id, child_path));
|
||||
}
|
||||
|
|
@ -1946,14 +1946,14 @@ impl DocumentMessageHandler {
|
|||
|
||||
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() {
|
||||
// Skip paths we've already visited to prevent cycles
|
||||
if !visited.insert(current_path.clone()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Record this descendant's instance path for collapsing
|
||||
// Record this descendant's tree path for collapsing
|
||||
paths.push(current_path.clone());
|
||||
|
||||
// Add nested content layers found via horizontal flow
|
||||
|
|
@ -2218,18 +2218,18 @@ impl DocumentMessageHandler {
|
|||
responses.add(NodeGraphMessage::SendGraph);
|
||||
|
||||
// The control path layer (Blend Path / Morph Path) should start collapsed.
|
||||
let instance_path = {
|
||||
// Build instance 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 tree_path = {
|
||||
// Build tree path from root down to the control path layer, which is a sibling of the main layer under `parent`.
|
||||
let mut tree_path: Vec<NodeId> = parent
|
||||
.ancestors(network_interface.document_metadata())
|
||||
.take_while(|&ancestor| ancestor != LayerNodeIdentifier::ROOT_PARENT)
|
||||
.map(LayerNodeIdentifier::to_node)
|
||||
.collect();
|
||||
instance_path.reverse();
|
||||
instance_path.push(control_path_id);
|
||||
instance_path
|
||||
tree_path.reverse();
|
||||
tree_path.push(control_path_id);
|
||||
tree_path
|
||||
};
|
||||
responses.add(DocumentMessage::ToggleLayerExpansion { instance_path, recursive: false });
|
||||
responses.add(DocumentMessage::ToggleLayerExpansion { tree_path, recursive: false });
|
||||
|
||||
return folder_id;
|
||||
}
|
||||
|
|
@ -2375,7 +2375,7 @@ impl DocumentMessageHandler {
|
|||
});
|
||||
|
||||
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.
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ pub struct GraphOperationMessageContext<'a> {
|
|||
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize, ExtractField)]
|
||||
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.
|
||||
#[message_handler_data]
|
||||
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`.
|
||||
// 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" {
|
||||
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>)) {
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
self.network_interface.set_input(&input_connector, input, &[]);
|
||||
self.responses.add(PropertiesPanelMessage::Refresh);
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||
implementation: DocumentNodeImplementation::ProtoNode(graphic::path_of_subgraph::IDENTIFIER),
|
||||
..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.
|
||||
DocumentNode {
|
||||
inputs: vec![
|
||||
|
|
@ -343,7 +343,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||
..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,
|
||||
},
|
||||
DocumentNodeDefinition {
|
||||
|
|
@ -374,7 +374,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||
implementation: DocumentNodeImplementation::ProtoNode(graphic::path_of_subgraph::IDENTIFIER),
|
||||
..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.
|
||||
DocumentNode {
|
||||
inputs: vec![
|
||||
|
|
@ -697,7 +697,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||
},
|
||||
..Default::default()
|
||||
},
|
||||
// 4: Multiply (total instances)
|
||||
// 4: Multiply (total items)
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(16, 1)),
|
||||
|
|
@ -1545,7 +1545,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||
exports: vec![
|
||||
// Primary output: the whole match (String)
|
||||
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),
|
||||
],
|
||||
nodes: [
|
||||
|
|
@ -1561,13 +1561,13 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||
implementation: DocumentNodeImplementation::ProtoNode(text_nodes::regex::regex_find::IDENTIFIER),
|
||||
..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 {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::value(TaggedValue::F64(0.), false)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::extract_element::IDENTIFIER),
|
||||
..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 {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::value(TaggedValue::F64(0.), false)],
|
||||
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
|
||||
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 {
|
||||
let mut map: NodeProperties = HashMap::new();
|
||||
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
|
||||
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 {
|
||||
let mut map: InputProperties = HashMap::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);
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
// 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 {
|
||||
|
|
@ -2468,7 +2468,7 @@ impl NodeGraphMessageHandler {
|
|||
}
|
||||
|
||||
// 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![
|
||||
Separator::new(SeparatorStyle::Related).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_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_START_OFFSET: &str = "Exclude some distance from the start of the path before the first instance.";
|
||||
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_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 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) 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;
|
||||
}
|
||||
|
||||
/// 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>>) {
|
||||
self.document_metadata.first_element_source_ids = new;
|
||||
}
|
||||
|
|
@ -6636,11 +6636,11 @@ pub enum NodeTypeTransientMetadata {
|
|||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
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
|
||||
pub layer_width: TransientMetadata<u32>,
|
||||
// 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,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ impl SelectedNodes {
|
|||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[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
|
||||
/// 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>>);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
// Migrate from the old source/target v1 "Morph" node to the new vector table based v2 "Morph" node.
|
||||
// This doesn't produce exactly equivalent results in cases involving input vector tables with multiple rows.
|
||||
// The old version would zip the source and target table rows, interpoleating 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.
|
||||
// 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 `Table<Vector>` values with multiple items.
|
||||
// The old version would zip the source and target items, interpolating each pair together.
|
||||
// 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.
|
||||
if reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::morph::IDENTIFIER) && (inputs_count == 3 || inputs_count == 4) {
|
||||
// 3 inputs - old signature (#3405):
|
||||
|
|
@ -1669,7 +1669,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
|||
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 {
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
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;
|
||||
};
|
||||
|
||||
|
|
@ -430,7 +430,7 @@ pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInter
|
|||
tilt,
|
||||
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> {
|
||||
|
|
|
|||
|
|
@ -1281,7 +1281,7 @@ impl ShapeState {
|
|||
};
|
||||
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();
|
||||
|
||||
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.
|
||||
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]);
|
||||
};
|
||||
|
||||
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.
|
||||
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.)
|
||||
} else {
|
||||
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> {
|
||||
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
|
||||
.iter()
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@
|
|||
const isViewport = placeholder.hasAttribute("data-is-viewport");
|
||||
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) {
|
||||
const newCanvas = window.document.createElement("canvas");
|
||||
const context = newCanvas.getContext("2d");
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
ancestorOfSelected: boolean;
|
||||
parentsVisible: boolean;
|
||||
parentsUnlocked: boolean;
|
||||
instancePath: bigint[];
|
||||
treePath: bigint[];
|
||||
};
|
||||
|
||||
type DraggingData = {
|
||||
|
|
@ -140,10 +140,10 @@
|
|||
editor.toggleLayerLock(id);
|
||||
}
|
||||
|
||||
function handleExpandArrowClickWithModifiers(e: MouseEvent, instancePath: bigint[]) {
|
||||
function handleExpandArrowClickWithModifiers(e: MouseEvent, treePath: bigint[]) {
|
||||
const accel = operatingSystem() === "Mac" ? e.metaKey : e.ctrlKey;
|
||||
const collapseRecursive = e.altKey || accel;
|
||||
editor.toggleLayerExpansion(BigUint64Array.from(instancePath), collapseRecursive);
|
||||
editor.toggleLayerExpansion(BigUint64Array.from(treePath), collapseRecursive);
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
|
|
@ -514,7 +514,7 @@
|
|||
// Build the new layer hierarchy
|
||||
const recurse = (children: LayerStructureEntry[], depth: number, parentId: bigint | undefined, parentPath: bigint[], parentsVisible: boolean, parentsUnlocked: boolean) => {
|
||||
children.forEach((item, index) => {
|
||||
const instancePath = [...parentPath, item.layerId];
|
||||
const treePath = [...parentPath, item.layerId];
|
||||
const mapping = layerCache.get(String(item.layerId));
|
||||
|
||||
if (mapping) {
|
||||
|
|
@ -531,14 +531,14 @@
|
|||
ancestorOfSelected: item.descendantSelected,
|
||||
parentsVisible,
|
||||
parentsUnlocked,
|
||||
instancePath,
|
||||
treePath,
|
||||
});
|
||||
}
|
||||
|
||||
// Call self recursively, propagating this layer's visibility/lock state to its children
|
||||
const childParentsVisible = parentsVisible && (mapping?.visible ?? 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);
|
||||
|
|
@ -605,7 +605,7 @@
|
|||
: "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" : "")}
|
||||
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"
|
||||
></button>
|
||||
{:else}
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@
|
|||
<tbody>
|
||||
{#each widgetData.tableWidgets as row}
|
||||
<tr>
|
||||
{#each row as cell}
|
||||
{#each row as widget}
|
||||
<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>
|
||||
{/each}
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -948,9 +948,9 @@ impl EditorWrapper {
|
|||
|
||||
/// Toggle expansions state of a layer from the layer list
|
||||
#[wasm_bindgen(js_name = toggleLayerExpansion)]
|
||||
pub fn toggle_layer_expansion(&self, instance_path: &[u64], recursive: bool) {
|
||||
let instance_path = instance_path.iter().map(|&id| NodeId(id)).collect();
|
||||
let message = DocumentMessage::ToggleLayerExpansion { instance_path, recursive };
|
||||
pub fn toggle_layer_expansion(&self, tree_path: &[u64], recursive: bool) {
|
||||
let tree_path = tree_path.iter().map(|&id| NodeId(id)).collect();
|
||||
let message = DocumentMessage::ToggleLayerExpansion { tree_path, recursive };
|
||||
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.
|
||||
Some(match concrete_type.id? {
|
||||
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<GradientStops>>() => TaggedValue::GradientTable(Table::new_from_element(GradientStops::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.
|
||||
//! 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>;
|
||||
|
||||
/// 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.
|
||||
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.
|
||||
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>)
|
||||
}
|
||||
|
||||
|
|
@ -346,8 +346,8 @@ impl AttributeColumns {
|
|||
// Push values into existing columns, or a default if the row lacks that attribute
|
||||
for (column_key, column) in &mut self.columns {
|
||||
if let Some(position) = row_entries.iter().position(|(k, _)| k == column_key) {
|
||||
let (_, cell_value) = row_entries.swap_remove(position);
|
||||
column.push(cell_value);
|
||||
let (_, value) = row_entries.swap_remove(position);
|
||||
column.push(value);
|
||||
} else {
|
||||
column.push_default();
|
||||
}
|
||||
|
|
@ -389,8 +389,8 @@ impl AttributeColumns {
|
|||
self.len += other_len;
|
||||
}
|
||||
|
||||
/// Gets a reference to a cell value at the given index from the column for the given key.
|
||||
fn get_cell<T: 'static>(&self, key: &str, index: usize) -> Option<&T> {
|
||||
/// Gets a reference to the value at the given index from the column for the given key.
|
||||
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 })
|
||||
}
|
||||
|
||||
|
|
@ -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.
|
||||
fn get_or_insert_default_cell<T: Clone + Send + Sync + Default + Debug + 'static>(&mut self, key: &str, index: usize) -> &mut T {
|
||||
/// 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_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 = (*self.columns[column_position].1).as_any_mut().downcast_mut::<Column<T>>().unwrap();
|
||||
&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.
|
||||
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 column_position = self.find_or_create_column::<T>(&key);
|
||||
let column = (*self.columns[column_position].1).as_any_mut().downcast_mut::<Column<T>>().unwrap();
|
||||
column.0[index] = value;
|
||||
}
|
||||
|
||||
/// Returns a debug-formatted string for a cell 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> {
|
||||
/// Returns a debug-formatted string for the value at the given index in the column for the given key.
|
||||
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)| {
|
||||
if k == key {
|
||||
if let Some(cell) = column.get_any(index)
|
||||
&& let Some(text) = overrides(cell)
|
||||
if let Some(value) = column.get_any(index)
|
||||
&& let Some(text) = overrides(value)
|
||||
{
|
||||
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.
|
||||
fn get_any_cell(&self, key: &str, index: usize) -> Option<&dyn std::any::Any> {
|
||||
/// Returns a type-erased reference to the value at the given index in the column for the given key.
|
||||
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 })
|
||||
}
|
||||
|
||||
|
|
@ -487,8 +487,8 @@ impl AttributeColumns {
|
|||
let mut attributes = AttributeValues::new();
|
||||
|
||||
for (key, column) in &self.columns {
|
||||
if let Some(cell) = column.clone_cell(index) {
|
||||
attributes.0.push((key.clone(), cell));
|
||||
if let Some(value) = column.clone_value(index) {
|
||||
attributes.0.push((key.clone(), value));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -500,8 +500,8 @@ impl AttributeColumns {
|
|||
let mut rows: Vec<AttributeValues> = (0..self.len).map(|_| AttributeValues::new()).collect();
|
||||
|
||||
for (key, column) in self.columns {
|
||||
for (i, cell) in column.drain().into_iter().enumerate() {
|
||||
rows[i].0.push((key.clone(), cell));
|
||||
for (i, value) in column.drain().into_iter().enumerate() {
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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,
|
||||
/// 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 {
|
||||
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.
|
||||
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.
|
||||
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
|
||||
// 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)? {
|
||||
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(),
|
||||
|
|
|
|||
|
|
@ -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`)
|
||||
/// and discarding all other non-matching content. Recursion through `Graphic::Graphic` sub-tables composes transforms and opacity.
|
||||
/// 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-`Table`s composes transforms and opacity.
|
||||
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 {
|
||||
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");
|
||||
|
||||
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) => {
|
||||
for index in 0..sub_table.len() {
|
||||
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);
|
||||
}
|
||||
// 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 => {
|
||||
if let Some(typed_table) = extract_variant(other) {
|
||||
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,
|
||||
/// 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 {
|
||||
fn try_from_graphic(graphic: Graphic) -> Option<Table<Self>>;
|
||||
}
|
||||
|
|
@ -217,7 +217,7 @@ impl TryFromGraphic for GradientStops {
|
|||
pub trait IntoGraphicTable {
|
||||
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>
|
||||
where
|
||||
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
|
||||
// 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)? {
|
||||
GraphicFormat::OldGraphicGroup(old) => old.elements.into_iter().map(|(graphic, _)| TableRow::new_from_element(graphic)).collect(),
|
||||
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)))
|
||||
.collect(),
|
||||
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()) {
|
||||
let mut graphic_table = Table::new();
|
||||
for index in 0..old_table.len() {
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ pub mod migrations {
|
|||
Ok(match VectorFormat::deserialize(deserializer)? {
|
||||
VectorFormat::Vector(vector) => Table::new_from_element(vector),
|
||||
// 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 {
|
||||
style: old.style,
|
||||
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
|
||||
// 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> {
|
||||
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
|
||||
// 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)? {
|
||||
FormatVersions::Image(image) => TableRow::new_from_element(Raster::new_cpu(image)),
|
||||
FormatVersions::OldImageFrame(old) => TableRow::new_from_element(Raster::new_cpu(old.image)),
|
||||
|
|
|
|||
|
|
@ -410,7 +410,7 @@ impl Render for Graphic {
|
|||
}
|
||||
Graphic::Vector(table) => {
|
||||
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() {
|
||||
let layer_path: Table<NodeId> = table.attribute_cloned_or_default("editor:layer", 0);
|
||||
let layer = layer_path.iter_element_values().next_back().copied();
|
||||
|
|
@ -423,7 +423,7 @@ impl Render for Graphic {
|
|||
Graphic::RasterCPU(table) => {
|
||||
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() {
|
||||
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default("transform", 0));
|
||||
}
|
||||
|
|
@ -431,7 +431,7 @@ impl Render for Graphic {
|
|||
Graphic::RasterGPU(table) => {
|
||||
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() {
|
||||
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default("transform", 0));
|
||||
}
|
||||
|
|
@ -439,7 +439,7 @@ impl Render for Graphic {
|
|||
Graphic::Color(table) => {
|
||||
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() {
|
||||
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default("transform", 0));
|
||||
}
|
||||
|
|
@ -447,7 +447,7 @@ impl Render for Graphic {
|
|||
Graphic::Gradient(table) => {
|
||||
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() {
|
||||
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 {
|
||||
element.collect_metadata(metadata, footprint, Some(element_id));
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1334,7 +1334,7 @@ impl Render for Table<Vector> {
|
|||
let layer = layer_path.iter_element_values().next_back().copied();
|
||||
|
||||
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
|
||||
// normally provides but skipped due to the None element_id.
|
||||
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()));
|
||||
}
|
||||
|
||||
// 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
|
||||
// surface the original child layers' click targets.
|
||||
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.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() {
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default("transform", 0);
|
||||
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.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() {
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default("transform", 0);
|
||||
metadata.local_transforms.insert(element_id, transform);
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ impl<PointId: Identifier> Subpath<PointId> {
|
|||
let mut intersections_vec = Vec::new();
|
||||
let err = accuracy.unwrap_or(MAX_ABSOLUTE_DIFFERENCE);
|
||||
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)| {
|
||||
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)| {
|
||||
|
|
|
|||
|
|
@ -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>> {
|
||||
async fn convert(self, _: Footprint, _converter: &'i WgpuExecutor) -> Table<Raster<GPU>> {
|
||||
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>> {
|
||||
async fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> Table<Raster<GPU>> {
|
||||
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>> {
|
||||
async fn convert(self, _: Footprint, _converter: &'i WgpuExecutor) -> Table<Raster<CPU>> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts GPU raster table to CPU by downloading texture data in one go
|
||||
///
|
||||
/// then asynchronously maps all buffers and processes the results.
|
||||
/// 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.
|
||||
impl<'i> Convert<Table<Raster<CPU>>, &'i WgpuExecutor> for Table<Raster<GPU>> {
|
||||
async fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> Table<Raster<CPU>> {
|
||||
let device = &executor.context.device;
|
||||
|
|
@ -218,7 +216,7 @@ impl<'i> Convert<Table<Raster<CPU>>, &'i WgpuExecutor> for Table<Raster<GPU>> {
|
|||
|
||||
map_results
|
||||
.into_iter()
|
||||
.zip(rows_meta.into_iter())
|
||||
.zip(rows_meta)
|
||||
.map(|(element, row)| {
|
||||
let (_, attributes) = row.into_parts();
|
||||
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.
|
||||
///
|
||||
/// 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(""))]
|
||||
pub async fn upload_texture<'a: 'n, T: Convert<Table<Raster<GPU>>, &'a WgpuExecutor>>(
|
||||
_: 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.
|
||||
blend_mode: BlendMode,
|
||||
) -> 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
|
||||
}
|
||||
|
|
@ -216,7 +216,7 @@ fn opacity<T: MultiplyAlpha>(
|
|||
#[default(100.)]
|
||||
opacity: Percentage,
|
||||
) -> 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
|
||||
}
|
||||
|
|
@ -247,7 +247,7 @@ fn blending<T: SetBlendMode + MultiplyAlpha + MultiplyFill + SetClip>(
|
|||
/// Whether the content inherits the alpha of the content beneath it.
|
||||
clip: bool,
|
||||
) -> 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.multiply_alpha(opacity / 100.);
|
||||
content.multiply_fill(fill / 100.);
|
||||
|
|
|
|||
|
|
@ -199,8 +199,8 @@ async fn brush(
|
|||
if image.is_empty() {
|
||||
image.push(TableRow::default());
|
||||
}
|
||||
// TODO: Find a way to handle more than one row
|
||||
let table_row = image.clone_row(0).expect("Expected the one row we just pushed");
|
||||
// TODO: Find a way to handle more than one item
|
||||
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 [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);
|
||||
|
||||
// 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 {
|
||||
return Table::new();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ impl BrushCacheImpl {
|
|||
background = std::mem::take(&mut self.blended_image);
|
||||
|
||||
// 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_point_skip = 0;
|
||||
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: (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.
|
||||
#[node_macro::node(category("Context"), path(core_types::vector))]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use graphic_types::{
|
|||
use raster_types::{CPU, GPU, Raster};
|
||||
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(""))]
|
||||
pub async fn create_artboard<T: IntoGraphicTable + 'n>(
|
||||
ctx: impl ExtractAll + CloneVarArgs + Ctx,
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ use graphic_types::{Artboard, Vector};
|
|||
use raster_types::{CPU, GPU, Raster};
|
||||
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.
|
||||
#[node_macro::node(category("General"))]
|
||||
pub fn index_elements<T: graphic_types::graphic::AtIndex + Clone + Default>(
|
||||
_: impl Ctx,
|
||||
/// The collection of data, such as a list or table.
|
||||
/// The list of data.
|
||||
#[implementations(
|
||||
Table<Artboard>,
|
||||
Table<Graphic>,
|
||||
|
|
@ -28,8 +28,8 @@ pub fn index_elements<T: graphic_types::graphic::AtIndex + Clone + Default>(
|
|||
Table<u8>,
|
||||
Table<NodeId>,
|
||||
)]
|
||||
collection: 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.
|
||||
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 list, starting from -1 for the last item.
|
||||
index: SignedInteger,
|
||||
) -> T::Output
|
||||
where
|
||||
|
|
@ -37,20 +37,15 @@ where
|
|||
{
|
||||
let index = index as i32;
|
||||
|
||||
if index < 0 {
|
||||
collection.at_index_from_end(-index as usize)
|
||||
} else {
|
||||
collection.at_index(index as usize)
|
||||
}
|
||||
.unwrap_or_default()
|
||||
if index < 0 { list.at_index_from_end(-index as usize) } else { list.at_index(index as usize) }.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Returns the collection with the element at the specified index removed.
|
||||
/// If no value exists at that index, the collection is returned unchanged.
|
||||
/// Returns the list with the element at the specified index removed.
|
||||
/// If no value exists at that index, the list is returned unchanged.
|
||||
#[node_macro::node(category("General"))]
|
||||
pub fn omit_element<T: graphic_types::graphic::OmitIndex + Clone + Default>(
|
||||
_: impl Ctx,
|
||||
/// The collection of data, such as a list or table.
|
||||
/// The list of data.
|
||||
#[implementations(
|
||||
Table<String>,
|
||||
Table<Artboard>,
|
||||
|
|
@ -61,26 +56,26 @@ pub fn omit_element<T: graphic_types::graphic::OmitIndex + Clone + Default>(
|
|||
Table<Color>,
|
||||
Table<GradientStops>,
|
||||
)]
|
||||
collection: 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.
|
||||
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 list, starting from -1 for the last item.
|
||||
index: SignedInteger,
|
||||
) -> T {
|
||||
let index = index as i32;
|
||||
|
||||
if index < 0 {
|
||||
collection.omit_index_from_end(index.unsigned_abs() as usize)
|
||||
list.omit_index_from_end(index.unsigned_abs() as usize)
|
||||
} 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.
|
||||
/// Use this when downstream nodes want just the inner value rather than a single-row 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 `Table` containing a single item.
|
||||
/// If no value exists at that index, the element type's default is returned.
|
||||
#[node_macro::node(category("General"))]
|
||||
pub fn extract_element<T: Clone + Default + Send + Sync + 'static>(
|
||||
_: impl Ctx,
|
||||
/// The table of data to extract from.
|
||||
/// The `Table` of data to extract from.
|
||||
#[implementations(
|
||||
Table<String>,
|
||||
Table<f64>,
|
||||
|
|
@ -94,7 +89,7 @@ pub fn extract_element<T: Clone + Default + Send + Sync + 'static>(
|
|||
Table<Artboard>,
|
||||
)]
|
||||
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,
|
||||
) -> T {
|
||||
let len = table.len();
|
||||
|
|
@ -192,14 +187,14 @@ where
|
|||
|
||||
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 {
|
||||
for instance in content.clone().into_iter() {
|
||||
result_table.push(instance);
|
||||
for item in content.clone().into_iter() {
|
||||
result_table.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
// Create and add mirrored instance
|
||||
// Create and add mirrored items
|
||||
for mut row in content.into_iter() {
|
||||
let current_transform: DAffine2 = row.attribute_cloned_or_default("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`
|
||||
/// 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.
|
||||
/// 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
|
||||
/// the layer is at the root document network or nested inside a custom subgraph.
|
||||
#[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()
|
||||
}
|
||||
|
||||
/// Writes a per-row attribute column on the input table. The value-producing input is evaluated once per row,
|
||||
/// with the row's element index and the row itself (as a single-row table vararg) passed via context, so the
|
||||
/// upstream pipeline can return a different value per row that may be derived from the row's own data.
|
||||
/// If the column already exists, its values are replaced; if not, the column is created.
|
||||
/// Writes a named attribute on each item of the input `Table`. The value-producing input is evaluated once per item,
|
||||
/// with the item's index and the item itself (as a `Table` containing only that item, passed as a vararg) provided via
|
||||
/// context, so the upstream pipeline can return a different value per item that may be derived from the item's own data.
|
||||
/// If the attribute already exists, its values are replaced; if not, the attribute is added.
|
||||
#[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>(
|
||||
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(
|
||||
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>,
|
||||
|
|
@ -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>,
|
||||
)]
|
||||
mut content: Table<T>,
|
||||
/// The attribute name (column key) to write or replace.
|
||||
/// The attribute name (key) to write or replace.
|
||||
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(
|
||||
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
|
||||
}
|
||||
|
||||
/// 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"))]
|
||||
pub async fn extend<T: 'n + Send + Clone>(
|
||||
_: 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>)]
|
||||
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]
|
||||
#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)]
|
||||
new: Table<T>,
|
||||
|
|
@ -327,8 +322,8 @@ pub async fn wrap_graphic<T: Into<Graphic> + 'n>(
|
|||
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.
|
||||
/// If it is already a graphic table, it is not wrapped again. Use the 'Wrap Graphic' node if wrapping is always desired.
|
||||
/// 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 `Table<Graphic>`, it is not wrapped again. Use the 'Wrap Graphic' node if wrapping is always desired.
|
||||
#[node_macro::node(category("General"))]
|
||||
pub async fn to_graphic<T: IntoGraphicTable + 'n>(
|
||||
_: impl Ctx,
|
||||
|
|
@ -345,7 +340,7 @@ pub async fn to_graphic<T: IntoGraphicTable + 'n>(
|
|||
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"))]
|
||||
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>?
|
||||
|
|
@ -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);
|
||||
}
|
||||
// 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);
|
||||
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
|
||||
}
|
||||
|
||||
/// 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"))]
|
||||
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()
|
||||
}
|
||||
|
||||
/// 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"))]
|
||||
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()
|
||||
}
|
||||
|
||||
/// 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"))]
|
||||
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()
|
||||
}
|
||||
|
||||
/// 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"))]
|
||||
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()
|
||||
}
|
||||
|
||||
/// 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"))]
|
||||
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>();
|
||||
|
|
|
|||
|
|
@ -14,14 +14,14 @@ use vector_types::kurbo::{Affine, BezPath, CubicBez, Line, ParamCurve, PathSeg,
|
|||
pub use vector_types::vector::misc::BooleanOperation;
|
||||
|
||||
// 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: with multiple rows while still assuming a single row for the boolean operations.
|
||||
// TODO: since before we used a Vec of single-item `Table`s and now we use a single `Table`
|
||||
// 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.
|
||||
#[node_macro::node(category("Vector: Modifier"), memoize)]
|
||||
async fn boolean_operation<I: graphic_types::IntoGraphicTable + 'n + Send + Clone>(
|
||||
_: 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>)]
|
||||
content: I,
|
||||
/// 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);
|
||||
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.
|
||||
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 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);
|
||||
let copy_from = vector.element(index).unwrap();
|
||||
let element = Vector {
|
||||
|
|
@ -166,7 +166,7 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
|
|||
let graphic = graphic_table.element(index).unwrap();
|
||||
match graphic.clone() {
|
||||
Graphic::Vector(vector) => {
|
||||
// Apply the parent graphic's transform to each element of the vector table
|
||||
// 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);
|
||||
vector
|
||||
.into_iter()
|
||||
|
|
@ -191,7 +191,7 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
|
|||
.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)
|
||||
// back to the originating raster layer
|
||||
(0..image.len())
|
||||
|
|
@ -217,7 +217,7 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
|
|||
.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)
|
||||
// back to the originating raster layer
|
||||
(0..image.len())
|
||||
|
|
@ -231,12 +231,12 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
|
|||
}
|
||||
Graphic::Graphic(mut graphic) => {
|
||||
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default("transform", index);
|
||||
// Apply the parent graphic's transform to each element of inner table
|
||||
// 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") {
|
||||
*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 unioned = boolean_operation_on_vector_table(&flattened, BooleanOperation::Union);
|
||||
|
||||
|
|
|
|||
|
|
@ -112,13 +112,13 @@ pub fn combine_channels(
|
|||
.zip(blue)
|
||||
.zip(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 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 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())?;
|
||||
|
||||
// Get the common width and height of the channels, which must have equal dimensions
|
||||
|
|
@ -183,7 +183,7 @@ pub fn mask(
|
|||
#[expose]
|
||||
stencil: 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 {
|
||||
// No stencil provided so we return the original 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("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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ async fn repeat<T: Into<Graphic> + Default + Send + Clone + 'static>(
|
|||
Context -> Table<Color>,
|
||||
Context -> Table<GradientStops>,
|
||||
)]
|
||||
instance: impl Node<'n, Context<'static>, Output = Table<T>>,
|
||||
content: impl Node<'n, Context<'static>, Output = Table<T>>,
|
||||
#[default(1)]
|
||||
#[hard_min(1)]
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -54,7 +54,7 @@ pub async fn repeat_array<T: Into<Graphic> + Default + Send + Clone + 'static>(
|
|||
Context -> Table<Color>,
|
||||
Context -> Table<GradientStops>,
|
||||
)]
|
||||
instance: impl Node<'n, Context<'static>, Output = Table<T>>,
|
||||
content: impl Node<'n, Context<'static>, Output = Table<T>>,
|
||||
#[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.
|
||||
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 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() {
|
||||
let Some(mut row) = generated_instance.clone_row(row_index) else { continue };
|
||||
for row_index in 0..generated_content.len() {
|
||||
let Some(mut row) = generated_content.clone_row(row_index) else { continue };
|
||||
|
||||
let local_transform: DAffine2 = row.attribute_cloned_or_default("transform");
|
||||
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<GradientStops>,
|
||||
)]
|
||||
instance: impl Node<'n, Context<'static>, Output = Table<T>>,
|
||||
content: impl Node<'n, Context<'static>, Output = Table<T>>,
|
||||
start_angle: Angle,
|
||||
#[unit(" px")]
|
||||
#[default(5)]
|
||||
|
|
@ -121,10 +121,10 @@ async fn repeat_radial<T: Into<Graphic> + Default + Send + Clone + 'static>(
|
|||
let transform = angle * translation;
|
||||
|
||||
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() {
|
||||
let Some(mut row) = generated_instance.clone_row(row_index) else { continue };
|
||||
for row_index in 0..generated_content.len() {
|
||||
let Some(mut row) = generated_content.clone_row(row_index) else { continue };
|
||||
|
||||
let local_transform: DAffine2 = row.attribute_cloned_or_default("transform");
|
||||
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<GradientStops>,
|
||||
)]
|
||||
instance: impl Node<'n, Context<'static>, Output = Table<T>>,
|
||||
content: impl Node<'n, Context<'static>, Output = Table<T>>,
|
||||
reverse: bool,
|
||||
) -> Table<T> {
|
||||
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 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;
|
||||
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.
|
||||
///
|
||||
/// 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:
|
||||
/// • **Index Elements**: access the `N`th query result.
|
||||
|
|
|
|||
|
|
@ -19,11 +19,11 @@ pub struct PathBuilder {
|
|||
}
|
||||
|
||||
impl PathBuilder {
|
||||
pub fn new(per_glyph_instances: bool, scale: f64) -> Self {
|
||||
pub fn new(per_glyph_items: bool, scale: f64) -> Self {
|
||||
Self {
|
||||
current_subpath: Subpath::new(Vec::new(), false),
|
||||
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,
|
||||
id: PointId::ZERO,
|
||||
origin: DVec2::default(),
|
||||
|
|
@ -35,7 +35,7 @@ impl PathBuilder {
|
|||
}
|
||||
|
||||
#[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 settings = DrawSettings::unhinted(Size::new(size), location_ref);
|
||||
glyph.draw(settings, self).unwrap();
|
||||
|
|
@ -50,18 +50,18 @@ impl PathBuilder {
|
|||
glyph_subpath.apply_transform(skew);
|
||||
}
|
||||
|
||||
if per_glyph_instances {
|
||||
if per_glyph_items {
|
||||
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)));
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 run_y = glyph_run.baseline();
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ impl PathBuilder {
|
|||
|
||||
// User-requested tilt applied around baseline to avoid vertical displacement
|
||||
// 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.])
|
||||
} else {
|
||||
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
|
||||
// This preserves the distinction between font styling and user transformations
|
||||
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.])
|
||||
} else {
|
||||
DAffine2::from_translation(DVec2::new(0., run_y as f64))
|
||||
|
|
@ -107,10 +107,10 @@ impl PathBuilder {
|
|||
|
||||
let glyph_id = GlyphId::from(glyph.id);
|
||||
if let Some(glyph_outline) = outlines.get(glyph_id) {
|
||||
if !per_glyph_instances {
|
||||
if !per_glyph_items {
|
||||
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.
|
||||
///
|
||||
/// 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).
|
||||
#[node_macro::node(category(""))]
|
||||
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.
|
||||
///
|
||||
/// 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"))]
|
||||
fn regex_find_all(
|
||||
_: impl Ctx,
|
||||
|
|
|
|||
|
|
@ -87,19 +87,19 @@ impl TextContext {
|
|||
}
|
||||
|
||||
/// 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 {
|
||||
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 item in line.items() {
|
||||
if let PositionedLayoutItem::GlyphRun(glyph_run) = item
|
||||
&& 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 vector_types::Vector;
|
||||
|
||||
pub fn to_path(text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig, per_glyph_instances: bool) -> Table<Vector> {
|
||||
TextContext::with_thread_local(|ctx| ctx.to_path(text, font, font_cache, typesetting, per_glyph_instances))
|
||||
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_items))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
/// 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"))]
|
||||
fn replace_transform<T>(
|
||||
_: impl Ctx + InjectFootprint,
|
||||
|
|
@ -109,7 +109,7 @@ fn replace_transform<T>(
|
|||
}
|
||||
|
||||
// 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))]
|
||||
async fn extract_transform<T>(
|
||||
_: 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 });
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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::{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>`.
|
||||
trait VectorTableIterMut {
|
||||
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.
|
||||
#[expose]
|
||||
#[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Color>, Table<GradientStops>)]
|
||||
instance: Table<I>,
|
||||
/// Minimum range of randomized sizes given to each instance.
|
||||
content: Table<I>,
|
||||
/// Minimum range of randomized sizes given to each placed copy.
|
||||
#[default(1)]
|
||||
#[range((0., 2.))]
|
||||
#[unit("x")]
|
||||
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)]
|
||||
#[range((0., 2.))]
|
||||
#[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).
|
||||
#[range((-50., 50.))]
|
||||
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,
|
||||
/// 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.))]
|
||||
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,
|
||||
) -> Table<I> {
|
||||
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);
|
||||
|
||||
for row_index in 0..instance.len() {
|
||||
let Some(mut row) = instance.clone_row(row_index) else { continue };
|
||||
for row_index in 0..content.len() {
|
||||
let Some(mut row) = content.clone_row(row_index) else { continue };
|
||||
let row_transform: DAffine2 = row.attribute_cloned_or_default("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);
|
||||
|
||||
// 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.set_attribute("transform", DAffine2::IDENTITY);
|
||||
row
|
||||
|
|
@ -797,7 +797,7 @@ where
|
|||
let mut items: Vec<(f64, f64, DVec2, TableRow<T>)> = elements
|
||||
.into_iter()
|
||||
.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 (w, h, top_left) = match single.bounding_box(DAffine2::IDENTITY, false) {
|
||||
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));
|
||||
}
|
||||
|
||||
// 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 fill_row = has_fill.then(|| {
|
||||
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);
|
||||
|
||||
// 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 {
|
||||
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<_>>(),
|
||||
|
|
@ -1289,7 +1289,7 @@ pub async fn flatten_path<T: IntoGraphicTable + 'n + Send>(_: impl Ctx, #[implem
|
|||
let graphic_table = content.into_graphic_table();
|
||||
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 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();
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 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);
|
||||
|
||||
// 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() {
|
||||
let primary = flattened.len() - 1;
|
||||
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();
|
||||
|
||||
// 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 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 {
|
||||
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
|
||||
// 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
|
||||
// get the correct footprint: parent * group_transform * child_transform.
|
||||
// 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()
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[node_macro::node(category("Vector: Measure"), path(graphene_core::vector))]
|
||||
async fn index_points(
|
||||
|
|
|
|||
Loading…
Reference in New Issue