Refactor the TypeScript data flow for full type safety and auto-generation of Rust types (#3865)

* Migrate Specta to Tsify to auto-generate messages.ts, working except colors and widgets

* Adopt the generated FillColor/Color/GradientStops

* Fix widget typing

* Separate WidgetGroup enum variants into wrapper structs

* Small rename

* Simplify widgets further

* Clean up message type references

* Switch type imports to the auto-generated file

* Remove lowercase serde rename

* Fix FillChoice deserialization

* Fix small regression from #3837

* Improve type safety

* Make WidgetSpan type-safe

* More cleanup and type safety

* More type safety

* More type safety

* Get the rest to type-check without errors; improve widget builder macro to have optional icons; improve Svelte 5 configs

* Cargo fmt

* Fix imports

* Update outdated readme info

* Fix lint command rename references

* Fix typos

* One more typos fix

* Remove unnecessary dep: prefix from the edited Cargo.toml files

* Remove excess parts from Cargo.toml

* Fix compiling on desktop

* Revert "Remove excess parts from Cargo.toml"

This reverts commit 6b711117b3a5d5d8a3ee20f36a43bc74930b7c82.

* Update dev docs with simpler, more accurate instructions
This commit is contained in:
Keavon Chambers
2026-03-09 16:35:04 -07:00
parent fbd2658148
commit 52d2b38a82
199 changed files with 2265 additions and 2811 deletions
@@ -128,7 +128,7 @@ impl DataPanelMessageHandler {
}
if !widgets.is_empty() {
layout.0.insert(0, LayoutGroup::Row { widgets });
layout.0.insert(0, LayoutGroup::row(widgets));
}
responses.add(LayoutMessage::SendLayout {
@@ -185,7 +185,7 @@ fn column_headings(value: &[&str]) -> Vec<WidgetInstance> {
fn label(x: impl Into<String>) -> Vec<LayoutGroup> {
let error = vec![TextLabel::new(x).widget_instance()];
vec![LayoutGroup::Row { widgets: error }]
vec![LayoutGroup::row(error)]
}
trait TableRowLayout {
@@ -234,7 +234,7 @@ impl<T: TableRowLayout> TableRowLayout for Vec<T> {
rows.insert(0, column_headings(&["", "element"]));
vec![LayoutGroup::Table { rows, unstyled: false }]
vec![LayoutGroup::table(rows, false)]
}
}
@@ -276,7 +276,7 @@ impl<T: TableRowLayout> TableRowLayout for Table<T> {
rows.insert(0, column_headings(&["", "element", "transform", "alpha_blending", "source_node_id"]));
vec![LayoutGroup::Table { rows, unstyled: false }]
vec![LayoutGroup::table(rows, false)]
}
}
@@ -488,7 +488,7 @@ impl TableRowLayout for Vector {
}
}
vec![LayoutGroup::Row { widgets: table_tabs }, LayoutGroup::Table { rows: table_rows, unstyled: false }]
vec![LayoutGroup::row(table_tabs), LayoutGroup::table(table_rows, false)]
}
}
@@ -504,7 +504,7 @@ impl TableRowLayout for Raster<CPU> {
if raster.width == 0 || raster.height == 0 {
let widgets = vec![TextLabel::new("Image has no area").widget_instance()];
return vec![LayoutGroup::Row { widgets }];
return vec![LayoutGroup::row(widgets)];
}
let base64_string = raster.base64_string.clone().unwrap_or_else(|| {
@@ -519,7 +519,7 @@ impl TableRowLayout for Raster<CPU> {
});
let widgets = vec![ImageLabel::new(base64_string).widget_instance()];
vec![LayoutGroup::Row { widgets }]
vec![LayoutGroup::row(widgets)]
}
}
@@ -532,7 +532,7 @@ impl TableRowLayout for Raster<GPU> {
}
fn element_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 }]
vec![LayoutGroup::row(widgets)]
}
}
@@ -552,7 +552,7 @@ impl TableRowLayout for Color {
}
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
let widgets = vec![self.element_widget(0)];
vec![LayoutGroup::Row { widgets }]
vec![LayoutGroup::row(widgets)]
}
}
@@ -572,7 +572,7 @@ impl TableRowLayout for GradientStops {
}
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
let widgets = vec![self.element_widget(0)];
vec![LayoutGroup::Row { widgets }]
vec![LayoutGroup::row(widgets)]
}
}
@@ -585,7 +585,7 @@ impl TableRowLayout for f64 {
}
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
let widgets = vec![TextLabel::new(self.to_string()).widget_instance()];
vec![LayoutGroup::Row { widgets }]
vec![LayoutGroup::row(widgets)]
}
}
@@ -598,7 +598,7 @@ impl TableRowLayout for u32 {
}
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
let widgets = vec![TextLabel::new(self.to_string()).widget_instance()];
vec![LayoutGroup::Row { widgets }]
vec![LayoutGroup::row(widgets)]
}
}
@@ -611,7 +611,7 @@ impl TableRowLayout for u64 {
}
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
let widgets = vec![TextLabel::new(self.to_string()).widget_instance()];
vec![LayoutGroup::Row { widgets }]
vec![LayoutGroup::row(widgets)]
}
}
@@ -624,7 +624,7 @@ impl TableRowLayout for bool {
}
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
let widgets = vec![TextLabel::new(self.to_string()).widget_instance()];
vec![LayoutGroup::Row { widgets }]
vec![LayoutGroup::row(widgets)]
}
}
@@ -643,7 +643,7 @@ impl TableRowLayout for String {
}
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
let widgets = vec![TextAreaInput::new(self.to_string()).disabled(true).widget_instance()];
vec![LayoutGroup::Row { widgets }]
vec![LayoutGroup::row(widgets)]
}
}
@@ -656,7 +656,7 @@ impl TableRowLayout for Option<f64> {
}
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
let widgets = vec![TextLabel::new(format!("{self:?}")).widget_instance()];
vec![LayoutGroup::Row { widgets }]
vec![LayoutGroup::row(widgets)]
}
}
@@ -669,7 +669,7 @@ impl TableRowLayout for DVec2 {
}
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
let widgets = vec![TextLabel::new(format!("({}, {})", self.x, self.y)).widget_instance()];
vec![LayoutGroup::Row { widgets }]
vec![LayoutGroup::row(widgets)]
}
}
@@ -682,7 +682,7 @@ impl TableRowLayout for Vec2 {
}
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
let widgets = vec![TextLabel::new(format!("({}, {})", self.x, self.y)).widget_instance()];
vec![LayoutGroup::Row { widgets }]
vec![LayoutGroup::row(widgets)]
}
}
@@ -695,7 +695,7 @@ impl TableRowLayout for DAffine2 {
}
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
let widgets = vec![TextLabel::new(format_transform_matrix(self)).widget_instance()];
vec![LayoutGroup::Row { widgets }]
vec![LayoutGroup::row(widgets)]
}
}
@@ -709,7 +709,7 @@ impl TableRowLayout for Affine2 {
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
let matrix = DAffine2::from_cols_array(&self.to_cols_array().map(|x| x as f64));
let widgets = vec![TextLabel::new(format_transform_matrix(&matrix)).widget_instance()];
vec![LayoutGroup::Row { widgets }]
vec![LayoutGroup::row(widgets)]
}
}
@@ -1,5 +1,4 @@
use super::node_graph::document_node_definitions;
use super::node_graph::utility_types::Transform;
use super::utility_types::error::EditorError;
use super::utility_types::misc::{GroupFolderType, SNAP_FUNCTIONS_FOR_BOUNDING_BOXES, SNAP_FUNCTIONS_FOR_PATHS, SnappingOptions, SnappingState};
use super::utility_types::network_interface::{self, NodeNetworkInterface, TransactionStatus};
@@ -848,7 +847,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
document_id,
name: format!("{}.{}", self.name.clone(), FILE_EXTENSION),
path: self.path.clone(),
content: self.serialize_document().into_bytes(),
content: self.serialize_document().into_bytes().into(),
})
}
DocumentMessage::SavedDocument { path } => {
@@ -1332,11 +1331,8 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
responses.add(NodeGraphMessage::UpdateImportsExports);
responses.add(FrontendMessage::UpdateNodeGraphTransform {
transform: Transform {
scale: transform.matrix2.x_axis.x,
x: transform.translation.x,
y: transform.translation.y,
},
translation: transform.translation.into(),
scale: transform.matrix2.x_axis.x,
})
}
}
@@ -2216,256 +2212,222 @@ impl DocumentMessageHandler {
.widget_instance(),
PopoverButton::new()
.popover_layout(Layout(vec![
LayoutGroup::Row {
widgets: vec![TextLabel::new("Overlays").bold(true).widget_instance()],
},
LayoutGroup::Row {
widgets: vec![TextLabel::new("General").widget_instance()],
},
LayoutGroup::Row {
widgets: {
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(self.overlays_visibility_settings.artboard_name)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::ArtboardName),
}
.into()
})
.for_label(checkbox_id)
.widget_instance(),
TextLabel::new("Artboard Name".to_string()).for_checkbox(checkbox_id).widget_instance(),
]
},
},
LayoutGroup::Row {
widgets: {
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(self.overlays_visibility_settings.transform_measurement)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::TransformMeasurement),
}
.into()
})
.for_label(checkbox_id)
.widget_instance(),
TextLabel::new("G/R/S Measurement".to_string()).for_checkbox(checkbox_id).widget_instance(),
]
},
},
LayoutGroup::Row {
widgets: vec![TextLabel::new("Select Tool").widget_instance()],
},
LayoutGroup::Row {
widgets: {
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(self.overlays_visibility_settings.quick_measurement)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::QuickMeasurement),
}
.into()
})
.for_label(checkbox_id)
.widget_instance(),
TextLabel::new("Quick Measurement".to_string()).for_checkbox(checkbox_id).widget_instance(),
]
},
},
LayoutGroup::Row {
widgets: {
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(self.overlays_visibility_settings.transform_cage)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::TransformCage),
}
.into()
})
.for_label(checkbox_id)
.widget_instance(),
TextLabel::new("Transform Cage".to_string()).for_checkbox(checkbox_id).widget_instance(),
]
},
},
LayoutGroup::Row {
widgets: {
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(self.overlays_visibility_settings.compass_rose)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::CompassRose),
}
.into()
})
.for_label(checkbox_id)
.widget_instance(),
TextLabel::new("Transform Dial".to_string()).for_checkbox(checkbox_id).widget_instance(),
]
},
},
LayoutGroup::Row {
widgets: {
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(self.overlays_visibility_settings.pivot)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::Pivot),
}
.into()
})
.for_label(checkbox_id)
.widget_instance(),
TextLabel::new("Transform Pivot".to_string()).for_checkbox(checkbox_id).widget_instance(),
]
},
},
LayoutGroup::Row {
widgets: {
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(self.overlays_visibility_settings.pivot)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::Origin),
}
.into()
})
.for_label(checkbox_id)
.widget_instance(),
TextLabel::new("Transform Origin".to_string()).for_checkbox(checkbox_id).widget_instance(),
]
},
},
LayoutGroup::Row {
widgets: {
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(self.overlays_visibility_settings.hover_outline)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::HoverOutline),
}
.into()
})
.for_label(checkbox_id)
.widget_instance(),
TextLabel::new("Hover Outline".to_string()).for_checkbox(checkbox_id).widget_instance(),
]
},
},
LayoutGroup::Row {
widgets: {
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(self.overlays_visibility_settings.selection_outline)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::SelectionOutline),
}
.into()
})
.for_label(checkbox_id)
.widget_instance(),
TextLabel::new("Selection Outline".to_string()).for_checkbox(checkbox_id).widget_instance(),
]
},
},
LayoutGroup::Row {
widgets: {
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(self.overlays_visibility_settings.layer_origin_cross)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::LayerOriginCross),
}
.into()
})
.for_label(checkbox_id)
.widget_instance(),
TextLabel::new("Layer Origin".to_string()).for_checkbox(checkbox_id).widget_instance(),
]
},
},
LayoutGroup::Row {
widgets: vec![TextLabel::new("Pen & Path Tools").widget_instance()],
},
LayoutGroup::Row {
widgets: {
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(self.overlays_visibility_settings.path)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::Path),
}
.into()
})
.for_label(checkbox_id)
.widget_instance(),
TextLabel::new("Path".to_string()).for_checkbox(checkbox_id).widget_instance(),
]
},
},
LayoutGroup::Row {
widgets: {
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(self.overlays_visibility_settings.anchors)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::Anchors),
}
.into()
})
.for_label(checkbox_id)
.widget_instance(),
TextLabel::new("Anchors".to_string()).for_checkbox(checkbox_id).widget_instance(),
]
},
},
LayoutGroup::Row {
widgets: {
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(self.overlays_visibility_settings.handles)
.disabled(!self.overlays_visibility_settings.anchors)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::Handles),
}
.into()
})
.for_label(checkbox_id)
.widget_instance(),
TextLabel::new("Handles".to_string())
.disabled(!self.overlays_visibility_settings.anchors)
.for_checkbox(checkbox_id)
.widget_instance(),
]
},
},
LayoutGroup::row(vec![TextLabel::new("Overlays").bold(true).widget_instance()]),
LayoutGroup::row(vec![TextLabel::new("General").widget_instance()]),
LayoutGroup::row({
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(self.overlays_visibility_settings.artboard_name)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::ArtboardName),
}
.into()
})
.for_label(checkbox_id)
.widget_instance(),
TextLabel::new("Artboard Name".to_string()).for_checkbox(checkbox_id).widget_instance(),
]
}),
LayoutGroup::row({
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(self.overlays_visibility_settings.transform_measurement)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::TransformMeasurement),
}
.into()
})
.for_label(checkbox_id)
.widget_instance(),
TextLabel::new("G/R/S Measurement".to_string()).for_checkbox(checkbox_id).widget_instance(),
]
}),
LayoutGroup::row(vec![TextLabel::new("Select Tool").widget_instance()]),
LayoutGroup::row({
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(self.overlays_visibility_settings.quick_measurement)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::QuickMeasurement),
}
.into()
})
.for_label(checkbox_id)
.widget_instance(),
TextLabel::new("Quick Measurement".to_string()).for_checkbox(checkbox_id).widget_instance(),
]
}),
LayoutGroup::row({
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(self.overlays_visibility_settings.transform_cage)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::TransformCage),
}
.into()
})
.for_label(checkbox_id)
.widget_instance(),
TextLabel::new("Transform Cage".to_string()).for_checkbox(checkbox_id).widget_instance(),
]
}),
LayoutGroup::row({
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(self.overlays_visibility_settings.compass_rose)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::CompassRose),
}
.into()
})
.for_label(checkbox_id)
.widget_instance(),
TextLabel::new("Transform Dial".to_string()).for_checkbox(checkbox_id).widget_instance(),
]
}),
LayoutGroup::row({
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(self.overlays_visibility_settings.pivot)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::Pivot),
}
.into()
})
.for_label(checkbox_id)
.widget_instance(),
TextLabel::new("Transform Pivot".to_string()).for_checkbox(checkbox_id).widget_instance(),
]
}),
LayoutGroup::row({
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(self.overlays_visibility_settings.origin)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::Origin),
}
.into()
})
.for_label(checkbox_id)
.widget_instance(),
TextLabel::new("Transform Origin".to_string()).for_checkbox(checkbox_id).widget_instance(),
]
}),
LayoutGroup::row({
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(self.overlays_visibility_settings.hover_outline)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::HoverOutline),
}
.into()
})
.for_label(checkbox_id)
.widget_instance(),
TextLabel::new("Hover Outline".to_string()).for_checkbox(checkbox_id).widget_instance(),
]
}),
LayoutGroup::row({
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(self.overlays_visibility_settings.selection_outline)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::SelectionOutline),
}
.into()
})
.for_label(checkbox_id)
.widget_instance(),
TextLabel::new("Selection Outline".to_string()).for_checkbox(checkbox_id).widget_instance(),
]
}),
LayoutGroup::row({
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(self.overlays_visibility_settings.layer_origin_cross)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::LayerOriginCross),
}
.into()
})
.for_label(checkbox_id)
.widget_instance(),
TextLabel::new("Layer Origin".to_string()).for_checkbox(checkbox_id).widget_instance(),
]
}),
LayoutGroup::row(vec![TextLabel::new("Pen & Path Tools").widget_instance()]),
LayoutGroup::row({
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(self.overlays_visibility_settings.path)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::Path),
}
.into()
})
.for_label(checkbox_id)
.widget_instance(),
TextLabel::new("Path".to_string()).for_checkbox(checkbox_id).widget_instance(),
]
}),
LayoutGroup::row({
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(self.overlays_visibility_settings.anchors)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::Anchors),
}
.into()
})
.for_label(checkbox_id)
.widget_instance(),
TextLabel::new("Anchors".to_string()).for_checkbox(checkbox_id).widget_instance(),
]
}),
LayoutGroup::row({
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(self.overlays_visibility_settings.handles)
.disabled(!self.overlays_visibility_settings.anchors)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::Handles),
}
.into()
})
.for_label(checkbox_id)
.widget_instance(),
TextLabel::new("Handles".to_string())
.disabled(!self.overlays_visibility_settings.anchors)
.for_checkbox(checkbox_id)
.widget_instance(),
]
}),
]))
.widget_instance(),
Separator::new(SeparatorStyle::Related).widget_instance(),
@@ -2484,16 +2446,12 @@ impl DocumentMessageHandler {
PopoverButton::new()
.popover_layout(Layout(
[
LayoutGroup::Row {
widgets: vec![TextLabel::new("Snapping").bold(true).widget_instance()],
},
LayoutGroup::Row {
widgets: vec![TextLabel::new(SnappingOptions::BoundingBoxes.to_string()).widget_instance()],
},
LayoutGroup::row(vec![TextLabel::new("Snapping").bold(true).widget_instance()]),
LayoutGroup::row(vec![TextLabel::new(SnappingOptions::BoundingBoxes.to_string()).widget_instance()]),
]
.into_iter()
.chain(SNAP_FUNCTIONS_FOR_BOUNDING_BOXES.into_iter().map(|(name, closure, description)| LayoutGroup::Row {
widgets: {
.chain(SNAP_FUNCTIONS_FOR_BOUNDING_BOXES.into_iter().map(|(name, closure, description)| {
LayoutGroup::row({
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(*closure(&mut snapping_state))
@@ -2510,13 +2468,11 @@ impl DocumentMessageHandler {
.widget_instance(),
TextLabel::new(name).tooltip_label(name).tooltip_description(description).for_checkbox(checkbox_id).widget_instance(),
]
},
})
}))
.chain([LayoutGroup::Row {
widgets: vec![TextLabel::new(SnappingOptions::Paths.to_string()).widget_instance()],
}])
.chain(SNAP_FUNCTIONS_FOR_PATHS.into_iter().map(|(name, closure, description)| LayoutGroup::Row {
widgets: {
.chain([LayoutGroup::row(vec![TextLabel::new(SnappingOptions::Paths.to_string()).widget_instance()])])
.chain(SNAP_FUNCTIONS_FOR_PATHS.into_iter().map(|(name, closure, description)| {
LayoutGroup::row({
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(*closure(&mut snapping_state2))
@@ -2533,7 +2489,7 @@ impl DocumentMessageHandler {
.widget_instance(),
TextLabel::new(name).tooltip_label(name).tooltip_description(description).for_checkbox(checkbox_id).widget_instance(),
]
},
})
}))
.collect(),
))
@@ -2630,8 +2586,8 @@ impl DocumentMessageHandler {
widgets.extend([
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
TextButton::new("Node Graph")
.icon(Some((if self.graph_view_overlay_open { "GraphViewOpen" } else { "GraphViewClosed" }).into()))
.hover_icon(Some((if self.graph_view_overlay_open { "GraphViewClosed" } else { "GraphViewOpen" }).into()))
.icon(if self.graph_view_overlay_open { "GraphViewOpen" } else { "GraphViewClosed" })
.hover_icon(if self.graph_view_overlay_open { "GraphViewClosed" } else { "GraphViewOpen" })
.tooltip_label(if self.graph_view_overlay_open { "Hide Node Graph" } else { "Show Node Graph" })
.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::GraphViewOverlayToggle))
.on_update(move |_| DocumentMessage::GraphViewOverlayToggle.into())
@@ -2639,7 +2595,7 @@ impl DocumentMessageHandler {
]);
responses.add(LayoutMessage::SendLayout {
layout: Layout(vec![LayoutGroup::Row { widgets }]),
layout: Layout(vec![LayoutGroup::row(widgets)]),
layout_target: LayoutTarget::DocumentBar,
});
responses.add(NodeGraphMessage::RunDocumentGraph);
@@ -2777,25 +2733,25 @@ impl DocumentMessageHandler {
.tooltip_label("Fill")
.widget_instance(),
];
let layers_panel_control_bar_left = Layout(vec![LayoutGroup::Row { widgets }]);
let layers_panel_control_bar_left = Layout(vec![LayoutGroup::row(widgets)]);
let widgets = vec![
IconButton::new(if selection_all_locked { "PadlockLocked" } else { "PadlockUnlocked" }, 24)
.hover_icon(Some((if selection_all_locked { "PadlockUnlocked" } else { "PadlockLocked" }).into()))
.hover_icon(if selection_all_locked { "PadlockUnlocked" } else { "PadlockLocked" })
.tooltip_label(if selection_all_locked { "Unlock Selected" } else { "Lock Selected" })
.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::ToggleSelectedLocked))
.on_update(|_| NodeGraphMessage::ToggleSelectedLocked.into())
.disabled(!has_selection)
.widget_instance(),
IconButton::new(if selection_all_visible { "EyeVisible" } else { "EyeHidden" }, 24)
.hover_icon(Some((if selection_all_visible { "EyeHide" } else { "EyeShow" }).into()))
.hover_icon(if selection_all_visible { "EyeHide" } else { "EyeShow" })
.tooltip_label(if selection_all_visible { "Hide Selected" } else { "Show Selected" })
.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::ToggleSelectedVisibility))
.on_update(|_| DocumentMessage::ToggleSelectedVisibility.into())
.disabled(!has_selection)
.widget_instance(),
];
let layers_panel_control_bar_right = Layout(vec![LayoutGroup::Row { widgets }]);
let layers_panel_control_bar_right = Layout(vec![LayoutGroup::row(widgets)]);
responses.add(LayoutMessage::SendLayout {
layout: layers_panel_control_bar_left,
@@ -2821,7 +2777,7 @@ impl DocumentMessageHandler {
let widgets = vec![
PopoverButton::new()
.icon(Some("Node".to_string()))
.icon("Node")
.menu_direction(Some(MenuDirection::Top))
.tooltip_description("Add an operation to the end of this layer's chain of nodes.")
.disabled(!has_selection || has_multiple_selection)
@@ -2849,7 +2805,7 @@ impl DocumentMessageHandler {
}
})
.widget_instance();
Layout(vec![LayoutGroup::Row { widgets: vec![node_chooser] }])
Layout(vec![LayoutGroup::row(vec![node_chooser])])
})
.widget_instance(),
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
@@ -2875,7 +2831,7 @@ impl DocumentMessageHandler {
.widget_instance(),
];
responses.add(LayoutMessage::SendLayout {
layout: Layout(vec![LayoutGroup::Row { widgets }]),
layout: Layout(vec![LayoutGroup::row(widgets)]),
layout_target: LayoutTarget::LayersPanelBottomBar,
});
}
@@ -57,7 +57,8 @@ impl NodePropertiesContext<'_> {
/// The key used to access definitions for a network node or proto node.
/// For proto nodes, this is their [`ProtoNodeIdentifier`].
/// For network nodes, it doesn't necessarily have to be the same as the network's display name, but it often is.
#[derive(Debug, Clone, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Debug, Clone, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type", content = "data")]
pub enum DefinitionIdentifier {
ProtoNode(ProtoNodeIdentifier),
@@ -2191,9 +2192,10 @@ fn static_input_properties() -> InputProperties {
true
});
Ok(vec![LayoutGroup::Row {
widgets: node_properties::number_widget(ParameterWidgetsInfo::new(node_id, index, blank_assist, context), number_input),
}])
Ok(vec![LayoutGroup::row(node_properties::number_widget(
ParameterWidgetsInfo::new(node_id, index, blank_assist, context),
number_input,
))])
}),
);
map.insert(
@@ -2227,10 +2229,12 @@ fn static_input_properties() -> InputProperties {
number_input = number_input.step(number_step);
}
};
Ok(vec![LayoutGroup::Row {
// NOTE: The bool input MUST be at the input index directly before the f64 input!
widgets: node_properties::optional_f64_widget(ParameterWidgetsInfo::new(node_id, index, false, context), index - 1, number_input),
}])
// NOTE: The bool input MUST be at the input index directly before the f64 input!
Ok(vec![LayoutGroup::row(node_properties::optional_f64_widget(
ParameterWidgetsInfo::new(node_id, index, false, context),
index - 1,
number_input,
))])
}),
);
map.insert(
@@ -2298,7 +2302,7 @@ fn static_input_properties() -> InputProperties {
"noise_properties_noise_type".to_string(),
Box::new(|node_id, index, context| {
let noise_type_row = enum_choice::<NoiseType>().for_socket(ParameterWidgetsInfo::new(node_id, index, true, context)).property_row();
Ok(vec![noise_type_row, LayoutGroup::Row { widgets: Vec::new() }])
Ok(vec![noise_type_row, LayoutGroup::row(Vec::new())])
}),
);
map.insert(
@@ -2320,7 +2324,7 @@ fn static_input_properties() -> InputProperties {
ParameterWidgetsInfo::new(node_id, index, true, context),
NumberInput::default().min(0.).disabled(!coherent_noise_active || !domain_warp_active),
);
Ok(vec![domain_warp_amplitude.into(), LayoutGroup::Row { widgets: Vec::new() }])
Ok(vec![domain_warp_amplitude.into(), LayoutGroup::row(Vec::new())])
}),
);
map.insert(
@@ -2408,7 +2412,7 @@ fn static_input_properties() -> InputProperties {
.range_max(Some(10.))
.disabled(!ping_pong_active || !coherent_noise_active || !fractal_active || domain_warp_only_fractal_type_wrongly_active),
);
Ok(vec![fractal_ping_pong_strength.into(), LayoutGroup::Row { widgets: Vec::new() }])
Ok(vec![fractal_ping_pong_strength.into(), LayoutGroup::row(Vec::new())])
}),
);
map.insert(
@@ -2504,7 +2508,7 @@ fn static_input_properties() -> InputProperties {
]);
}
Ok(vec![LayoutGroup::Row { widgets }])
Ok(vec![LayoutGroup::row(widgets)])
}),
);
// Skew has a custom override that maps to degrees
@@ -2548,24 +2552,20 @@ fn static_input_properties() -> InputProperties {
]);
}
Ok(vec![LayoutGroup::Row { widgets }])
Ok(vec![LayoutGroup::row(widgets)])
}),
);
map.insert(
"text_area".to_string(),
Box::new(|node_id, index, context| {
Ok(vec![LayoutGroup::Row {
widgets: node_properties::text_area_widget(ParameterWidgetsInfo::new(node_id, index, true, context)),
}])
}),
Box::new(|node_id, index, context| Ok(vec![LayoutGroup::row(node_properties::text_area_widget(ParameterWidgetsInfo::new(node_id, index, true, context)))])),
);
map.insert(
"text_font".to_string(),
Box::new(|node_id, index, context| {
let (font, style) = node_properties::font_inputs(ParameterWidgetsInfo::new(node_id, index, true, context));
let mut result = vec![LayoutGroup::Row { widgets: font }];
let mut result = vec![LayoutGroup::row(font)];
if let Some(style) = style {
result.push(LayoutGroup::Row { widgets: style });
result.push(LayoutGroup::row(style));
}
Ok(result)
}),
@@ -847,7 +847,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
};
self.context_menu = Some(ContextMenuInformation {
context_menu_coordinates: (node_graph_point + node_graph_shift).as_ivec2(),
context_menu_coordinates: (node_graph_point + node_graph_shift).as_ivec2().into(),
context_menu_data,
});
@@ -1280,7 +1280,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
let compatible_type = network_interface.output_type(&output_connector, selection_network_path).add_node_string();
self.context_menu = Some(ContextMenuInformation {
context_menu_coordinates: (point + node_graph_shift).as_ivec2(),
context_menu_coordinates: (point + node_graph_shift).as_ivec2().into(),
context_menu_data: ContextMenuData::CreateNode { compatible_type },
});
@@ -2050,8 +2050,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
responses.add(FrontendMessage::UpdateImportsExports {
imports,
exports,
import_position,
export_position,
import_position: import_position.into(),
export_position: export_position.into(),
add_import_export,
});
}
@@ -2181,7 +2181,7 @@ impl NodeGraphMessageHandler {
let mut widgets = vec![
PopoverButton::new()
.icon(Some("Node".to_string()))
.icon("Node")
.tooltip_label("New Node")
.tooltip_description("To add a node at the pointer location, perform the shortcut in an open area of the graph.")
.tooltip_shortcut(action_shortcut_manual!(Key::MouseRight))
@@ -2222,7 +2222,7 @@ impl NodeGraphMessageHandler {
}
})
.widget_instance();
Layout(vec![LayoutGroup::Row { widgets: vec![node_chooser] }])
Layout(vec![LayoutGroup::row(vec![node_chooser])])
})
.widget_instance(),
//
@@ -2252,14 +2252,14 @@ impl NodeGraphMessageHandler {
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
//
IconButton::new(if selection_all_locked { "PadlockLocked" } else { "PadlockUnlocked" }, 24)
.hover_icon(Some((if selection_all_locked { "PadlockUnlocked" } else { "PadlockLocked" }).into()))
.hover_icon(if selection_all_locked { "PadlockUnlocked" } else { "PadlockLocked" })
.tooltip_label(if selection_all_locked { "Unlock Selected" } else { "Lock Selected" })
.tooltip_shortcut(action_shortcut!(NodeGraphMessageDiscriminant::ToggleSelectedLocked))
.on_update(|_| NodeGraphMessage::ToggleSelectedLocked.into())
.disabled(!has_selection || !selection_includes_layers)
.widget_instance(),
IconButton::new(if selection_all_visible { "EyeVisible" } else { "EyeHidden" }, 24)
.hover_icon(Some((if selection_all_visible { "EyeHide" } else { "EyeShow" }).into()))
.hover_icon(if selection_all_visible { "EyeHide" } else { "EyeShow" })
.tooltip_label(if selection_all_visible { "Hide Selected" } else { "Show Selected" })
.tooltip_shortcut(action_shortcut!(NodeGraphMessageDiscriminant::ToggleSelectedVisibility))
.on_update(|_| NodeGraphMessage::ToggleSelectedVisibility.into())
@@ -2286,7 +2286,7 @@ impl NodeGraphMessageHandler {
// If only one node is selected then show the preview or stop previewing button
if let Some(node_id) = previewing {
let button = TextButton::new("End Preview")
.icon(Some("FrameAll".to_string()))
.icon("FrameAll")
.tooltip_description("Restore preview to the graph output.")
.on_update(move |_| NodeGraphMessage::TogglePreview { node_id }.into())
.widget_instance();
@@ -2298,7 +2298,7 @@ impl NodeGraphMessageHandler {
.any(|export| matches!(export, NodeInput::Node { node_id: export_node_id, .. } if *export_node_id == node_id));
if selection_is_not_already_the_output && no_other_selections {
let button = TextButton::new("Preview")
.icon(Some("FrameAll".to_string()))
.icon("FrameAll")
.tooltip_label("Preview")
.tooltip_description("Temporarily set the graph output to the selected node or layer. Perform the shortcut on a node or layer for quick access.")
.tooltip_shortcut(action_shortcut_manual!(Key::Alt, Key::MouseLeft))
@@ -2323,7 +2323,7 @@ impl NodeGraphMessageHandler {
]);
}
self.widgets[0] = LayoutGroup::Row { widgets };
self.widgets[0] = LayoutGroup::row(widgets);
}
fn update_graph_bar_right(
@@ -2357,15 +2357,15 @@ impl NodeGraphMessageHandler {
widgets.extend([
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
TextButton::new("Node Graph")
.icon(Some("GraphViewOpen".into()))
.hover_icon(Some("GraphViewClosed".into()))
.icon("GraphViewOpen")
.hover_icon("GraphViewClosed")
.tooltip_label("Hide Node Graph")
.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::GraphViewOverlayToggle))
.on_update(move |_| DocumentMessage::GraphViewOverlayToggle.into())
.widget_instance(),
]);
self.widgets[1] = LayoutGroup::Row { widgets };
self.widgets[1] = LayoutGroup::row(widgets);
}
/// Collate the properties panel sections for a node graph
@@ -2407,25 +2407,23 @@ impl NodeGraphMessageHandler {
let mut properties = Vec::new();
if let [node_id] = *nodes.as_slice() {
properties.push(LayoutGroup::Row {
widgets: vec![
Separator::new(SeparatorStyle::Related).widget_instance(),
IconLabel::new("Node").tooltip_description("Name of the selected node.").widget_instance(),
Separator::new(SeparatorStyle::Related).widget_instance(),
TextInput::new(context.network_interface.display_name(&node_id, context.selection_network_path))
.tooltip_description("Name of the selected node.")
.on_update(move |text_input| {
NodeGraphMessage::SetDisplayName {
node_id,
alias: text_input.value.clone(),
skip_adding_history_step: false,
}
.into()
})
.widget_instance(),
Separator::new(SeparatorStyle::Related).widget_instance(),
],
});
properties.push(LayoutGroup::row(vec![
Separator::new(SeparatorStyle::Related).widget_instance(),
IconLabel::new("Node").tooltip_description("Name of the selected node.").widget_instance(),
Separator::new(SeparatorStyle::Related).widget_instance(),
TextInput::new(context.network_interface.display_name(&node_id, context.selection_network_path))
.tooltip_description("Name of the selected node.")
.on_update(move |text_input| {
NodeGraphMessage::SetDisplayName {
node_id,
alias: text_input.value.clone(),
skip_adding_history_step: false,
}
.into()
})
.widget_instance(),
Separator::new(SeparatorStyle::Related).widget_instance(),
]));
}
properties.extend(selected_nodes);
@@ -2435,18 +2433,16 @@ 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
let mut properties = vec![LayoutGroup::Row {
widgets: vec![
Separator::new(SeparatorStyle::Related).widget_instance(),
IconLabel::new("File").tooltip_description("Name of the current document.").widget_instance(),
Separator::new(SeparatorStyle::Related).widget_instance(),
TextInput::new(context.document_name)
.tooltip_description("Name of the current document.")
.on_update(|text_input| DocumentMessage::RenameDocument { new_name: text_input.value.clone() }.into())
.widget_instance(),
Separator::new(SeparatorStyle::Related).widget_instance(),
],
}];
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(),
Separator::new(SeparatorStyle::Related).widget_instance(),
TextInput::new(context.document_name)
.tooltip_description("Name of the current document.")
.on_update(|text_input| DocumentMessage::RenameDocument { new_name: text_input.value.clone() }.into())
.widget_instance(),
Separator::new(SeparatorStyle::Related).widget_instance(),
])];
let Some(network) = context.network_interface.nested_network(context.selection_network_path) else {
warn!("No network in collate_properties");
@@ -2482,50 +2478,48 @@ impl NodeGraphMessageHandler {
return Vec::new();
}
let mut layer_properties = vec![LayoutGroup::Row {
widgets: vec![
Separator::new(SeparatorStyle::Related).widget_instance(),
IconLabel::new("Layer").tooltip_description("Name of the selected layer.").widget_instance(),
Separator::new(SeparatorStyle::Related).widget_instance(),
TextInput::new(context.network_interface.display_name(&layer, context.selection_network_path))
.tooltip_description("Name of the selected layer.")
.on_update(move |text_input| {
NodeGraphMessage::SetDisplayName {
node_id: layer,
alias: text_input.value.clone(),
skip_adding_history_step: false,
}
.into()
})
.widget_instance(),
Separator::new(SeparatorStyle::Related).widget_instance(),
PopoverButton::new()
.icon(Some("Node".to_string()))
.tooltip_description("Add an operation to the end of this layer's chain of nodes.")
.popover_layout({
let compatible_type = context
.network_interface
.upstream_output_connector(&InputConnector::node(layer, 1), &[])
.and_then(|upstream_output| context.network_interface.output_type(&upstream_output, &[]).add_node_string());
let mut layer_properties = vec![LayoutGroup::row(vec![
Separator::new(SeparatorStyle::Related).widget_instance(),
IconLabel::new("Layer").tooltip_description("Name of the selected layer.").widget_instance(),
Separator::new(SeparatorStyle::Related).widget_instance(),
TextInput::new(context.network_interface.display_name(&layer, context.selection_network_path))
.tooltip_description("Name of the selected layer.")
.on_update(move |text_input| {
NodeGraphMessage::SetDisplayName {
node_id: layer,
alias: text_input.value.clone(),
skip_adding_history_step: false,
}
.into()
})
.widget_instance(),
Separator::new(SeparatorStyle::Related).widget_instance(),
PopoverButton::new()
.icon("Node")
.tooltip_description("Add an operation to the end of this layer's chain of nodes.")
.popover_layout({
let compatible_type = context
.network_interface
.upstream_output_connector(&InputConnector::node(layer, 1), &[])
.and_then(|upstream_output| context.network_interface.output_type(&upstream_output, &[]).add_node_string());
let mut node_chooser = NodeCatalog::new();
node_chooser.intial_search = compatible_type.unwrap_or("".to_string());
let mut node_chooser = NodeCatalog::new();
node_chooser.intial_search = compatible_type.unwrap_or("".to_string());
let node_chooser = node_chooser
.on_update(move |node_type| {
NodeGraphMessage::CreateNodeInLayerWithTransaction {
node_type: node_type.clone(),
layer: LayerNodeIdentifier::new_unchecked(layer),
}
.into()
})
.widget_instance();
Layout(vec![LayoutGroup::Row { widgets: vec![node_chooser] }])
})
.widget_instance(),
Separator::new(SeparatorStyle::Related).widget_instance(),
],
}];
let node_chooser = node_chooser
.on_update(move |node_type| {
NodeGraphMessage::CreateNodeInLayerWithTransaction {
node_type: node_type.clone(),
layer: LayerNodeIdentifier::new_unchecked(layer),
}
.into()
})
.widget_instance();
Layout(vec![LayoutGroup::row(vec![node_chooser])])
})
.widget_instance(),
Separator::new(SeparatorStyle::Related).widget_instance(),
])];
// Iterate through all the upstream nodes, but stop when we reach another layer (since that's a point where we switch from horizontal to vertical flow)
let node_properties = context
@@ -2651,7 +2645,7 @@ impl NodeGraphMessageHandler {
exposed_outputs,
primary_output_connected_to_layer,
primary_input_connected_to_layer,
position,
position: position.into(),
previewed,
visible,
locked,
@@ -2695,6 +2689,7 @@ impl NodeGraphMessageHandler {
if network_interface.is_layer(&error_node, breadcrumb_network_path) {
position += IVec2::new(12, -12)
}
let position = position.into();
Some(NodeGraphErrorDiagnostic { position, error })
}
@@ -2841,7 +2836,7 @@ impl Default for NodeGraphMessageHandler {
Self {
network: Vec::new(),
has_selection: false,
widgets: [LayoutGroup::Row { widgets: Vec::new() }, LayoutGroup::Row { widgets: Vec::new() }],
widgets: [LayoutGroup::row(Vec::new()), LayoutGroup::row(Vec::new())],
drag_start: None,
begin_dragging: false,
node_has_moved_in_drag: false,
@@ -31,7 +31,7 @@ use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops, Gra
pub(crate) fn string_properties(text: &str) -> Vec<LayoutGroup> {
let widget = TextLabel::new(text).widget_instance();
vec![LayoutGroup::Row { widgets: vec![widget] }]
vec![LayoutGroup::row(vec![widget])]
}
fn optionally_update_value<T>(value: impl Fn(&T) -> Option<TaggedValue> + 'static + Send + Sync, node_id: NodeId, input_index: usize) -> impl Fn(&T) -> Message + 'static + Send + Sync {
@@ -538,11 +538,7 @@ pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg
);
}
let widgets = [
LayoutGroup::Row { widgets: location_widgets },
LayoutGroup::Row { widgets: scale_widgets },
LayoutGroup::Row { widgets: resolution_widgets },
];
let widgets = [LayoutGroup::row(location_widgets), LayoutGroup::row(scale_widgets), LayoutGroup::row(resolution_widgets)];
let (last, rest) = widgets.split_last().expect("Footprint widget should return multiple rows");
*extra_widgets = rest.to_vec();
last.clone()
@@ -651,13 +647,9 @@ pub fn transform_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg
.widget_instance(),
]);
vec![
LayoutGroup::Row { widgets: location_widgets },
LayoutGroup::Row { widgets: rotation_widgets },
LayoutGroup::Row { widgets: scale_widgets },
]
vec![LayoutGroup::row(location_widgets), LayoutGroup::row(rotation_widgets), LayoutGroup::row(scale_widgets)]
} else {
vec![LayoutGroup::Row { widgets: location_widgets }]
vec![LayoutGroup::row(location_widgets)]
};
if let Some((last, rest)) = widgets.split_last() {
@@ -676,7 +668,7 @@ pub fn vec2_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &st
let Some(document_node) = document_node else { return LayoutGroup::default() };
let Some(input) = document_node.inputs.get(index) else {
log::warn!("A widget failed to be built because its node's input index is invalid.");
return LayoutGroup::Row { widgets: vec![] };
return LayoutGroup::row(vec![]);
};
match input.as_non_exposed_value() {
Some(&TaggedValue::DVec2(dvec2)) => {
@@ -730,7 +722,7 @@ pub fn vec2_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &st
_ => {}
}
LayoutGroup::Row { widgets }
LayoutGroup::row(widgets)
}
pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text_input: TextInput) -> Vec<WidgetInstance> {
@@ -1101,7 +1093,7 @@ pub fn blend_mode_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Layout
let Some(document_node) = document_node else { return LayoutGroup::default() };
let Some(input) = document_node.inputs.get(index) else {
log::warn!("A widget failed to be built because its node's input index is invalid.");
return LayoutGroup::Row { widgets: vec![] };
return LayoutGroup::row(vec![]);
};
if let Some(&TaggedValue::BlendMode(blend_mode)) = input.as_non_exposed_value() {
let entries = BlendMode::list_svg_subset()
@@ -1126,7 +1118,7 @@ pub fn blend_mode_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Layout
.widget_instance(),
]);
}
LayoutGroup::Row { widgets }.with_tooltip_description("Formula used for blending.")
LayoutGroup::row(widgets).with_tooltip_description("Formula used for blending.")
}
pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button: ColorInput) -> LayoutGroup {
@@ -1137,7 +1129,7 @@ pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button:
let Some(document_node) = document_node else { return LayoutGroup::default() };
// Return early with just the label if the input is exposed to the graph, meaning we don't want to show the color picker widget in the Properties panel
let NodeInput::Value { tagged_value, exposed: false } = &document_node.inputs[index] else {
return LayoutGroup::Row { widgets };
return LayoutGroup::row(widgets);
};
// Add a separator
@@ -1176,7 +1168,7 @@ pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button:
x => warn!("Color {x:?}"),
}
LayoutGroup::Row { widgets }
LayoutGroup::row(widgets)
}
pub fn font_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup {
@@ -1192,7 +1184,7 @@ pub fn curve_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup
let Some(document_node) = document_node else { return LayoutGroup::default() };
let Some(input) = document_node.inputs.get(index) else {
log::warn!("A widget failed to be built because its node's input index is invalid.");
return LayoutGroup::Row { widgets: vec![] };
return LayoutGroup::row(vec![]);
};
if let Some(TaggedValue::Curve(curve)) = &input.as_non_exposed_value() {
widgets.extend_from_slice(&[
@@ -1203,7 +1195,7 @@ pub fn curve_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup
.widget_instance(),
])
}
LayoutGroup::Row { widgets }
LayoutGroup::row(widgets)
}
pub fn get_document_node<'a>(node_id: NodeId, context: &'a NodePropertiesContext<'a>) -> Result<&'a DocumentNode, String> {
@@ -1306,10 +1298,10 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node
.range_max(Some(100.)),
);
let mut layout = vec![LayoutGroup::Row { widgets: brightness }, LayoutGroup::Row { widgets: contrast }];
let mut layout = vec![LayoutGroup::row(brightness), LayoutGroup::row(contrast)];
if includes_use_classic {
// TODO: When we no longer use this function in the temporary "Brightness/Contrast Classic" node, remove this conditional pushing and just always include this
layout.push(LayoutGroup::Row { widgets: use_classic });
layout.push(LayoutGroup::row(use_classic));
}
layout
@@ -1358,18 +1350,13 @@ pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodeProper
let constant = number_widget(ParameterWidgetsInfo::new(node_id, constant_output_index, true, context), number_input);
// Monochrome
let mut layout = vec![LayoutGroup::Row { widgets: is_monochrome }];
let mut layout = vec![LayoutGroup::row(is_monochrome)];
// Output channel choice
if !is_monochrome_value {
layout.push(output_channel);
}
// Channel values
layout.extend([
LayoutGroup::Row { widgets: red },
LayoutGroup::Row { widgets: green },
LayoutGroup::Row { widgets: blue },
LayoutGroup::Row { widgets: constant },
]);
layout.extend([LayoutGroup::row(red), LayoutGroup::row(green), LayoutGroup::row(blue), LayoutGroup::row(constant)]);
layout
}
@@ -1422,10 +1409,10 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp
// Colors choice
colors,
// CMYK
LayoutGroup::Row { widgets: cyan },
LayoutGroup::Row { widgets: magenta },
LayoutGroup::Row { widgets: yellow },
LayoutGroup::Row { widgets: black },
LayoutGroup::row(cyan),
LayoutGroup::row(magenta),
LayoutGroup::row(yellow),
LayoutGroup::row(black),
// Mode
mode,
]
@@ -1458,12 +1445,10 @@ pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesConte
widgets.push(spacing);
}
GridType::Isometric => {
let spacing = LayoutGroup::Row {
widgets: number_widget(
ParameterWidgetsInfo::new(node_id, SpacingInput::<f64>::INDEX, true, context),
NumberInput::default().label("H").min(0.).unit(" px"),
),
};
let spacing = LayoutGroup::row(number_widget(
ParameterWidgetsInfo::new(node_id, SpacingInput::<f64>::INDEX, true, context),
NumberInput::default().label("H").min(0.).unit(" px"),
));
let angles = vec2_widget(ParameterWidgetsInfo::new(node_id, AnglesInput::INDEX, true, context), "", "", "°", None, false);
widgets.extend([spacing, angles]);
}
@@ -1473,7 +1458,7 @@ pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesConte
let columns = number_widget(ParameterWidgetsInfo::new(node_id, ColumnsInput::INDEX, true, context), NumberInput::default().min(1.));
let rows = number_widget(ParameterWidgetsInfo::new(node_id, RowsInput::INDEX, true, context), NumberInput::default().min(1.));
widgets.extend([LayoutGroup::Row { widgets: columns }, LayoutGroup::Row { widgets: rows }]);
widgets.extend([LayoutGroup::row(columns), LayoutGroup::row(rows)]);
widgets
}
@@ -1487,7 +1472,7 @@ pub(crate) fn spiral_properties(node_id: NodeId, context: &mut NodePropertiesCon
let turns = number_widget(ParameterWidgetsInfo::new(node_id, TurnsInput::INDEX, true, context), NumberInput::default().min(0.1));
let start_angle = number_widget(ParameterWidgetsInfo::new(node_id, StartAngleInput::INDEX, true, context), NumberInput::default().unit("°"));
let mut widgets = vec![spiral_type, LayoutGroup::Row { widgets: turns }, LayoutGroup::Row { widgets: start_angle }];
let mut widgets = vec![spiral_type, LayoutGroup::row(turns), LayoutGroup::row(start_angle)];
let document_node = match get_document_node(node_id, context) {
Ok(document_node) => document_node,
@@ -1504,24 +1489,28 @@ pub(crate) fn spiral_properties(node_id: NodeId, context: &mut NodePropertiesCon
if let Some(&TaggedValue::SpiralType(spiral_type)) = spiral_type_input.as_non_exposed_value() {
match spiral_type {
SpiralType::Archimedean => {
let inner_radius = LayoutGroup::Row {
widgets: number_widget(ParameterWidgetsInfo::new(node_id, InnerRadiusInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px")),
};
let inner_radius = LayoutGroup::row(number_widget(
ParameterWidgetsInfo::new(node_id, InnerRadiusInput::INDEX, true, context),
NumberInput::default().min(0.).unit(" px"),
));
let outer_radius = LayoutGroup::Row {
widgets: number_widget(ParameterWidgetsInfo::new(node_id, OuterRadiusInput::INDEX, true, context), NumberInput::default().unit(" px")),
};
let outer_radius = LayoutGroup::row(number_widget(
ParameterWidgetsInfo::new(node_id, OuterRadiusInput::INDEX, true, context),
NumberInput::default().unit(" px"),
));
widgets.extend([inner_radius, outer_radius]);
}
SpiralType::Logarithmic => {
let inner_radius = LayoutGroup::Row {
widgets: number_widget(ParameterWidgetsInfo::new(node_id, InnerRadiusInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px")),
};
let inner_radius = LayoutGroup::row(number_widget(
ParameterWidgetsInfo::new(node_id, InnerRadiusInput::INDEX, true, context),
NumberInput::default().min(0.).unit(" px"),
));
let outer_radius = LayoutGroup::Row {
widgets: number_widget(ParameterWidgetsInfo::new(node_id, OuterRadiusInput::INDEX, true, context), NumberInput::default().min(0.1).unit(" px")),
};
let outer_radius = LayoutGroup::row(number_widget(
ParameterWidgetsInfo::new(node_id, OuterRadiusInput::INDEX, true, context),
NumberInput::default().min(0.1).unit(" px"),
));
widgets.extend([inner_radius, outer_radius]);
}
@@ -1533,7 +1522,7 @@ pub(crate) fn spiral_properties(node_id: NodeId, context: &mut NodePropertiesCon
NumberInput::default().min(1.).max(180.).unit("°"),
);
widgets.push(LayoutGroup::Row { widgets: angular_resolution });
widgets.push(LayoutGroup::row(angular_resolution));
widgets
}
@@ -1574,13 +1563,13 @@ pub(crate) fn sample_polyline_properties(node_id: NodeId, context: &mut NodeProp
vec![
spacing.with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_SPACING),
match current_spacing {
Some(TaggedValue::PointSpacingType(PointSpacingType::Separation)) => LayoutGroup::Row { widgets: separation }.with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_SEPARATION),
Some(TaggedValue::PointSpacingType(PointSpacingType::Quantity)) => LayoutGroup::Row { widgets: quantity }.with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_QUANTITY),
_ => LayoutGroup::Row { widgets: vec![] },
Some(TaggedValue::PointSpacingType(PointSpacingType::Separation)) => LayoutGroup::row(separation).with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_SEPARATION),
Some(TaggedValue::PointSpacingType(PointSpacingType::Quantity)) => LayoutGroup::row(quantity).with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_QUANTITY),
_ => LayoutGroup::row(vec![]),
},
LayoutGroup::Row { widgets: start_offset }.with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_START_OFFSET),
LayoutGroup::Row { widgets: stop_offset }.with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_STOP_OFFSET),
LayoutGroup::Row { widgets: adaptive_spacing }.with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_ADAPTIVE_SPACING),
LayoutGroup::row(start_offset).with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_START_OFFSET),
LayoutGroup::row(stop_offset).with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_STOP_OFFSET),
LayoutGroup::row(adaptive_spacing).with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_ADAPTIVE_SPACING),
]
}
@@ -1594,11 +1583,7 @@ pub(crate) fn exposure_properties(node_id: NodeId, context: &mut NodePropertiesC
NumberInput::default().min(0.01).max(9.99).increment_step(0.1),
);
vec![
LayoutGroup::Row { widgets: exposure },
LayoutGroup::Row { widgets: offset },
LayoutGroup::Row { widgets: gamma_correction },
]
vec![LayoutGroup::row(exposure), LayoutGroup::row(offset), LayoutGroup::row(gamma_correction)]
}
pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
@@ -1722,11 +1707,11 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
let clamped = bool_widget(ParameterWidgetsInfo::new(node_id, ClampedInput::INDEX, true, context), CheckboxInput::default());
vec![
LayoutGroup::Row { widgets: size_x },
LayoutGroup::Row { widgets: size_y },
LayoutGroup::Row { widgets: corner_radius_row_1 },
LayoutGroup::Row { widgets: corner_radius_row_2 },
LayoutGroup::Row { widgets: clamped },
LayoutGroup::row(size_x),
LayoutGroup::row(size_y),
LayoutGroup::row(corner_radius_row_1),
LayoutGroup::row(corner_radius_row_2),
LayoutGroup::row(clamped),
]
}
@@ -1825,14 +1810,7 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper
let visible = context.network_interface.is_visible(&node_id, context.selection_network_path);
let pinned = context.network_interface.is_pinned(&node_id, context.selection_network_path);
LayoutGroup::Section {
name,
description,
visible,
pinned,
id: node_id.0,
layout: Layout(layout),
}
LayoutGroup::section(name, description, visible, pinned, node_id.0, Layout(layout))
}
/// Fill Node Widgets LayoutGroup
@@ -1856,7 +1834,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
) {
(fill, backup_color, backup_gradient)
} else {
return vec![LayoutGroup::Row { widgets: widgets_first_row }];
return vec![LayoutGroup::row(widgets_first_row)];
};
let fill2 = fill.clone();
let backup_color_fill: Fill = backup_color.clone().into();
@@ -1899,7 +1877,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
.on_commit(commit_value)
.widget_instance(),
);
let mut widgets = vec![LayoutGroup::Row { widgets: widgets_first_row }];
let mut widgets = vec![LayoutGroup::row(widgets_first_row)];
let fill_type_switch = {
let mut row = vec![TextLabel::new("").widget_instance()];
@@ -1942,7 +1920,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
RadioInput::new(entries).selected_index(Some(if fill.as_gradient().is_some() { 1 } else { 0 })).widget_instance(),
]);
LayoutGroup::Row { widgets: row }
LayoutGroup::row(row)
};
widgets.push(fill_type_switch);
@@ -2011,7 +1989,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
RadioInput::new(entries).selected_index(Some(gradient.gradient_type as u32)).widget_instance(),
]);
widgets.push(LayoutGroup::Row { widgets: row });
widgets.push(LayoutGroup::row(row));
}
widgets
@@ -2069,14 +2047,14 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) -
vec![
color,
LayoutGroup::Row { widgets: weight },
LayoutGroup::row(weight),
align,
cap,
join,
LayoutGroup::Row { widgets: miter_limit },
LayoutGroup::row(miter_limit),
paint_order,
LayoutGroup::Row { widgets: dash_lengths },
LayoutGroup::Row { widgets: dash_offset },
LayoutGroup::row(dash_lengths),
LayoutGroup::row(dash_offset),
]
}
@@ -2106,7 +2084,7 @@ pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesConte
});
let miter_limit = number_widget(ParameterWidgetsInfo::new(node_id, MiterLimitInput::INDEX, true, context), number_input);
vec![LayoutGroup::Row { widgets: distance }, join, LayoutGroup::Row { widgets: miter_limit }]
vec![LayoutGroup::row(distance), join, LayoutGroup::row(miter_limit)]
}
pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
@@ -2158,9 +2136,9 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) ->
let operand_a_hint = vec![TextLabel::new("(Operand A is the primary input)").widget_instance()];
vec![
LayoutGroup::Row { widgets: expression }.with_tooltip_description(r#"A math expression that may incorporate "A" and/or "B", such as "sqrt(A + B) - B^2"."#),
LayoutGroup::Row { widgets: operand_b }.with_tooltip_description(r#"The value of "B" when calculating the expression."#),
LayoutGroup::Row { widgets: operand_a_hint }.with_tooltip_description(r#""A" is fed by the value from the previous node in the primary data flow, or it is 0 if disconnected."#),
LayoutGroup::row(expression).with_tooltip_description(r#"A math expression that may incorporate "A" and/or "B", such as "sqrt(A + B) - B^2"."#),
LayoutGroup::row(operand_b).with_tooltip_description(r#"The value of "B" when calculating the expression."#),
LayoutGroup::row(operand_a_hint).with_tooltip_description(r#""A" is fed by the value from the previous node in the primary data flow, or it is 0 if disconnected."#),
]
}
@@ -2348,14 +2326,14 @@ pub mod choice {
let ParameterWidgetsInfo { document_node, node_id, index, .. } = self.parameter_info;
let Some(document_node) = document_node else {
log::error!("Could not get document node when building property row for node {node_id:?}");
return LayoutGroup::Row { widgets: Vec::new() };
return LayoutGroup::row(Vec::new());
};
let mut widgets = super::start_widgets(self.parameter_info);
let Some(input) = document_node.inputs.get(index) else {
log::warn!("A widget failed to be built because its node's input index is invalid.");
return LayoutGroup::Row { widgets: vec![] };
return LayoutGroup::row(vec![]);
};
let input: Option<W::Value> = input.as_non_exposed_value().and_then(|v| <&W::Value as TryFrom<&TaggedValue>>::try_from(v).ok()).cloned();
@@ -2367,7 +2345,7 @@ pub mod choice {
widgets.extend_from_slice(&[Separator::new(SeparatorStyle::Unrelated).widget_instance(), widget]);
}
let mut row = LayoutGroup::Row { widgets };
let mut row = LayoutGroup::row(widgets);
if let Some(desc) = self.widget_factory.description() {
row = row.with_tooltip_description(desc);
}
@@ -1,9 +1,9 @@
use glam::IVec2;
use graph_craft::document::NodeId;
use graph_craft::document::value::TaggedValue;
use graphene_std::Type;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub enum FrontendGraphDataType {
#[default]
General,
@@ -42,7 +42,8 @@ impl FrontendGraphDataType {
}
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct FrontendGraphInput {
#[serde(rename = "dataType")]
pub data_type: FrontendGraphDataType,
@@ -57,21 +58,23 @@ pub struct FrontendGraphInput {
pub connected_to: String,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct FrontendGraphOutput {
#[serde(rename = "dataType")]
pub data_type: FrontendGraphDataType,
pub name: String,
pub description: String,
#[serde(rename = "resolvedType")]
pub resolved_type: String,
pub description: String,
/// If connected to an export, it is "export index {index}".
/// If connected to a node, it is "{node name} input {input_index}".
#[serde(rename = "connectedTo")]
pub connected_to: Vec<String>,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct FrontendNode {
pub id: graph_craft::document::NodeId,
#[serde(rename = "isLayer")]
@@ -95,13 +98,14 @@ pub struct FrontendNode {
pub primary_input_connected_to_layer: bool,
#[serde(rename = "primaryOutputConnectedToLayer")]
pub primary_output_connected_to_layer: bool,
pub position: IVec2,
pub position: (i32, i32),
pub previewed: bool,
pub visible: bool,
pub locked: bool,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct FrontendNodeType {
pub identifier: String,
pub name: String,
@@ -110,7 +114,8 @@ pub struct FrontendNodeType {
pub input_types: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct DragStart {
pub start_x: f64,
pub start_y: f64,
@@ -118,14 +123,8 @@ pub struct DragStart {
pub round_y: i32,
}
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct Transform {
pub scale: f64,
pub x: f64,
pub y: f64,
}
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct BoxSelection {
#[serde(rename = "startX")]
pub start_x: u32,
@@ -137,7 +136,8 @@ pub struct BoxSelection {
pub end_y: u32,
}
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type", content = "data")]
pub enum ContextMenuData {
ModifyNode {
@@ -158,22 +158,25 @@ pub enum ContextMenuData {
},
}
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct ContextMenuInformation {
// Stores whether the context menu is open and its position in graph coordinates
#[serde(rename = "contextMenuCoordinates")]
pub context_menu_coordinates: IVec2,
pub context_menu_coordinates: (i32, i32),
#[serde(rename = "contextMenuData")]
pub context_menu_data: ContextMenuData,
}
#[derive(Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize)]
pub struct NodeGraphErrorDiagnostic {
pub position: IVec2,
pub position: (i32, i32),
pub error: String,
}
#[derive(Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize)]
pub struct FrontendClickTargets {
#[serde(rename = "nodeClickTargets")]
pub node_click_targets: Vec<String>,
@@ -189,7 +192,8 @@ pub struct FrontendClickTargets {
pub modify_import_export: Vec<String>,
}
#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum Direction {
Up,
Down,
@@ -228,42 +228,38 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec<LayoutGroup> {
})
};
widgets.push(LayoutGroup::Row {
widgets: vec![TextLabel::new("Grid").bold(true).widget_instance()],
});
widgets.push(LayoutGroup::row(vec![TextLabel::new("Grid").bold(true).widget_instance()]));
widgets.push(LayoutGroup::Row {
widgets: vec![
TextLabel::new("Type").table_align(true).widget_instance(),
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
RadioInput::new(vec![
RadioEntryData::new("rectangular").label("Rectangular").on_update(update_val(grid, |grid, _| {
if let GridType::Isometric { y_axis_spacing, angle_a, angle_b } = grid.grid_type {
grid.isometric_y_spacing = y_axis_spacing;
grid.isometric_angle_a = angle_a;
grid.isometric_angle_b = angle_b;
}
grid.grid_type = GridType::Rectangular { spacing: grid.rectangular_spacing };
})),
RadioEntryData::new("isometric").label("Isometric").on_update(update_val(grid, |grid, _| {
if let GridType::Rectangular { spacing } = grid.grid_type {
grid.rectangular_spacing = spacing;
}
grid.grid_type = GridType::Isometric {
y_axis_spacing: grid.isometric_y_spacing,
angle_a: grid.isometric_angle_a,
angle_b: grid.isometric_angle_b,
};
})),
])
.min_width(200)
.selected_index(Some(match grid.grid_type {
GridType::Rectangular { .. } => 0,
GridType::Isometric { .. } => 1,
}))
.widget_instance(),
],
});
widgets.push(LayoutGroup::row(vec![
TextLabel::new("Type").table_align(true).widget_instance(),
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
RadioInput::new(vec![
RadioEntryData::new("rectangular").label("Rectangular").on_update(update_val(grid, |grid, _| {
if let GridType::Isometric { y_axis_spacing, angle_a, angle_b } = grid.grid_type {
grid.isometric_y_spacing = y_axis_spacing;
grid.isometric_angle_a = angle_a;
grid.isometric_angle_b = angle_b;
}
grid.grid_type = GridType::Rectangular { spacing: grid.rectangular_spacing };
})),
RadioEntryData::new("isometric").label("Isometric").on_update(update_val(grid, |grid, _| {
if let GridType::Rectangular { spacing } = grid.grid_type {
grid.rectangular_spacing = spacing;
}
grid.grid_type = GridType::Isometric {
y_axis_spacing: grid.isometric_y_spacing,
angle_a: grid.isometric_angle_a,
angle_b: grid.isometric_angle_b,
};
})),
])
.min_width(200)
.selected_index(Some(match grid.grid_type {
GridType::Rectangular { .. } => 0,
GridType::Isometric { .. } => 1,
}))
.widget_instance(),
]));
let mut color_widgets = vec![
TextLabel::new("Display").table_align(true).widget_instance(),
@@ -288,80 +284,72 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec<LayoutGroup> {
}))
.widget_instance(),
);
widgets.push(LayoutGroup::Row { widgets: color_widgets });
widgets.push(LayoutGroup::row(color_widgets));
widgets.push(LayoutGroup::Row {
widgets: vec![
TextLabel::new("Origin").table_align(true).widget_instance(),
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
NumberInput::new(Some(grid.origin.x))
.label("X")
.unit(" px")
.min_width(98)
.on_update(update_origin(grid, |grid| Some(&mut grid.origin.x)))
.widget_instance(),
Separator::new(SeparatorStyle::Related).widget_instance(),
NumberInput::new(Some(grid.origin.y))
.label("Y")
.unit(" px")
.min_width(98)
.on_update(update_origin(grid, |grid| Some(&mut grid.origin.y)))
.widget_instance(),
],
});
widgets.push(LayoutGroup::row(vec![
TextLabel::new("Origin").table_align(true).widget_instance(),
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
NumberInput::new(Some(grid.origin.x))
.label("X")
.unit(" px")
.min_width(98)
.on_update(update_origin(grid, |grid| Some(&mut grid.origin.x)))
.widget_instance(),
Separator::new(SeparatorStyle::Related).widget_instance(),
NumberInput::new(Some(grid.origin.y))
.label("Y")
.unit(" px")
.min_width(98)
.on_update(update_origin(grid, |grid| Some(&mut grid.origin.y)))
.widget_instance(),
]));
match grid.grid_type {
GridType::Rectangular { spacing } => widgets.push(LayoutGroup::Row {
widgets: vec![
TextLabel::new("Spacing").table_align(true).widget_instance(),
GridType::Rectangular { spacing } => widgets.push(LayoutGroup::row(vec![
TextLabel::new("Spacing").table_align(true).widget_instance(),
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
NumberInput::new(Some(spacing.x))
.label("X")
.unit(" px")
.min(0.)
.min_width(98)
.on_update(update_origin(grid, |grid| grid.grid_type.rectangular_spacing().map(|spacing| &mut spacing.x)))
.widget_instance(),
Separator::new(SeparatorStyle::Related).widget_instance(),
NumberInput::new(Some(spacing.y))
.label("Y")
.unit(" px")
.min(0.)
.min_width(98)
.on_update(update_origin(grid, |grid| grid.grid_type.rectangular_spacing().map(|spacing| &mut spacing.y)))
.widget_instance(),
])),
GridType::Isometric { y_axis_spacing, angle_a, angle_b } => {
widgets.push(LayoutGroup::row(vec![
TextLabel::new("Y Spacing").table_align(true).widget_instance(),
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
NumberInput::new(Some(spacing.x))
.label("X")
NumberInput::new(Some(y_axis_spacing))
.unit(" px")
.min(0.)
.min_width(200)
.on_update(update_origin(grid, |grid| grid.grid_type.isometric_y_spacing()))
.widget_instance(),
]));
widgets.push(LayoutGroup::row(vec![
TextLabel::new("Angles").table_align(true).widget_instance(),
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
NumberInput::new(Some(angle_a))
.unit("°")
.min_width(98)
.on_update(update_origin(grid, |grid| grid.grid_type.rectangular_spacing().map(|spacing| &mut spacing.x)))
.on_update(update_origin(grid, |grid| grid.grid_type.angle_a()))
.widget_instance(),
Separator::new(SeparatorStyle::Related).widget_instance(),
NumberInput::new(Some(spacing.y))
.label("Y")
.unit(" px")
.min(0.)
NumberInput::new(Some(angle_b))
.unit("°")
.min_width(98)
.on_update(update_origin(grid, |grid| grid.grid_type.rectangular_spacing().map(|spacing| &mut spacing.y)))
.on_update(update_origin(grid, |grid| grid.grid_type.angle_b()))
.widget_instance(),
],
}),
GridType::Isometric { y_axis_spacing, angle_a, angle_b } => {
widgets.push(LayoutGroup::Row {
widgets: vec![
TextLabel::new("Y Spacing").table_align(true).widget_instance(),
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
NumberInput::new(Some(y_axis_spacing))
.unit(" px")
.min(0.)
.min_width(200)
.on_update(update_origin(grid, |grid| grid.grid_type.isometric_y_spacing()))
.widget_instance(),
],
});
widgets.push(LayoutGroup::Row {
widgets: vec![
TextLabel::new("Angles").table_align(true).widget_instance(),
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
NumberInput::new(Some(angle_a))
.unit("°")
.min_width(98)
.on_update(update_origin(grid, |grid| grid.grid_type.angle_a()))
.widget_instance(),
Separator::new(SeparatorStyle::Related).widget_instance(),
NumberInput::new(Some(angle_b))
.unit("°")
.min_width(98)
.on_update(update_origin(grid, |grid| grid.grid_type.angle_b()))
.widget_instance(),
],
});
]));
}
}
@@ -42,7 +42,8 @@ pub enum GizmoEmphasis {
// TODO Remove duplicated definition of this in `utility_types_web.rs`
/// Types of overlays used by DocumentMessage to enable/disable the selected set of viewport overlays.
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum OverlaysType {
ArtboardName,
CompassRose,
@@ -60,7 +61,8 @@ pub enum OverlaysType {
}
// TODO Remove duplicated definition of this in `utility_types_web.rs`
#[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
pub struct OverlaysVisibilitySettings {
pub all: bool,
@@ -160,11 +162,11 @@ impl OverlaysVisibilitySettings {
}
}
#[derive(serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(serde::Serialize, serde::Deserialize)]
pub struct OverlayContext {
// Serde functionality isn't used but is required by the message system macros
#[serde(skip)]
#[specta(skip)]
internal: Arc<Mutex<OverlayContextInternal>>,
pub viewport: ViewportMessageHandler,
pub visibility_settings: OverlaysVisibilitySettings,
@@ -35,7 +35,8 @@ pub enum GizmoEmphasis {
}
/// Types of overlays used by DocumentMessage to enable/disable the selected set of viewport overlays.
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum OverlaysType {
ArtboardName,
CompassRose,
@@ -52,7 +53,8 @@ pub enum OverlaysType {
Handles,
}
#[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
pub struct OverlaysVisibilitySettings {
pub all: bool,
@@ -150,11 +152,11 @@ impl OverlaysVisibilitySettings {
}
}
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct OverlayContext {
// Serde functionality isn't used but is required by the message system macros
#[serde(skip, default = "overlay_canvas_context")]
#[specta(skip)]
pub render_context: web_sys::CanvasRenderingContext2d,
pub viewport: ViewportMessageHandler,
pub visibility_settings: OverlaysVisibilitySettings,
@@ -2,7 +2,8 @@ use super::network_interface::NodeTemplate;
use graph_craft::document::NodeId;
#[repr(u8)]
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, Eq, Debug, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
pub enum Clipboard {
Internal,
Device,
@@ -234,7 +234,8 @@ impl DocumentMetadata {
// ===================
/// ID of a layer node
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize)]
pub struct LayerNodeIdentifier(NonZeroU64);
impl core::fmt::Debug for LayerNodeIdentifier {
@@ -3,7 +3,8 @@ use glam::DVec2;
use std::fmt;
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify), tsify(large_number_types_as_bigints))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
pub struct DocumentId(pub u64);
#[derive(PartialEq, Eq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize, Hash)]
@@ -12,13 +13,15 @@ pub enum FlipAxis {
Y,
}
#[derive(PartialEq, Eq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize, Hash, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(PartialEq, Eq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize, Hash)]
pub enum AlignAxis {
X,
Y,
}
#[derive(PartialEq, Eq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize, Hash, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(PartialEq, Eq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize, Hash)]
pub enum AlignAggregate {
Min,
Max,
@@ -5723,14 +5723,16 @@ impl Iterator for FlowIter<'_> {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ImportOrExport {
Import(usize),
Export(usize),
}
/// Represents an input connector with index based on the [`DocumentNode::inputs`] index, not the visible input index
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum InputConnector {
#[serde(rename = "node")]
Node {
@@ -5770,7 +5772,8 @@ impl InputConnector {
}
/// Represents an output connector
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum OutputConnector {
#[serde(rename = "node")]
Node {
@@ -1,25 +1,28 @@
use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use super::network_interface::NodeNetworkInterface;
use crate::messages::frontend::IconName;
use crate::messages::tool::common_functionality::graph_modification_utils;
use glam::DVec2;
use graph_craft::document::{NodeId, NodeNetwork};
/// Represents an entry in the layer tree hierarchy, sent to the frontend.
/// Each entry contains its layer ID and a list of its visible children.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub struct LayerStructureEntry {
#[serde(rename = "layerId")]
pub layer_id: NodeId,
pub children: Vec<LayerStructureEntry>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub struct LayerPanelEntry {
pub id: NodeId,
#[serde(rename = "implementationName")]
pub implementation_name: String,
#[serde(rename = "iconName")]
pub icon_name: Option<String>,
pub icon_name: Option<IconName>,
pub alias: String,
#[serde(rename = "inSelectedNetwork")]
pub in_selected_network: bool,
@@ -47,7 +50,8 @@ pub struct LayerPanelEntry {
}
/// IMPORTANT: the same node may appear multiple times.
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub struct SelectedNodes(pub Vec<NodeId>);
impl SelectedNodes {
@@ -157,5 +161,6 @@ impl SelectedNodes {
}
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub struct CollapsedLayers(pub Vec<LayerNodeIdentifier>);
@@ -3,7 +3,8 @@ use glam::{DVec2, IVec2};
use graphene_std::{uuid::NodeId, vector::misc::dvec2_to_point};
use kurbo::{BezPath, DEFAULT_ACCURACY, Line, Point, Shape};
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct WirePath {
#[serde(rename = "pathString")]
pub path_string: String,
@@ -13,7 +14,8 @@ pub struct WirePath {
pub dashed: bool,
}
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct WirePathUpdate {
pub id: NodeId,
#[serde(rename = "inputIndex")]
@@ -23,7 +25,8 @@ pub struct WirePathUpdate {
pub wire_path_update: Option<WirePath>,
}
#[derive(Copy, Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Copy, Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize)]
pub enum GraphWireStyle {
#[default]
Direct = 0,