From 52d2b38a82dfdaf7b0e843004de3fe838b6ff5af Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Mon, 9 Mar 2026 14:46:26 -0700 Subject: [PATCH] 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 --- .github/workflows/ci.yml | 2 +- .github/workflows/website.yml | 2 +- .vscode/settings.json | 15 +- Cargo.lock | 106 ++- Cargo.toml | 8 +- deny.toml | 2 +- .../wrapper/src/intercept_frontend_message.rs | 2 + desktop/wrapper/src/utils.rs | 10 +- editor/Cargo.toml | 3 +- editor/src/dispatcher.rs | 2 +- editor/src/generate_ts_types.rs | 34 - editor/src/lib.rs | 1 - .../app_window/app_window_message_handler.rs | 14 +- .../export_dialog_message_handler.rs | 10 +- .../new_document_dialog_message_handler.rs | 4 +- .../preferences_dialog_message_handler.rs | 4 +- .../simple_dialogs/about_graphite_dialog.rs | 20 +- .../close_all_documents_dialog.rs | 10 +- .../simple_dialogs/close_document_dialog.rs | 10 +- .../simple_dialogs/confirm_restart_dialog.rs | 26 +- .../simple_dialogs/demo_artwork_dialog.rs | 6 +- .../dialog/simple_dialogs/error_dialog.rs | 10 +- .../dialog/simple_dialogs/licenses_dialog.rs | 14 +- .../licenses_third_party_dialog.rs | 18 +- .../src/messages/frontend/frontend_message.rs | 50 +- editor/src/messages/frontend/mod.rs | 4 + editor/src/messages/frontend/utility_types.rs | 20 +- .../utility_types/input_keyboard.rs | 15 +- .../input_mapper/utility_types/input_mouse.rs | 3 +- .../input_mapper/utility_types/misc.rs | 4 +- .../messages/layout/layout_message_handler.rs | 67 +- .../layout/utility_types/layout_widget.rs | 173 ++-- .../utility_types/widgets/button_widgets.rs | 34 +- .../utility_types/widgets/input_widgets.rs | 67 +- .../utility_types/widgets/label_widgets.rs | 26 +- .../menu_bar/menu_bar_message_handler.rs | 4 +- editor/src/messages/message.rs | 8 - .../data_panel/data_panel_message_handler.rs | 40 +- .../document/document_message_handler.rs | 520 ++++++------ .../node_graph/document_node_definitions.rs | 40 +- .../node_graph/node_graph_message_handler.rs | 173 ++-- .../document/node_graph/node_properties.rs | 164 ++-- .../document/node_graph/utility_types.rs | 52 +- .../document/overlays/grid_overlays.rs | 184 ++--- .../document/overlays/utility_types_native.rs | 10 +- .../document/overlays/utility_types_web.rs | 10 +- .../document/utility_types/clipboards.rs | 3 +- .../utility_types/document_metadata.rs | 3 +- .../portfolio/document/utility_types/misc.rs | 9 +- .../utility_types/network_interface.rs | 9 +- .../portfolio/document/utility_types/nodes.rs | 15 +- .../portfolio/document/utility_types/wires.rs | 9 +- .../portfolio/portfolio_message_handler.rs | 18 +- .../preferences_message_handler.rs | 3 +- .../src/messages/preferences/utility_types.rs | 3 +- .../common_functionality/color_selector.rs | 3 +- .../tool/common_functionality/pivot.rs | 6 +- .../shapes/shape_utility.rs | 3 +- .../tool/tool_messages/artboard_tool.rs | 3 +- .../messages/tool/tool_messages/brush_tool.rs | 11 +- .../tool/tool_messages/eyedropper_tool.rs | 29 +- .../messages/tool/tool_messages/fill_tool.rs | 3 +- .../tool/tool_messages/freehand_tool.rs | 8 +- .../tool/tool_messages/gradient_tool.rs | 19 +- .../tool/tool_messages/navigate_tool.rs | 3 +- .../messages/tool/tool_messages/path_tool.rs | 61 +- .../messages/tool/tool_messages/pen_tool.rs | 11 +- .../tool/tool_messages/select_tool.rs | 14 +- .../messages/tool/tool_messages/shape_tool.rs | 8 +- .../tool/tool_messages/spline_tool.rs | 8 +- .../messages/tool/tool_messages/text_tool.rs | 12 +- editor/src/messages/tool/utility_types.rs | 48 +- .../viewport/viewport_message_handler.rs | 21 +- editor/src/node_graph_executor.rs | 7 +- editor/src/node_graph_executor/runtime.rs | 4 +- frontend/README.md | 31 +- frontend/eslint.config.js | 2 +- frontend/package-lock.json | 48 ++ frontend/package.json | 5 +- frontend/src/App.svelte | 2 +- frontend/src/README.md | 24 +- frontend/src/components/Editor.svelte | 2 +- frontend/src/components/README.md | 8 +- .../floating-menus/ColorPicker.svelte | 153 ++-- .../components/floating-menus/Dialog.svelte | 15 +- .../components/floating-menus/MenuList.svelte | 6 +- .../floating-menus/NodeCatalog.svelte | 2 +- .../components/floating-menus/Tooltip.svelte | 7 +- .../src/components/layout/FloatingMenu.svelte | 12 +- .../src/components/layout/LayoutCol.svelte | 2 +- .../src/components/layout/LayoutRow.svelte | 2 +- frontend/src/components/panels/Data.svelte | 2 +- .../src/components/panels/Document.svelte | 52 +- frontend/src/components/panels/Layers.svelte | 2 +- .../src/components/panels/Properties.svelte | 2 +- frontend/src/components/panels/Welcome.svelte | 6 +- frontend/src/components/views/Graph.svelte | 8 +- .../components/widgets/WidgetLayout.svelte | 17 +- .../components/widgets/WidgetSection.svelte | 11 +- .../src/components/widgets/WidgetSpan.svelte | 182 +++-- .../src/components/widgets/WidgetTable.svelte | 9 +- .../buttons/BreadcrumbTrailButtons.svelte | 2 +- .../widgets/buttons/IconButton.svelte | 2 +- .../widgets/buttons/ImageButton.svelte | 2 +- .../buttons/ParameterExposeButton.svelte | 2 +- .../widgets/buttons/PopoverButton.svelte | 3 +- .../widgets/buttons/TextButton.svelte | 4 +- .../widgets/inputs/CheckboxInput.svelte | 10 +- .../widgets/inputs/ColorInput.svelte | 17 +- .../widgets/inputs/CurveInput.svelte | 2 +- .../widgets/inputs/DropdownInput.svelte | 21 +- .../widgets/inputs/FieldInput.svelte | 2 +- .../widgets/inputs/NumberInput.svelte | 20 +- .../widgets/inputs/RadioInput.svelte | 2 +- .../widgets/inputs/ReferencePointInput.svelte | 2 +- .../widgets/inputs/RulerInput.svelte | 6 +- .../widgets/inputs/ScrollbarInput.svelte | 6 +- .../widgets/inputs/SpectrumInput.svelte | 18 +- .../widgets/inputs/TextAreaInput.svelte | 2 +- .../widgets/inputs/TextInput.svelte | 2 +- .../widgets/inputs/WorkingColorsInput.svelte | 18 +- .../widgets/labels/IconLabel.svelte | 2 +- .../widgets/labels/ImageLabel.svelte | 2 +- .../widgets/labels/Separator.svelte | 2 +- .../widgets/labels/ShortcutLabel.svelte | 21 +- .../widgets/labels/TextLabel.svelte | 2 +- .../src/components/window/MainWindow.svelte | 4 +- frontend/src/components/window/Panel.svelte | 36 +- .../src/components/window/StatusBar.svelte | 2 +- .../src/components/window/TitleBar.svelte | 10 +- .../src/components/window/Workspace.svelte | 24 +- frontend/src/editor.ts | 15 +- frontend/src/global.d.ts | 47 ++ frontend/src/io-managers/fonts.ts | 2 +- frontend/src/io-managers/input.ts | 28 +- frontend/src/io-managers/panic.ts | 3 +- frontend/src/io-managers/persistence.ts | 24 +- frontend/src/messages.ts | 766 ------------------ frontend/src/state-providers/app-window.ts | 12 +- frontend/src/state-providers/dialog.ts | 20 +- frontend/src/state-providers/document.ts | 24 +- frontend/src/state-providers/fullscreen.ts | 8 +- frontend/src/state-providers/node-graph.ts | 66 +- frontend/src/state-providers/portfolio.ts | 13 +- frontend/src/state-providers/tooltip.ts | 19 +- frontend/src/subscription-router.ts | 78 +- frontend/src/utility-functions/colors.ts | 117 ++- frontend/src/utility-functions/files.ts | 26 +- .../src/utility-functions/keyboard-entry.ts | 8 +- frontend/src/utility-functions/platform.ts | 23 - frontend/src/utility-functions/viewports.ts | 3 +- frontend/src/utility-functions/widgets.ts | 122 +-- frontend/svelte.config.js | 8 + frontend/vite.config.ts | 21 +- frontend/wasm/README.md | 19 +- frontend/wasm/src/editor_api.rs | 15 +- frontend/wasm/src/lib.rs | 2 +- ...ommuncation.rs => native_communication.rs} | 0 node-graph/README.md | 2 +- node-graph/graph-craft/Cargo.toml | 10 +- node-graph/graph-craft/src/document.rs | 2 +- node-graph/graph-craft/src/document/value.rs | 2 + node-graph/graph-craft/src/proto.rs | 4 +- .../graph-craft/src/wasm_application_io.rs | 3 +- node-graph/libraries/core-types/Cargo.toml | 4 +- node-graph/libraries/core-types/src/lib.rs | 3 +- node-graph/libraries/core-types/src/text.rs | 3 +- node-graph/libraries/core-types/src/types.rs | 10 +- node-graph/libraries/core-types/src/uuid.rs | 12 +- node-graph/libraries/graphic-types/Cargo.toml | 8 +- node-graph/libraries/no-std-types/Cargo.toml | 7 +- .../libraries/no-std-types/src/blending.rs | 6 +- .../no-std-types/src/color/color_types.rs | 9 +- node-graph/libraries/raster-types/Cargo.toml | 4 +- .../libraries/raster-types/src/image.rs | 14 +- node-graph/libraries/vector-types/Cargo.toml | 4 +- .../libraries/vector-types/src/gradient.rs | 9 +- .../libraries/vector-types/src/vector/misc.rs | 24 +- .../src/vector/reference_point.rs | 3 +- .../vector-types/src/vector/style.rs | 30 +- node-graph/nodes/gcore/Cargo.toml | 10 +- node-graph/nodes/gcore/src/extract_xy.rs | 3 +- node-graph/nodes/gstd/Cargo.toml | 8 + node-graph/nodes/path-bool/Cargo.toml | 8 +- node-graph/nodes/path-bool/src/lib.rs | 6 +- node-graph/nodes/raster/Cargo.toml | 19 +- node-graph/nodes/raster/src/adjustments.rs | 30 +- node-graph/nodes/raster/src/curve.rs | 6 +- node-graph/nodes/text/Cargo.toml | 3 + node-graph/nodes/text/src/font_cache.rs | 6 +- node-graph/nodes/text/src/lib.rs | 6 +- node-graph/nodes/vector/Cargo.toml | 3 + .../nodes/vector/src/generator_nodes.rs | 5 +- proc-macros/src/widget_builder.rs | 57 +- ...buted-computing-in-the-graphene-runtime.md | 2 +- .../guide/codebase-overview/_index.md | 2 +- .../guide/codebase-overview/debugging-tips.md | 6 + .../volunteer/guide/project-setup/_index.md | 19 +- website/package.json | 4 +- 199 files changed, 2267 insertions(+), 2813 deletions(-) delete mode 100644 editor/src/generate_ts_types.rs create mode 100644 frontend/src/global.d.ts delete mode 100644 frontend/src/messages.ts create mode 100644 frontend/svelte.config.js rename frontend/wasm/src/{native_communcation.rs => native_communication.rs} (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4985abfe..51a8feed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -117,7 +117,7 @@ jobs: NODE_ENV: production run: | cd frontend - npm run lint + npm run check # Run the Rust tests on the self-hosted native runner test: diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 3b8fb54e..e4bb6d1a 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -94,7 +94,7 @@ jobs: run: | cd website npm ci - npm run lint + npm run check zola --config config.toml build --minify - name: 📤 Publish to Cloudflare Pages diff --git a/.vscode/settings.json b/.vscode/settings.json index d1c8a4ae..f9831b6d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -39,25 +39,12 @@ "rust-analyzer.check.command": "clippy", "rust-analyzer.cargo.allTargets": false, "rust-analyzer.procMacro.ignored": { - "serde_derive": ["Serialize", "Deserialize"], - "specta_macros": ["Type"] // Disabled because of: https://github.com/specta-rs/specta/issues/387 + "serde_derive": ["Serialize", "Deserialize"] }, // ESLint config "eslint.format.enable": true, "eslint.workingDirectories": ["./frontend", "./website"], "eslint.validate": ["javascript", "typescript", "svelte"], - // Svelte config - "svelte.plugin.svelte.compilerWarnings": { - "css-unused-selector": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts` - "vite-plugin-svelte-css-no-scopable-elements": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts` - "a11y-no-static-element-interactions": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts` - "a11y-no-noninteractive-element-interactions": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts` - "a11y-click-events-have-key-events": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts` - "a11y_consider_explicit_label": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts` - "a11y_click_events_have_key_events": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts` - "a11y_no_noninteractive_element_interactions": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts` - "a11y_no_static_element_interactions": "ignore" // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts` - }, // Git Graph config "git-graph.repository.fetchAndPrune": true, "git-graph.repository.showRemoteHeads": false, diff --git a/Cargo.lock b/Cargo.lock index 2fd2e3d2..853d170a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" - [[package]] name = "ab_glyph" version = "0.2.31" @@ -1157,9 +1151,10 @@ dependencies = [ "serde", "serde_json", "skrifa 0.40.0", - "specta", "tinyvec", "tokio", + "tsify", + "wasm-bindgen", ] [[package]] @@ -2290,9 +2285,9 @@ dependencies = [ "rustc-hash 2.1.1", "serde", "serde_json", - "specta", "text-nodes", "tokio", + "tsify", "url", "vector-nodes", "wasm-bindgen", @@ -2348,7 +2343,8 @@ dependencies = [ "raster-types", "serde", "serde_json", - "specta", + "tsify", + "wasm-bindgen", ] [[package]] @@ -2414,8 +2410,8 @@ dependencies = [ "raster-types", "serde", "serde_json", - "specta", "vector-types", + "wasm-bindgen", ] [[package]] @@ -2538,11 +2534,12 @@ dependencies = [ "once_cell", "preprocessor", "serde", + "serde_bytes", "serde_json", - "specta", "spin", "thiserror 2.0.18", "tokio", + "tsify", "usvg", "vello", "wasm-bindgen", @@ -3740,8 +3737,9 @@ dependencies = [ "num-traits", "num_enum", "serde", - "specta", "spirv-std", + "tsify", + "wasm-bindgen", ] [[package]] @@ -4295,8 +4293,9 @@ dependencies = [ "node-macro", "path-bool", "serde", - "specta", + "tsify", "vector-types", + "wasm-bindgen", ] [[package]] @@ -4919,10 +4918,11 @@ dependencies = [ "raster-nodes-shaders", "raster-types", "serde", - "specta", "spirv-std", "tokio", + "tsify", "vector-types", + "wasm-bindgen", "wgpu-executor", ] @@ -4955,7 +4955,8 @@ dependencies = [ "node-macro", "serde", "serde_json", - "specta", + "tsify", + "wasm-bindgen", "wgpu", ] @@ -5639,6 +5640,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -5659,6 +5670,17 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "serde_json" version = "1.0.143" @@ -5905,29 +5927,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "specta" -version = "2.0.0-rc.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab7f01e9310a820edd31c80fde3cae445295adde21a3f9416517d7d65015b971" -dependencies = [ - "glam", - "specta-macros", - "thiserror 1.0.69", -] - -[[package]] -name = "specta-macros" -version = "2.0.0-rc.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0074b9e30ed84c6924eb63ad8d2fe71cdc82628525d84b1fcb1f2fd40676517" -dependencies = [ - "Inflector", - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "spin" version = "0.10.0" @@ -6245,7 +6244,9 @@ dependencies = [ "parley", "serde", "skrifa 0.40.0", + "tsify", "vector-types", + "wasm-bindgen", ] [[package]] @@ -6665,6 +6666,30 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tsify" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ec91b85e6c6592ed28636cb1dd1fac377ecbbeb170ff1d79f97aac5e38926d" +dependencies = [ + "serde", + "serde-wasm-bindgen", + "tsify-macros", + "wasm-bindgen", +] + +[[package]] +name = "tsify-macros" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc2c44dc9fe4baf55b88e032621b7a11b215a1f0a7de8d0aa04367207d915bc" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.106", +] + [[package]] name = "ttf-parser" version = "0.25.1" @@ -6909,7 +6934,9 @@ dependencies = [ "rustc-hash 2.1.1", "serde", "tokio", + "tsify", "vector-types", + "wasm-bindgen", ] [[package]] @@ -6931,8 +6958,9 @@ dependencies = [ "polycool", "rustc-hash 2.1.1", "serde", - "specta", "tinyvec", + "tsify", + "wasm-bindgen", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 2277419d..a7bd74b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -98,6 +98,7 @@ rustc-hash = "2.0" bytemuck = { version = "1.13", features = ["derive", "min_const_generics"] } serde = { version = "1.0", features = ["derive", "rc"] } serde_json = "1.0" +serde_bytes = "0.11" serde-wasm-bindgen = "0.6" reqwest = { version = "0.13", features = ["blocking", "json"] } futures = "0.3" @@ -176,11 +177,7 @@ fern = { version = "0.7", features = ["colored"] } num_enum = { version = "0.7", default-features = false } num-derive = "0.4" num-traits = { version = "0.2", default-features = false, features = ["libm"] } -specta = { version = "2.0.0-rc.22", features = [ - "glam", - "derive", - # "typescript", -] } +tsify = { version = "0.5", default-features = false, features = ["js"] } syn = { version = "2.0", default-features = false, features = [ "full", "derive", @@ -230,7 +227,6 @@ graphite-proc-macros = { opt-level = 1 } image = { opt-level = 2 } rustc-hash = { opt-level = 3 } serde_derive = { opt-level = 1 } -specta-macros = { opt-level = 1 } syn = { opt-level = 1 } node-macro = { opt-level = 2 } diff --git a/deny.toml b/deny.toml index ca81b234..b411f461 100644 --- a/deny.toml +++ b/deny.toml @@ -183,7 +183,7 @@ allow-git = [] [sources.allow-org] # 1 or more github.com organizations to allow git sources for -github = ["linebender", "Rust-GPU", "specta-rs"] +github = ["linebender", "Rust-GPU"] # 1 or more gitlab.com organizations to allow git sources for #gitlab = [""] # 1 or more bitbucket.org organizations to allow git sources for diff --git a/desktop/wrapper/src/intercept_frontend_message.rs b/desktop/wrapper/src/intercept_frontend_message.rs index fe430ad4..f9c2bd1d 100644 --- a/desktop/wrapper/src/intercept_frontend_message.rs +++ b/desktop/wrapper/src/intercept_frontend_message.rs @@ -26,6 +26,7 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD }); } FrontendMessage::TriggerSaveDocument { document_id, name, path, content } => { + let content = content.into_vec(); if let Some(path) = path { dispatcher.respond(DesktopFrontendMessage::WriteFile { path, content }); } else { @@ -42,6 +43,7 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD } } FrontendMessage::TriggerSaveFile { name, content } => { + let content = content.into_vec(); dispatcher.respond(DesktopFrontendMessage::SaveFileDialog { title: "Save File".to_string(), default_filename: name, diff --git a/desktop/wrapper/src/utils.rs b/desktop/wrapper/src/utils.rs index ca246dfa..0d24ecb9 100644 --- a/desktop/wrapper/src/utils.rs +++ b/desktop/wrapper/src/utils.rs @@ -6,7 +6,7 @@ pub(crate) mod menu { use graphite_editor::messages::input_mapper::utility_types::input_keyboard::{Key, LabeledKeyOrMouseMotion, LabeledShortcut}; use graphite_editor::messages::input_mapper::utility_types::misc::ActionShortcut; use graphite_editor::messages::layout::LayoutMessage; - use graphite_editor::messages::tool::tool_messages::tool_prelude::{Layout, LayoutGroup, LayoutTarget, MenuListEntry, Widget, WidgetId}; + use graphite_editor::messages::tool::tool_messages::tool_prelude::{Layout, LayoutGroup, LayoutTarget, MenuListEntry, Widget, WidgetId, WidgetRow}; use crate::messages::{EditorMessage, KeyCode, MenuItem, Modifiers, Shortcut}; @@ -15,7 +15,7 @@ pub(crate) mod menu { [layout_group] => layout_group, _ => panic!("Menu bar layout is supposed to have exactly one layout group"), }; - let LayoutGroup::Row { widgets } = layout_group else { + let LayoutGroup::Row(WidgetRow { widgets }) = layout_group else { panic!("Menu bar layout group is supposed to be a row"); }; widgets @@ -88,8 +88,8 @@ pub(crate) mod menu { _ => None, }; - match icon.as_str() { - "CheckboxChecked" => { + match icon.as_deref() { + Some("CheckboxChecked") => { return MenuItem::Checkbox { id, text, @@ -98,7 +98,7 @@ pub(crate) mod menu { checked: true, }; } - "CheckboxUnchecked" => { + Some("CheckboxUnchecked") => { return MenuItem::Checkbox { id, text, diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 44725071..4719d543 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -29,12 +29,13 @@ log = { workspace = true } bitflags = { workspace = true } thiserror = { workspace = true } serde = { workspace = true } +serde_bytes = { workspace = true } serde_json = { workspace = true } kurbo = { workspace = true } futures = { workspace = true } glam = { workspace = true } derivative = { workspace = true } -specta = { workspace = true } +tsify = { workspace = true } dyn-any = { workspace = true } num_enum = { workspace = true } usvg = { workspace = true } diff --git a/editor/src/dispatcher.rs b/editor/src/dispatcher.rs index 40883223..6c312ed6 100644 --- a/editor/src/dispatcher.rs +++ b/editor/src/dispatcher.rs @@ -596,7 +596,7 @@ mod test { } = response { if let DiffUpdate::Layout(sub_layout) = &diff[0].new_value { - if let LayoutGroup::Row { widgets } = &sub_layout.0[0] { + if let LayoutGroup::Row(WidgetRow { widgets }) = &sub_layout.0[0] { if let Widget::TextLabel(TextLabel { value, .. }) = &*widgets[0].widget { print_problem_to_terminal_on_failure(value); } diff --git a/editor/src/generate_ts_types.rs b/editor/src/generate_ts_types.rs deleted file mode 100644 index 659b860f..00000000 --- a/editor/src/generate_ts_types.rs +++ /dev/null @@ -1,34 +0,0 @@ -/// Running this test will generate a `types.ts` file at the root of the repo, -/// containing every type annotated with `specta::Type` -// #[cfg(all(test, feature = "specta-export"))] -#[ignore] -#[test] -fn generate_ts_types() { - // TODO: Un-comment this out when we figure out how to reenable the "typescript` Specta feature flag - - // use crate::messages::prelude::FrontendMessage; - // use specta::ts::{export_named_datatype, BigIntExportBehavior, ExportConfig}; - // use specta::{NamedType, TypeMap}; - // use std::fs::File; - // use std::io::Write; - - // let config = ExportConfig::new().bigint(BigIntExportBehavior::Number); - - // let mut type_map = TypeMap::default(); - - // let datatype = FrontendMessage::definition_named_data_type(&mut type_map); - - // let mut export = String::new(); - - // export += &export_named_datatype(&config, &datatype, &type_map).unwrap(); - - // type_map - // .iter() - // .map(|(_, v)| v) - // .flat_map(|v| export_named_datatype(&config, v, &type_map)) - // .for_each(|e| export += &format!("\n\n{e}")); - - // let mut file = File::create("../types.ts").unwrap(); - - // write!(file, "{export}").ok(); -} diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 2c599451..df738234 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -3,7 +3,6 @@ extern crate graphite_proc_macros; // `macro_use` puts these macros into scope for all descendant code files #[macro_use] mod macros; -mod generate_ts_types; #[macro_use] extern crate log; diff --git a/editor/src/messages/app_window/app_window_message_handler.rs b/editor/src/messages/app_window/app_window_message_handler.rs index fd7b05f6..3c4d4ad6 100644 --- a/editor/src/messages/app_window/app_window_message_handler.rs +++ b/editor/src/messages/app_window/app_window_message_handler.rs @@ -11,37 +11,46 @@ impl MessageHandler for AppWindowMessageHandler { fn process_message(&mut self, message: AppWindowMessage, responses: &mut std::collections::VecDeque, _: ()) { match message { AppWindowMessage::PointerLock => { + #[cfg(not(target_family = "wasm"))] responses.add(FrontendMessage::WindowPointerLock); } AppWindowMessage::PointerLockMove { x, y } => { - responses.add(FrontendMessage::WindowPointerLockMove { x, y }); + responses.add(FrontendMessage::WindowPointerLockMove { position: (x, y) }); } AppWindowMessage::Close => { + #[cfg(not(target_family = "wasm"))] responses.add(FrontendMessage::WindowClose); } AppWindowMessage::Minimize => { + #[cfg(not(target_family = "wasm"))] responses.add(FrontendMessage::WindowMinimize); } AppWindowMessage::Maximize => { + #[cfg(not(target_family = "wasm"))] responses.add(FrontendMessage::WindowMaximize); } AppWindowMessage::Fullscreen => { responses.add(FrontendMessage::WindowFullscreen); } AppWindowMessage::Drag => { + #[cfg(not(target_family = "wasm"))] responses.add(FrontendMessage::WindowDrag); } AppWindowMessage::Hide => { + #[cfg(not(target_family = "wasm"))] responses.add(FrontendMessage::WindowHide); } AppWindowMessage::HideOthers => { + #[cfg(not(target_family = "wasm"))] responses.add(FrontendMessage::WindowHideOthers); } AppWindowMessage::ShowAll => { + #[cfg(not(target_family = "wasm"))] responses.add(FrontendMessage::WindowShowAll); } AppWindowMessage::Restart => { responses.add(PortfolioMessage::AutoSaveAllDocuments); + #[cfg(not(target_family = "wasm"))] responses.add(FrontendMessage::WindowRestart); } } @@ -57,7 +66,8 @@ impl MessageHandler for AppWindowMessageHandler { ); } -#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize)] pub enum AppWindowPlatform { #[default] Web, diff --git a/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs b/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs index 5eb151d1..a99962d2 100644 --- a/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs +++ b/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs @@ -85,7 +85,7 @@ impl DialogLayoutHolder for ExportDialogMessageHandler { TextButton::new("Cancel").on_update(|_| FrontendMessage::DialogClose.into()).widget_instance(), ]; - Layout(vec![LayoutGroup::Row { widgets }]) + Layout(vec![LayoutGroup::row(widgets)]) } } @@ -170,10 +170,10 @@ impl LayoutHolder for ExportDialogMessageHandler { ]; Layout(vec![ - LayoutGroup::Row { widgets: export_type }, - LayoutGroup::Row { widgets: resolution }, - LayoutGroup::Row { widgets: export_area }, - LayoutGroup::Row { widgets: transparent_background }, + LayoutGroup::row(export_type), + LayoutGroup::row(resolution), + LayoutGroup::row(export_area), + LayoutGroup::row(transparent_background), ]) } } diff --git a/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs b/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs index a1fd9058..e16a2120 100644 --- a/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs +++ b/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs @@ -71,7 +71,7 @@ impl DialogLayoutHolder for NewDocumentDialogMessageHandler { TextButton::new("Cancel").on_update(|_| FrontendMessage::DialogClose.into()).widget_instance(), ]; - Layout(vec![LayoutGroup::Row { widgets }]) + Layout(vec![LayoutGroup::row(widgets)]) } } @@ -122,6 +122,6 @@ impl LayoutHolder for NewDocumentDialogMessageHandler { .widget_instance(), ]; - Layout(vec![LayoutGroup::Row { widgets: name }, LayoutGroup::Row { widgets: infinite }, LayoutGroup::Row { widgets: scale }]) + Layout(vec![LayoutGroup::row(name), LayoutGroup::row(infinite), LayoutGroup::row(scale)]) } } diff --git a/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs b/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs index dd114bc7..e4d50a86 100644 --- a/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs +++ b/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs @@ -389,7 +389,7 @@ impl PreferencesDialogMessageHandler { } } - Layout(rows.into_iter().map(|r| LayoutGroup::Row { widgets: r }).collect()) + Layout(rows.into_iter().map(|r| LayoutGroup::row(r)).collect()) } pub fn send_layout(&self, responses: &mut VecDeque, layout_target: LayoutTarget, preferences: &PreferencesMessageHandler) { @@ -416,7 +416,7 @@ impl PreferencesDialogMessageHandler { TextButton::new("Reset to Defaults").on_update(|_| PreferencesMessage::ResetToDefaults.into()).widget_instance(), ]; - Layout(vec![LayoutGroup::Row { widgets }]) + Layout(vec![LayoutGroup::row(widgets)]) } fn send_layout_buttons(&self, responses: &mut VecDeque, layout_target: LayoutTarget) { diff --git a/editor/src/messages/dialog/simple_dialogs/about_graphite_dialog.rs b/editor/src/messages/dialog/simple_dialogs/about_graphite_dialog.rs index f9817dcf..8dae9ceb 100644 --- a/editor/src/messages/dialog/simple_dialogs/about_graphite_dialog.rs +++ b/editor/src/messages/dialog/simple_dialogs/about_graphite_dialog.rs @@ -15,7 +15,7 @@ impl DialogLayoutHolder for AboutGraphiteDialog { fn layout_buttons(&self) -> Layout { let widgets = vec![TextButton::new("OK").emphasized(true).on_update(|_| FrontendMessage::DialogClose.into()).widget_instance()]; - Layout(vec![LayoutGroup::Row { widgets }]) + Layout(vec![LayoutGroup::row(widgets)]) } fn layout_column_2(&self) -> Layout { @@ -29,7 +29,7 @@ impl DialogLayoutHolder for AboutGraphiteDialog { .into_iter() .map(|(icon, label, url)| { TextButton::new(label) - .icon(Some(icon.into())) + .icon(icon) .flush(true) .on_update(|_| FrontendMessage::TriggerVisitLink { url: url.into() }.into()) .widget_instance() @@ -40,7 +40,7 @@ impl DialogLayoutHolder for AboutGraphiteDialog { let localized_commit_year = self.localized_commit_year.clone(); widgets.push( TextButton::new("Licenses") - .icon(Some("License".into())) + .icon("License") .flush(true) .on_update(move |_| { DialogMessage::RequestLicensesDialogWithLocalizedCommitDate { @@ -51,22 +51,16 @@ impl DialogLayoutHolder for AboutGraphiteDialog { .widget_instance(), ); - Layout(vec![LayoutGroup::Column { widgets }]) + Layout(vec![LayoutGroup::column(widgets)]) } } impl LayoutHolder for AboutGraphiteDialog { fn layout(&self) -> Layout { Layout(vec![ - LayoutGroup::Row { - widgets: vec![TextLabel::new("About this release").bold(true).widget_instance()], - }, - LayoutGroup::Row { - widgets: vec![TextLabel::new(commit_info_localized(&self.localized_commit_date)).multiline(true).widget_instance()], - }, - LayoutGroup::Row { - widgets: vec![TextLabel::new(format!("Copyright © {} Graphite contributors", self.localized_commit_year)).widget_instance()], - }, + LayoutGroup::row(vec![TextLabel::new("About this release").bold(true).widget_instance()]), + LayoutGroup::row(vec![TextLabel::new(commit_info_localized(&self.localized_commit_date)).multiline(true).widget_instance()]), + LayoutGroup::row(vec![TextLabel::new(format!("Copyright © {} Graphite contributors", self.localized_commit_year)).widget_instance()]), ]) } } diff --git a/editor/src/messages/dialog/simple_dialogs/close_all_documents_dialog.rs b/editor/src/messages/dialog/simple_dialogs/close_all_documents_dialog.rs index b02cb5c3..262250d7 100644 --- a/editor/src/messages/dialog/simple_dialogs/close_all_documents_dialog.rs +++ b/editor/src/messages/dialog/simple_dialogs/close_all_documents_dialog.rs @@ -24,7 +24,7 @@ impl DialogLayoutHolder for CloseAllDocumentsDialog { TextButton::new("Cancel").on_update(|_| FrontendMessage::DialogClose.into()).widget_instance(), ]; - Layout(vec![LayoutGroup::Row { widgets }]) + Layout(vec![LayoutGroup::row(widgets)]) } } @@ -33,12 +33,8 @@ impl LayoutHolder for CloseAllDocumentsDialog { let unsaved_list = "• ".to_string() + &self.unsaved_document_names.join("\n• "); Layout(vec![ - LayoutGroup::Row { - widgets: vec![TextLabel::new("Save documents before closing them?").bold(true).multiline(true).widget_instance()], - }, - LayoutGroup::Row { - widgets: vec![TextLabel::new(format!("Documents with unsaved changes:\n{unsaved_list}")).multiline(true).widget_instance()], - }, + LayoutGroup::row(vec![TextLabel::new("Save documents before closing them?").bold(true).multiline(true).widget_instance()]), + LayoutGroup::row(vec![TextLabel::new(format!("Documents with unsaved changes:\n{unsaved_list}")).multiline(true).widget_instance()]), ]) } } diff --git a/editor/src/messages/dialog/simple_dialogs/close_document_dialog.rs b/editor/src/messages/dialog/simple_dialogs/close_document_dialog.rs index 9e9f56d8..6f8cf662 100644 --- a/editor/src/messages/dialog/simple_dialogs/close_document_dialog.rs +++ b/editor/src/messages/dialog/simple_dialogs/close_document_dialog.rs @@ -35,7 +35,7 @@ impl DialogLayoutHolder for CloseDocumentDialog { TextButton::new("Cancel").on_update(|_| FrontendMessage::DialogClose.into()).widget_instance(), ]; - Layout(vec![LayoutGroup::Row { widgets }]) + Layout(vec![LayoutGroup::row(widgets)]) } } @@ -52,12 +52,8 @@ impl LayoutHolder for CloseDocumentDialog { let break_lines = if self.document_name.len() > max_one_line_length { '\n' } else { ' ' }; Layout(vec![ - LayoutGroup::Row { - widgets: vec![TextLabel::new("Save document before closing it?").bold(true).widget_instance()], - }, - LayoutGroup::Row { - widgets: vec![TextLabel::new(format!("\"{name}{ellipsis}\"{break_lines}has unsaved changes")).multiline(true).widget_instance()], - }, + LayoutGroup::row(vec![TextLabel::new("Save document before closing it?").bold(true).widget_instance()]), + LayoutGroup::row(vec![TextLabel::new(format!("\"{name}{ellipsis}\"{break_lines}has unsaved changes")).multiline(true).widget_instance()]), ]) } } diff --git a/editor/src/messages/dialog/simple_dialogs/confirm_restart_dialog.rs b/editor/src/messages/dialog/simple_dialogs/confirm_restart_dialog.rs index 17856f9b..5cf55b48 100644 --- a/editor/src/messages/dialog/simple_dialogs/confirm_restart_dialog.rs +++ b/editor/src/messages/dialog/simple_dialogs/confirm_restart_dialog.rs @@ -24,7 +24,7 @@ impl DialogLayoutHolder for ConfirmRestartDialog { TextButton::new("Later").on_update(|_| FrontendMessage::DialogClose.into()).widget_instance(), ]; - Layout(vec![LayoutGroup::Row { widgets }]) + Layout(vec![LayoutGroup::row(widgets)]) } } @@ -33,27 +33,23 @@ impl LayoutHolder for ConfirmRestartDialog { let changed_settings = "• ".to_string() + &self.changed_settings.join("\n• "); Layout(vec![ - LayoutGroup::Row { - widgets: vec![TextLabel::new("Restart to apply changes?").bold(true).multiline(true).widget_instance()], - }, - LayoutGroup::Row { - widgets: vec![ - TextLabel::new( - format!( - " + LayoutGroup::row(vec![TextLabel::new("Restart to apply changes?").bold(true).multiline(true).widget_instance()]), + LayoutGroup::row(vec![ + TextLabel::new( + format!( + " Settings that only take effect on next launch:\n\ {changed_settings}\n\ \n\ This only takes a few seconds. Open documents,\n\ even unsaved ones, will be automatically restored. " - ) - .trim(), ) - .multiline(true) - .widget_instance(), - ], - }, + .trim(), + ) + .multiline(true) + .widget_instance(), + ]), ]) } } diff --git a/editor/src/messages/dialog/simple_dialogs/demo_artwork_dialog.rs b/editor/src/messages/dialog/simple_dialogs/demo_artwork_dialog.rs index 7b261b53..7b702c5f 100644 --- a/editor/src/messages/dialog/simple_dialogs/demo_artwork_dialog.rs +++ b/editor/src/messages/dialog/simple_dialogs/demo_artwork_dialog.rs @@ -22,7 +22,7 @@ impl DialogLayoutHolder for DemoArtworkDialog { fn layout_buttons(&self) -> Layout { let widgets = vec![TextButton::new("Close").emphasized(true).on_update(|_| FrontendMessage::DialogClose.into()).widget_instance()]; - Layout(vec![LayoutGroup::Row { widgets }]) + Layout(vec![LayoutGroup::row(widgets)]) } } @@ -46,7 +46,7 @@ impl LayoutHolder for DemoArtworkDialog { let images = chunk .iter() - .map(|(name, thumbnail, filename)| ImageButton::new(*thumbnail).width(Some("256px".into())).on_update(|_| make_dialog(name, filename)).widget_instance()) + .map(|(name, thumbnail, filename)| ImageButton::new(*thumbnail).width("256px").on_update(|_| make_dialog(name, filename)).widget_instance()) .collect(); let buttons = chunk @@ -54,7 +54,7 @@ impl LayoutHolder for DemoArtworkDialog { .map(|(name, _, filename)| TextButton::new(*name).min_width(256).flush(true).on_update(|_| make_dialog(name, filename)).widget_instance()) .collect(); - vec![LayoutGroup::Row { widgets: images }, LayoutGroup::Row { widgets: buttons }, LayoutGroup::Row { widgets: vec![] }] + vec![LayoutGroup::row(images), LayoutGroup::row(buttons), LayoutGroup::row(vec![])] }) .collect(); let _ = rows_of_images_with_buttons.pop(); diff --git a/editor/src/messages/dialog/simple_dialogs/error_dialog.rs b/editor/src/messages/dialog/simple_dialogs/error_dialog.rs index a47c676a..e92541a2 100644 --- a/editor/src/messages/dialog/simple_dialogs/error_dialog.rs +++ b/editor/src/messages/dialog/simple_dialogs/error_dialog.rs @@ -14,19 +14,15 @@ impl DialogLayoutHolder for ErrorDialog { fn layout_buttons(&self) -> Layout { let widgets = vec![TextButton::new("OK").emphasized(true).on_update(|_| FrontendMessage::DialogClose.into()).widget_instance()]; - Layout(vec![LayoutGroup::Row { widgets }]) + Layout(vec![LayoutGroup::row(widgets)]) } } impl LayoutHolder for ErrorDialog { fn layout(&self) -> Layout { Layout(vec![ - LayoutGroup::Row { - widgets: vec![TextLabel::new(&self.title).bold(true).widget_instance()], - }, - LayoutGroup::Row { - widgets: vec![TextLabel::new(&self.description).multiline(true).widget_instance()], - }, + LayoutGroup::row(vec![TextLabel::new(&self.title).bold(true).widget_instance()]), + LayoutGroup::row(vec![TextLabel::new(&self.description).multiline(true).widget_instance()]), ]) } } diff --git a/editor/src/messages/dialog/simple_dialogs/licenses_dialog.rs b/editor/src/messages/dialog/simple_dialogs/licenses_dialog.rs index ed748932..3bdf5fc6 100644 --- a/editor/src/messages/dialog/simple_dialogs/licenses_dialog.rs +++ b/editor/src/messages/dialog/simple_dialogs/licenses_dialog.rs @@ -12,7 +12,7 @@ impl DialogLayoutHolder for LicensesDialog { fn layout_buttons(&self) -> Layout { let widgets = vec![TextButton::new("OK").emphasized(true).on_update(|_| FrontendMessage::DialogClose.into()).widget_instance()]; - Layout(vec![LayoutGroup::Row { widgets }]) + Layout(vec![LayoutGroup::row(widgets)]) } fn layout_column_2(&self) -> Layout { @@ -34,10 +34,10 @@ impl DialogLayoutHolder for LicensesDialog { ]; let widgets = button_definitions .iter() - .map(|&(icon, label, message_factory)| TextButton::new(label).icon(Some((icon).into())).flush(true).on_update(move |_| message_factory()).widget_instance()) + .map(|&(icon, label, message_factory)| TextButton::new(label).icon(icon).flush(true).on_update(move |_| message_factory()).widget_instance()) .collect(); - Layout(vec![LayoutGroup::Column { widgets }]) + Layout(vec![LayoutGroup::column(widgets)]) } } @@ -56,12 +56,8 @@ impl LayoutHolder for LicensesDialog { let description = description.trim(); Layout(vec![ - LayoutGroup::Row { - widgets: vec![TextLabel::new("Graphite is free, open source software").bold(true).widget_instance()], - }, - LayoutGroup::Row { - widgets: vec![TextLabel::new(description).multiline(true).widget_instance()], - }, + LayoutGroup::row(vec![TextLabel::new("Graphite is free, open source software").bold(true).widget_instance()]), + LayoutGroup::row(vec![TextLabel::new(description).multiline(true).widget_instance()]), ]) } } diff --git a/editor/src/messages/dialog/simple_dialogs/licenses_third_party_dialog.rs b/editor/src/messages/dialog/simple_dialogs/licenses_third_party_dialog.rs index 890803bb..842e1857 100644 --- a/editor/src/messages/dialog/simple_dialogs/licenses_third_party_dialog.rs +++ b/editor/src/messages/dialog/simple_dialogs/licenses_third_party_dialog.rs @@ -12,7 +12,7 @@ impl DialogLayoutHolder for LicensesThirdPartyDialog { fn layout_buttons(&self) -> Layout { let widgets = vec![TextButton::new("OK").emphasized(true).on_update(|_| FrontendMessage::DialogClose.into()).widget_instance()]; - Layout(vec![LayoutGroup::Row { widgets }]) + Layout(vec![LayoutGroup::row(widgets)]) } } @@ -31,14 +31,12 @@ impl LayoutHolder for LicensesThirdPartyDialog { // Two characters (one before, one after) the sequence of underscore characters, plus one additional column to provide a space between the text and the scrollbar let non_wrapping_column_width = license_text.split('\n').map(|line| line.chars().filter(|&c| c == '_').count() as u32).max().unwrap_or(0) + 2 + 1; - Layout(vec![LayoutGroup::Row { - widgets: vec![ - TextLabel::new(license_text) - .monospace(true) - .multiline(true) - .min_width_characters(non_wrapping_column_width) - .widget_instance(), - ], - }]) + Layout(vec![LayoutGroup::row(vec![ + TextLabel::new(license_text) + .monospace(true) + .multiline(true) + .min_width_characters(non_wrapping_column_width) + .widget_instance(), + ])]) } } diff --git a/editor/src/messages/frontend/frontend_message.rs b/editor/src/messages/frontend/frontend_message.rs index 5126b9cc..ba5484a6 100644 --- a/editor/src/messages/frontend/frontend_message.rs +++ b/editor/src/messages/frontend/frontend_message.rs @@ -1,15 +1,16 @@ +use super::IconName; use super::utility_types::{DocumentDetails, MouseCursorIcon, OpenDocument}; use crate::messages::app_window::app_window_message_handler::AppWindowPlatform; use crate::messages::frontend::utility_types::EyedropperPreviewImage; use crate::messages::input_mapper::utility_types::misc::ActionShortcut; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::node_graph::utility_types::{ - BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, NodeGraphErrorDiagnostic, Transform, + BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, NodeGraphErrorDiagnostic, }; use crate::messages::portfolio::document::utility_types::nodes::{LayerPanelEntry, LayerStructureEntry}; use crate::messages::portfolio::document::utility_types::wires::{WirePath, WirePathUpdate}; use crate::messages::prelude::*; -use glam::IVec2; +use crate::messages::tool::tool_messages::eyedropper_tool::PrimarySecondary; use graph_craft::document::NodeId; use graphene_std::raster::Image; use graphene_std::raster::color::Color; @@ -20,13 +21,14 @@ use std::path::PathBuf; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; #[impl_message(Message, Frontend)] -#[derive(derivative::Derivative, Clone, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(derivative::Derivative, Clone, serde::Serialize, serde::Deserialize)] #[derivative(Debug, PartialEq)] pub enum FrontendMessage { // Display prefix: make the frontend show something, like a dialog DisplayDialog { title: String, - icon: String, + icon: IconName, }, DialogClose, DisplayDialogPanic { @@ -41,7 +43,7 @@ pub enum FrontendMessage { font_size: f64, color: String, #[serde(rename = "fontData")] - font_data: Vec, + font_data: serde_bytes::ByteBuf, transform: [f64; 6], #[serde(rename = "maxWidth")] max_width: Option, @@ -51,7 +53,7 @@ pub enum FrontendMessage { }, DisplayEditableTextboxUpdateFontData { #[serde(rename = "fontData")] - font_data: Vec, + font_data: serde_bytes::ByteBuf, }, DisplayEditableTextboxTransform { transform: [f64; 6], @@ -87,11 +89,11 @@ pub enum FrontendMessage { document_id: DocumentId, name: String, path: Option, - content: Vec, + content: serde_bytes::ByteBuf, }, TriggerSaveFile { name: String, - content: Vec, + content: serde_bytes::ByteBuf, }, TriggerExportImage { svg: String, @@ -125,6 +127,7 @@ pub enum FrontendMessage { TriggerOpen, TriggerImport, TriggerSavePreferences { + #[tsify(type = "unknown")] preferences: PreferencesMessageHandler, }, TriggerSaveActiveDocument { @@ -152,9 +155,8 @@ pub enum FrontendMessage { document_id: DocumentId, }, UpdateGradientStopColorPickerPosition { - color: Color, - x: f64, - y: f64, + color: Color, // TODO: Color (without `none`) -> Color (with `none`) + position: (f64, f64), }, UpdateImportsExports { /// If the primary import is not visible, then it is None. @@ -163,10 +165,10 @@ pub enum FrontendMessage { exports: Vec>, /// The primary import location. #[serde(rename = "importPosition")] - import_position: IVec2, + import_position: (i32, i32), /// The primary export location. #[serde(rename = "exportPosition")] - export_position: IVec2, + export_position: (i32, i32), /// The document network does not have an add import or export button. #[serde(rename = "addImportExport")] add_import_export: bool, @@ -202,7 +204,7 @@ pub enum FrontendMessage { UpdateLayout { #[serde(rename = "layoutTarget")] layout_target: LayoutTarget, - diff: Vec, + diff: Vec, // TODO: Align this with what's generated }, UpdateImportReorderIndex { #[serde(rename = "importIndex")] @@ -223,6 +225,8 @@ pub enum FrontendMessage { UpdateDocumentArtwork { svg: String, }, + // This message is intercepted before being sent to the frontend + #[serde(skip)] UpdateImageData { image_data: Vec<(u64, Image)>, }, @@ -253,7 +257,7 @@ pub enum FrontendMessage { #[serde(rename = "secondaryColor")] secondary_color: String, #[serde(rename = "setColorChoice")] - set_color_choice: Option, + set_color_choice: Option, }, UpdateGraphFadeArtwork { percentage: f64, @@ -278,7 +282,8 @@ pub enum FrontendMessage { selected: Vec, }, UpdateNodeGraphTransform { - transform: Transform, + translation: (f64, f64), + scale: f64, }, UpdateNodeThumbnail { id: NodeId, @@ -304,6 +309,7 @@ pub enum FrontendMessage { UpdateViewportHolePunch { active: bool, }, + #[cfg(not(target_family = "wasm"))] UpdateViewportPhysicalBounds { x: f64, y: f64, @@ -322,18 +328,26 @@ pub enum FrontendMessage { }, // Window prefix: cause the application window to do something + #[cfg(not(target_family = "wasm"))] WindowPointerLock, WindowPointerLockMove { - x: f64, - y: f64, + position: (f64, f64), }, + #[cfg(not(target_family = "wasm"))] WindowClose, + #[cfg(not(target_family = "wasm"))] WindowMinimize, + #[cfg(not(target_family = "wasm"))] WindowMaximize, WindowFullscreen, + #[cfg(not(target_family = "wasm"))] WindowDrag, + #[cfg(not(target_family = "wasm"))] WindowHide, + #[cfg(not(target_family = "wasm"))] WindowHideOthers, + #[cfg(not(target_family = "wasm"))] WindowShowAll, + #[cfg(not(target_family = "wasm"))] WindowRestart, } diff --git a/editor/src/messages/frontend/mod.rs b/editor/src/messages/frontend/mod.rs index 81d963f5..8ab9dfb3 100644 --- a/editor/src/messages/frontend/mod.rs +++ b/editor/src/messages/frontend/mod.rs @@ -4,3 +4,7 @@ pub mod utility_types; #[doc(inline)] pub use frontend_message::{FrontendMessage, FrontendMessageDiscriminant}; + +// TODO: Make this an enum with the actual icon names, somehow derived from or tied to the frontend icon set. +// TODO: Then remove `#[widget_builder(string)]` from all icon fields. +pub type IconName = String; diff --git a/editor/src/messages/frontend/utility_types.rs b/editor/src/messages/frontend/utility_types.rs index 31d2866f..979a3d02 100644 --- a/editor/src/messages/frontend/utility_types.rs +++ b/editor/src/messages/frontend/utility_types.rs @@ -3,13 +3,15 @@ use std::path::PathBuf; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::prelude::*; -#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize)] pub struct OpenDocument { pub id: DocumentId, pub details: DocumentDetails, } -#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize)] pub struct DocumentDetails { pub name: String, pub path: Option, @@ -19,7 +21,8 @@ pub struct DocumentDetails { pub is_auto_saved: bool, } -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize)] pub enum MouseCursorIcon { #[default] Default, @@ -37,7 +40,8 @@ pub enum MouseCursorIcon { Rotate, } -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize)] pub enum FileType { #[default] Png, @@ -55,7 +59,8 @@ impl FileType { } } -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize)] pub enum ExportBounds { #[default] AllArtwork, @@ -63,9 +68,10 @@ pub enum ExportBounds { Artboard(LayerNodeIdentifier), } -#[derive(Clone, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify), tsify(large_number_types_as_bigints))] +#[derive(Clone, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize)] pub struct EyedropperPreviewImage { - pub data: Vec, + pub data: serde_bytes::ByteBuf, pub width: u32, pub height: u32, } diff --git a/editor/src/messages/input_mapper/utility_types/input_keyboard.rs b/editor/src/messages/input_mapper/utility_types/input_keyboard.rs index e8c9bc53..b6a2424c 100644 --- a/editor/src/messages/input_mapper/utility_types/input_keyboard.rs +++ b/editor/src/messages/input_mapper/utility_types/input_keyboard.rs @@ -67,7 +67,8 @@ bitflags! { // (although we ignore the shift key, so the user doesn't have to press `Ctrl Shift +` on a US keyboard), even if the keyboard layout // is for a different locale where the `+` key is somewhere entirely different, shifted or not. This would then also work for numpad `+`. #[impl_message(Message, InputMapperMessage, KeyDown)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, specta::Type, num_enum::TryFromPrimitive)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, num_enum::TryFromPrimitive)] #[repr(u8)] pub enum Key { // Writing system keys @@ -379,7 +380,8 @@ impl fmt::Display for KeysGroup { // LabeledKey // ========== -#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] pub struct LabeledKey { key: Key, label: String, @@ -395,7 +397,8 @@ impl LabeledKey { // MouseMotion // =========== -#[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 MouseMotion { None, Lmb, @@ -415,7 +418,8 @@ pub enum MouseMotion { // LabeledKeyOrMouseMotion // ======================= -#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] #[serde(untagged)] pub enum LabeledKeyOrMouseMotion { Key(LabeledKey), @@ -437,7 +441,8 @@ impl From for LabeledKeyOrMouseMotion { // LabeledShortcut // =============== -#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct LabeledShortcut(pub Vec); impl From for LabeledShortcut { diff --git a/editor/src/messages/input_mapper/utility_types/input_mouse.rs b/editor/src/messages/input_mapper/utility_types/input_mouse.rs index 6fba5027..420a3bdd 100644 --- a/editor/src/messages/input_mapper/utility_types/input_mouse.rs +++ b/editor/src/messages/input_mapper/utility_types/input_mouse.rs @@ -110,7 +110,8 @@ bitflags! { } #[impl_message(Message, InputMapperMessage, DoubleClick)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, specta::Type, num_enum::TryFromPrimitive)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, num_enum::TryFromPrimitive)] #[repr(u8)] pub enum MouseButton { Left, diff --git a/editor/src/messages/input_mapper/utility_types/misc.rs b/editor/src/messages/input_mapper/utility_types/misc.rs index 2470e7a7..ea0bfc00 100644 --- a/editor/src/messages/input_mapper/utility_types/misc.rs +++ b/editor/src/messages/input_mapper/utility_types/misc.rs @@ -106,8 +106,10 @@ pub struct MappingEntry { pub disabled: bool, } -#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] pub enum ActionShortcut { + #[serde(skip)] Action(MessageDiscriminant), #[serde(rename = "shortcut")] Shortcut(LabeledShortcut), diff --git a/editor/src/messages/layout/layout_message_handler.rs b/editor/src/messages/layout/layout_message_handler.rs index de3d1ab3..73dfa2ae 100644 --- a/editor/src/messages/layout/layout_message_handler.rs +++ b/editor/src/messages/layout/layout_message_handler.rs @@ -1,8 +1,7 @@ use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::prelude::*; -use graphene_std::raster::color::Color; -use graphene_std::vector::style::{FillChoice, GradientStop, GradientStops}; +use graphene_std::vector::style::FillChoice; use serde_json::Value; use std::collections::HashMap; @@ -63,7 +62,7 @@ impl LayoutMessageHandler { while let Some((mut widget_path, layout_group)) = stack.pop() { match layout_group { // Check if any of the widgets in the current column or row have the correct id - LayoutGroup::Column { widgets } | LayoutGroup::Row { widgets } => { + LayoutGroup::Column(WidgetColumn { widgets }) | LayoutGroup::Row(WidgetRow { widgets }) => { for (index, widget) in widgets.iter().enumerate() { // Return if this is the correct ID if widget.widget_id == widget_id { @@ -84,10 +83,10 @@ impl LayoutMessageHandler { } } // A section contains more LayoutGroups which we add to the stack. - LayoutGroup::Section { layout, .. } => { + LayoutGroup::Section(WidgetSection { layout, .. }) => { stack.extend(layout.0.iter().enumerate().map(|(index, val)| ([widget_path.as_slice(), &[index]].concat(), val))); } - LayoutGroup::Table { rows, .. } => { + LayoutGroup::Table(WidgetTable { rows, .. }) => { for (row_index, row) in rows.iter().enumerate() { for (cell_index, cell) in row.iter().enumerate() { // Return if this is the correct ID @@ -158,60 +157,12 @@ impl LayoutMessageHandler { let callback_message = match action { WidgetValueAction::Commit => (color_button.on_commit.callback)(&()), WidgetValueAction::Update => { - // Decodes the colors in gamma, not linear - let decode_color = |color: &serde_json::map::Map| -> Option { - let red = color.get("red").and_then(|x| x.as_f64()).map(|x| x as f32); - let green = color.get("green").and_then(|x| x.as_f64()).map(|x| x as f32); - let blue = color.get("blue").and_then(|x| x.as_f64()).map(|x| x as f32); - let alpha = color.get("alpha").and_then(|x| x.as_f64()).map(|x| x as f32); - - if let (Some(red), Some(green), Some(blue), Some(alpha)) = (red, green, blue, alpha) - && let Some(color) = Color::from_rgbaf32(red, green, blue, alpha) - { - return Some(color); - } - None + let Ok(fill_choice) = serde_json::from_value::(value) else { + warn!("ColorInput update was not able to be parsed as FillChoice: {color_button:?}"); + return; }; - - (|| { - let Some(update_value) = value.as_object() else { - warn!("ColorInput update was not of type: object"); - return Message::NoOp; - }; - - // None - let is_none = update_value.get("none").and_then(|x| x.as_bool()); - if is_none == Some(true) { - color_button.value = FillChoice::None; - return (color_button.on_update.callback)(color_button); - } - - // Solid - if let Some(color) = decode_color(update_value) { - color_button.value = FillChoice::Solid(color); - return (color_button.on_update.callback)(color_button); - } - - // Gradient - let positions = update_value.get("position").and_then(|x| x.as_array()); - let midpoints = update_value.get("midpoint").and_then(|x| x.as_array()); - let colors = update_value.get("color").and_then(|x| x.as_array()); - - if let (Some(positions), Some(midpoints), Some(colors)) = (positions, midpoints, colors) { - let gradient_stops = positions.iter().zip(midpoints.iter()).zip(colors.iter()).filter_map(|((pos, mid), col)| { - let position = pos.as_f64()?; - let midpoint = mid.as_f64()?; - let color = col.as_object().and_then(decode_color)?; - Some(GradientStop { position, midpoint, color }) - }); - - color_button.value = FillChoice::Gradient(GradientStops::new(gradient_stops)); - return (color_button.on_update.callback)(color_button); - } - - warn!("ColorInput update was not able to be parsed with color data: {color_button:?}"); - Message::NoOp - })() + color_button.value = fill_choice; + (color_button.on_update.callback)(color_button) } }; diff --git a/editor/src/messages/layout/utility_types/layout_widget.rs b/editor/src/messages/layout/utility_types/layout_widget.rs index fef1061f..ac136311 100644 --- a/editor/src/messages/layout/utility_types/layout_widget.rs +++ b/editor/src/messages/layout/utility_types/layout_widget.rs @@ -10,7 +10,8 @@ use std::hash::{Hash, Hasher}; use std::sync::Arc; #[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 WidgetId(pub u64); impl core::fmt::Display for WidgetId { @@ -19,7 +20,8 @@ impl core::fmt::Display for WidgetId { } } -#[derive(PartialEq, Clone, Debug, Hash, Eq, Copy, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(PartialEq, Clone, Debug, Hash, Eq, Copy, serde::Serialize, serde::Deserialize)] #[repr(u8)] pub enum LayoutTarget { /// The spreadsheet panel allows for the visualisation of data in the graph. @@ -59,6 +61,7 @@ pub enum LayoutTarget { // KEEP THIS ENUM LAST // This is a marker that is used to define an array that is used to hold widgets + #[serde(skip)] _LayoutTargetLength, } @@ -151,7 +154,8 @@ fn compute_checkbox_id(layout_target: LayoutTarget, widget_path: &[usize], widge } /// Contains an arrangement of widgets mounted somewhere specific in the frontend. -#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize, PartialEq, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize, PartialEq)] pub struct Layout(pub Vec); impl Layout { @@ -241,19 +245,19 @@ impl<'a> Iterator for WidgetIter<'a> { } match self.stack.pop() { - Some(LayoutGroup::Column { widgets }) => { + Some(LayoutGroup::Column(WidgetColumn { widgets })) => { self.current_slice = Some(widgets); self.next() } - Some(LayoutGroup::Row { widgets }) => { + Some(LayoutGroup::Row(WidgetRow { widgets })) => { self.current_slice = Some(widgets); self.next() } - Some(LayoutGroup::Table { rows, .. }) => { + Some(LayoutGroup::Table(WidgetTable { rows, .. })) => { self.table.extend(rows.iter().flatten().rev()); self.next() } - Some(LayoutGroup::Section { layout, .. }) => { + Some(LayoutGroup::Section(WidgetSection { layout, .. })) => { for layout_row in &layout.0 { self.stack.push(layout_row); } @@ -293,19 +297,19 @@ impl<'a> Iterator for WidgetIterMut<'a> { } match self.stack.pop() { - Some(LayoutGroup::Column { widgets }) => { + Some(LayoutGroup::Column(WidgetColumn { widgets })) => { self.current_slice = Some(widgets); self.next() } - Some(LayoutGroup::Row { widgets }) => { + Some(LayoutGroup::Row(WidgetRow { widgets })) => { self.current_slice = Some(widgets); self.next() } - Some(LayoutGroup::Table { rows, .. }) => { + Some(LayoutGroup::Table(WidgetTable { rows, .. })) => { self.table.extend(rows.iter_mut().flatten().rev()); self.next() } - Some(LayoutGroup::Section { layout, .. }) => { + Some(LayoutGroup::Section(WidgetSection { layout, .. })) => { for layout_row in &mut layout.0 { self.stack.push(layout_row); } @@ -316,52 +320,88 @@ impl<'a> Iterator for WidgetIterMut<'a> { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub enum LayoutGroup { - #[serde(rename = "column")] - Column { - #[serde(rename = "columnWidgets")] - widgets: Vec, - }, - #[serde(rename = "row")] - Row { - #[serde(rename = "rowWidgets")] - widgets: Vec, - }, - #[serde(rename = "table")] - Table { - #[serde(rename = "tableWidgets")] - rows: Vec>, - unstyled: bool, - }, - #[serde(rename = "section")] - Section { - name: String, - description: String, - visible: bool, - pinned: bool, - id: u64, - layout: Layout, - }, + Column(WidgetColumn), + Row(WidgetRow), + Table(WidgetTable), + Section(WidgetSection), +} + +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct WidgetColumn { + #[serde(rename = "columnWidgets")] + pub widgets: Vec, +} + +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct WidgetRow { + #[serde(rename = "rowWidgets")] + pub widgets: Vec, +} + +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct WidgetTable { + #[serde(rename = "tableWidgets")] + pub rows: Vec>, + pub unstyled: bool, +} + +#[cfg_attr(feature = "wasm", derive(tsify::Tsify), tsify(large_number_types_as_bigints))] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct WidgetSection { + pub name: String, + pub description: String, + pub visible: bool, + pub pinned: bool, + pub id: u64, + pub layout: Layout, } impl Default for LayoutGroup { fn default() -> Self { - Self::Row { widgets: Vec::new() } + Self::Row(Default::default()) } } impl From> for LayoutGroup { fn from(widgets: Vec) -> LayoutGroup { - LayoutGroup::Row { widgets } + LayoutGroup::Row(WidgetRow { widgets }) } } impl LayoutGroup { + pub fn row(widgets: Vec) -> Self { + Self::Row(WidgetRow { widgets }) + } + + pub fn column(widgets: Vec) -> Self { + Self::Column(WidgetColumn { widgets }) + } + + pub fn table(rows: Vec>, unstyled: bool) -> Self { + Self::Table(WidgetTable { rows, unstyled }) + } + + pub fn section(name: impl Into, description: impl Into, visible: bool, pinned: bool, id: u64, layout: Layout) -> Self { + Self::Section(WidgetSection { + name: name.into(), + description: description.into(), + visible, + pinned, + id, + layout, + }) + } + /// Applies a tooltip description to all widgets without a tooltip in this row or column. pub fn with_tooltip_description(self, description: impl Into) -> Self { let (is_col, mut widgets) = match self { - LayoutGroup::Column { widgets } => (true, widgets), - LayoutGroup::Row { widgets } => (false, widgets), + LayoutGroup::Column(WidgetColumn { widgets }) => (true, widgets), + LayoutGroup::Row(WidgetRow { widgets }) => (false, widgets), _ => unimplemented!(), }; let description = description.into(); @@ -394,7 +434,7 @@ impl LayoutGroup { val.clone_from(&description); } } - if is_col { Self::Column { widgets } } else { Self::Row { widgets } } + if is_col { Self::Column(WidgetColumn { widgets }) } else { Self::Row(WidgetRow { widgets }) } } pub fn iter_mut(&mut self) -> WidgetIterMut<'_> { @@ -413,7 +453,8 @@ impl Diffable for LayoutGroup { fn diff(&mut self, new: Self, widget_path: &mut Vec, widget_diffs: &mut Vec) { let is_column = matches!(new, Self::Column { .. }); match (self, new) { - (Self::Column { widgets: current_widgets }, Self::Column { widgets: new_widgets }) | (Self::Row { widgets: current_widgets }, Self::Row { widgets: new_widgets }) => { + (Self::Column(WidgetColumn { widgets: current_widgets }), Self::Column(WidgetColumn { widgets: new_widgets })) + | (Self::Row(WidgetRow { widgets: current_widgets }), Self::Row(WidgetRow { widgets: new_widgets })) => { // If the lengths are different then resend the entire panel // TODO: Diff insersion and deletion of items if current_widgets.len() != new_widgets.len() { @@ -421,7 +462,12 @@ impl Diffable for LayoutGroup { current_widgets.clone_from(&new_widgets); // Push back a LayoutGroup update to the diff - let new_value = (if is_column { Self::Column { widgets: new_widgets } } else { Self::Row { widgets: new_widgets } }).into_diff_update(); + let new_value = (if is_column { + Self::Column(WidgetColumn { widgets: new_widgets }) + } else { + Self::Row(WidgetRow { widgets: new_widgets }) + }) + .into_diff_update(); let widget_path = widget_path.to_vec(); widget_diffs.push(WidgetDiff { widget_path, new_value }); return; @@ -434,22 +480,22 @@ impl Diffable for LayoutGroup { } } ( - Self::Section { + Self::Section(WidgetSection { name: current_name, description: current_description, visible: current_visible, pinned: current_pinned, id: current_id, layout: current_layout, - }, - Self::Section { + }), + Self::Section(WidgetSection { name: new_name, description: new_description, visible: new_visible, pinned: new_pinned, id: new_id, layout: new_layout, - }, + }), ) => { // Resend the entire panel if the lengths, names, visibility, or node IDs are different // TODO: Diff insersion and deletion of items @@ -469,14 +515,14 @@ impl Diffable for LayoutGroup { current_layout.clone_from(&new_layout); // Push an update layout group to the diff - let new_value = Self::Section { + let new_value = Self::Section(WidgetSection { name: new_name, description: new_description, visible: new_visible, pinned: new_pinned, id: new_id, layout: new_layout, - } + }) .into_diff_update(); let widget_path = widget_path.to_vec(); widget_diffs.push(WidgetDiff { widget_path, new_value }); @@ -501,14 +547,14 @@ impl Diffable for LayoutGroup { fn collect_checkbox_ids(&self, layout_target: LayoutTarget, widget_path: &mut Vec, checkbox_map: &mut HashMap) { match self { - Self::Column { widgets } | Self::Row { widgets } => { + Self::Column(WidgetColumn { widgets }) | Self::Row(WidgetRow { widgets }) => { for (index, widget) in widgets.iter().enumerate() { widget_path.push(index); widget.collect_checkbox_ids(layout_target, widget_path, checkbox_map); widget_path.pop(); } } - Self::Table { rows, .. } => { + Self::Table(WidgetTable { rows, .. }) => { for (row_idx, row) in rows.iter().enumerate() { for (col_idx, widget) in row.iter().enumerate() { widget_path.push(row_idx); @@ -519,7 +565,7 @@ impl Diffable for LayoutGroup { } } } - Self::Section { layout, .. } => { + Self::Section(WidgetSection { layout, .. }) => { layout.collect_checkbox_ids(layout_target, widget_path, checkbox_map); } } @@ -527,14 +573,14 @@ impl Diffable for LayoutGroup { fn replace_widget_ids(&mut self, layout_target: LayoutTarget, widget_path: &mut Vec, checkbox_map: &HashMap) { match self { - Self::Column { widgets } | Self::Row { widgets } => { + Self::Column(WidgetColumn { widgets }) | Self::Row(WidgetRow { widgets }) => { for (index, widget) in widgets.iter_mut().enumerate() { widget_path.push(index); widget.replace_widget_ids(layout_target, widget_path, checkbox_map); widget_path.pop(); } } - Self::Table { rows, .. } => { + Self::Table(WidgetTable { rows, .. }) => { for (row_idx, row) in rows.iter_mut().enumerate() { for (col_idx, widget) in row.iter_mut().enumerate() { widget_path.push(row_idx); @@ -545,14 +591,15 @@ impl Diffable for LayoutGroup { } } } - Self::Section { layout, .. } => { + Self::Section(WidgetSection { layout, .. }) => { layout.replace_widget_ids(layout_target, widget_path, checkbox_map); } } } } -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct WidgetInstance { #[serde(rename = "widgetId")] pub widget_id: WidgetId, @@ -674,9 +721,8 @@ impl Diffable for WidgetInstance { } } -#[derive(Clone, specta::Type)] +#[derive(Clone)] pub struct WidgetCallback { - #[specta(skip)] pub callback: Arc Message + 'static + Send + Sync>, } @@ -692,7 +738,8 @@ impl Default for WidgetCallback { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub enum Widget { BreadcrumbTrailButtons(BreadcrumbTrailButtons), CheckboxInput(CheckboxInput), @@ -719,7 +766,8 @@ pub enum Widget { } /// A single change to part of the UI, containing the location of the change and the new value. -#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify), tsify(large_number_types_as_bigints))] +#[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. @@ -732,7 +780,8 @@ pub struct WidgetDiff { } /// The new value of the UI, sent as part of a diff. -#[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 DiffUpdate { #[serde(rename = "layout")] Layout(Layout), diff --git a/editor/src/messages/layout/utility_types/widgets/button_widgets.rs b/editor/src/messages/layout/utility_types/widgets/button_widgets.rs index 54a1e5ba..cd5f9840 100644 --- a/editor/src/messages/layout/utility_types/widgets/button_widgets.rs +++ b/editor/src/messages/layout/utility_types/widgets/button_widgets.rs @@ -1,3 +1,4 @@ +use crate::messages::frontend::IconName; use crate::messages::input_mapper::utility_types::misc::ActionShortcut; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType; @@ -6,12 +7,14 @@ use derivative::*; use graphene_std::vector::style::FillChoice; use graphite_proc_macros::WidgetBuilder; -#[derive(Clone, Default, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Clone, Default, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder)] #[derivative(Debug, PartialEq)] pub struct IconButton { // Content #[widget_builder(constructor)] - pub icon: String, + #[widget_builder(string)] + pub icon: IconName, #[serde(rename = "hoverIcon")] pub hover_icon: Option, #[widget_builder(constructor)] @@ -38,12 +41,14 @@ pub struct IconButton { pub on_commit: WidgetCallback<()>, } -#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder)] #[derivative(Debug, PartialEq, Default)] pub struct PopoverButton { // Content pub style: Option, - pub icon: Option, + #[widget_builder(string)] + pub icon: Option, pub disabled: bool, // Children @@ -63,7 +68,8 @@ pub struct PopoverButton { pub tooltip_shortcut: Option, } -#[derive(Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)] pub enum MenuDirection { Top, #[default] @@ -77,7 +83,8 @@ pub enum MenuDirection { Center, } -#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder)] #[derivative(Debug, PartialEq)] pub struct ParameterExposeButton { // Content @@ -102,13 +109,15 @@ pub struct ParameterExposeButton { pub on_commit: WidgetCallback<()>, } -#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder)] #[derivative(Debug, PartialEq)] pub struct TextButton { // Content #[widget_builder(constructor)] pub label: String, - pub icon: Option, + #[widget_builder(string)] + pub icon: Option, #[serde(rename = "hoverIcon")] pub hover_icon: Option, pub disabled: bool, @@ -146,7 +155,8 @@ pub struct TextButton { pub on_commit: WidgetCallback<()>, } -#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder)] #[derivative(Debug, PartialEq)] pub struct ImageButton { // Content @@ -172,7 +182,8 @@ pub struct ImageButton { pub on_commit: WidgetCallback<()>, } -#[derive(Clone, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Clone, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder)] #[derivative(Debug, PartialEq, Default)] pub struct ColorInput { // Content @@ -207,7 +218,8 @@ pub struct ColorInput { pub on_commit: WidgetCallback<()>, } -#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder)] #[derivative(Debug, PartialEq)] pub struct BreadcrumbTrailButtons { // Content diff --git a/editor/src/messages/layout/utility_types/widgets/input_widgets.rs b/editor/src/messages/layout/utility_types/widgets/input_widgets.rs index c4b8773a..a1062f9c 100644 --- a/editor/src/messages/layout/utility_types/widgets/input_widgets.rs +++ b/editor/src/messages/layout/utility_types/widgets/input_widgets.rs @@ -1,3 +1,4 @@ +use crate::messages::frontend::IconName; use crate::messages::input_mapper::utility_types::misc::ActionShortcut; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier; @@ -7,14 +8,15 @@ use graphene_std::raster::curve::Curve; use graphene_std::transform::ReferencePoint; use graphite_proc_macros::WidgetBuilder; -#[derive(Clone, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Clone, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder)] #[derivative(Debug, Default, PartialEq)] pub struct CheckboxInput { // Content #[widget_builder(constructor)] pub checked: bool, - #[derivative(Default(value = "\"Checkmark\".to_string()"))] - pub icon: String, + #[widget_builder(string)] + pub icon: Option, #[serde(rename = "forLabel")] pub for_label: CheckboxId, pub disabled: bool, @@ -36,6 +38,7 @@ pub struct CheckboxInput { pub on_commit: WidgetCallback<()>, } +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)] pub struct CheckboxId(pub u64); @@ -49,14 +52,9 @@ impl Default for CheckboxId { Self::new() } } -impl specta::Type for CheckboxId { - fn inline(_type_map: &mut specta::TypeCollection, _generics: specta::Generics) -> specta::datatype::DataType { - // TODO: This might not be right, but it works for now. We just need the type `bigint | undefined`. - specta::datatype::DataType::Primitive(specta::datatype::PrimitiveType::u64) - } -} -#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder)] #[derivative(Debug, PartialEq, Default)] pub struct DropdownInput { // Content @@ -102,7 +100,8 @@ pub struct DropdownInput { pub type MenuListEntrySections = Vec>; -#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify), tsify(large_number_types_as_bigints))] +#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder)] #[derivative(Debug, PartialEq)] #[widget_builder(not_widget_instance)] pub struct MenuListEntry { @@ -110,7 +109,8 @@ pub struct MenuListEntry { #[widget_builder(constructor)] pub value: String, pub label: String, - pub icon: String, + #[widget_builder(string)] + pub icon: Option, pub disabled: bool, // Children @@ -120,7 +120,7 @@ pub struct MenuListEntry { pub children_hash: u64, // Styling - pub font: String, + pub font: Option, // Tooltips #[serde(rename = "tooltipLabel")] @@ -148,7 +148,8 @@ impl std::hash::Hash for MenuListEntry { } } -#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder)] #[derivative(Debug, PartialEq, Default)] pub struct NumberInput { // Content @@ -246,22 +247,30 @@ impl NumberInput { } } -#[derive(Clone, serde::Serialize, serde::Deserialize, Debug, Default, PartialEq, Eq, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Clone, serde::Serialize, serde::Deserialize, Debug, Default, PartialEq, Eq)] pub enum NumberInputIncrementBehavior { + /// The value is added by `step`. #[default] Add, + /// The value is multiplied by `step`. Multiply, + /// The functions `incrementCallbackIncrease` and `incrementCallbackDecrease` call custom behavior. Callback, + /// The increment arrows are not shown. + None, } -#[derive(Clone, serde::Serialize, serde::Deserialize, Debug, Default, PartialEq, Eq, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Clone, serde::Serialize, serde::Deserialize, Debug, Default, PartialEq, Eq)] pub enum NumberInputMode { #[default] Increment, Range, } -#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder)] #[derivative(Debug, PartialEq, Default)] pub struct NodeCatalog { // Content @@ -280,7 +289,8 @@ pub struct NodeCatalog { pub on_commit: WidgetCallback<()>, } -#[derive(Clone, Default, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Clone, Default, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder)] #[derivative(Debug, PartialEq)] pub struct RadioInput { // Content @@ -303,7 +313,8 @@ pub struct RadioInput { // Callbacks exists on the `RadioEntryData` children, not this parent `RadioInput` } -#[derive(Clone, Default, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Clone, Default, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder)] #[derivative(Debug, PartialEq)] #[widget_builder(not_widget_instance)] pub struct RadioEntryData { @@ -311,7 +322,8 @@ pub struct RadioEntryData { #[widget_builder(constructor)] pub value: String, pub label: String, - pub icon: String, + #[widget_builder(string)] + pub icon: Option, // Tooltips #[serde(rename = "tooltipLabel")] @@ -330,7 +342,8 @@ pub struct RadioEntryData { pub on_commit: WidgetCallback<()>, } -#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder)] #[derivative(Debug, PartialEq, Default)] pub struct WorkingColorsInput { // Content @@ -340,7 +353,8 @@ pub struct WorkingColorsInput { pub secondary: Color, } -#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder)] #[derivative(Debug, PartialEq, Default)] pub struct TextAreaInput { // Content @@ -366,7 +380,8 @@ pub struct TextAreaInput { pub on_commit: WidgetCallback<()>, } -#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder)] #[derivative(Debug, PartialEq, Default)] pub struct TextInput { // Content @@ -403,7 +418,8 @@ pub struct TextInput { pub on_commit: WidgetCallback<()>, } -#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder)] #[derivative(Debug, PartialEq, Default)] pub struct CurveInput { // Content @@ -427,7 +443,8 @@ pub struct CurveInput { pub on_commit: WidgetCallback<()>, } -#[derive(Clone, Default, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Clone, Default, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder)] #[derivative(Debug, PartialEq)] pub struct ReferencePointInput { // Content diff --git a/editor/src/messages/layout/utility_types/widgets/label_widgets.rs b/editor/src/messages/layout/utility_types/widgets/label_widgets.rs index deea81e0..8a3512ab 100644 --- a/editor/src/messages/layout/utility_types/widgets/label_widgets.rs +++ b/editor/src/messages/layout/utility_types/widgets/label_widgets.rs @@ -1,13 +1,15 @@ use super::input_widgets::CheckboxId; -use crate::messages::input_mapper::utility_types::misc::ActionShortcut; +use crate::messages::{frontend::IconName, input_mapper::utility_types::misc::ActionShortcut}; use derivative::*; use graphite_proc_macros::WidgetBuilder; -#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Debug, Default, PartialEq, Eq, WidgetBuilder, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Debug, Default, PartialEq, Eq, WidgetBuilder)] pub struct IconLabel { // Content #[widget_builder(constructor)] - pub icon: String, + #[widget_builder(string)] + pub icon: IconName, pub disabled: bool, // Tooltips @@ -19,7 +21,8 @@ pub struct IconLabel { pub tooltip_shortcut: Option, } -#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, WidgetBuilder, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, WidgetBuilder)] pub struct Separator { // Content pub direction: SeparatorDirection, @@ -27,14 +30,16 @@ pub struct Separator { pub style: SeparatorStyle, } -#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum SeparatorDirection { #[default] Horizontal, Vertical, } -#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum SeparatorStyle { Related, #[default] @@ -42,7 +47,8 @@ pub enum SeparatorStyle { Section, } -#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Debug, Eq, Default, WidgetBuilder, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Debug, Eq, Default, WidgetBuilder)] #[derivative(PartialEq)] pub struct TextLabel { // Content @@ -78,7 +84,8 @@ pub struct TextLabel { pub tooltip_shortcut: Option, } -#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder)] #[derivative(Debug, PartialEq)] pub struct ImageLabel { // Content @@ -96,7 +103,8 @@ pub struct ImageLabel { pub tooltip_shortcut: Option, } -#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder)] #[derivative(Debug, PartialEq)] pub struct ShortcutLabel { // Content diff --git a/editor/src/messages/menu_bar/menu_bar_message_handler.rs b/editor/src/messages/menu_bar/menu_bar_message_handler.rs index b52cc259..6dae07fc 100644 --- a/editor/src/messages/menu_bar/menu_bar_message_handler.rs +++ b/editor/src/messages/menu_bar/menu_bar_message_handler.rs @@ -76,7 +76,7 @@ impl LayoutHolder for MenuBarMessageHandler { TextButton::new("Graphite") .label("") .flush(true) - .icon(Some("GraphiteLogo".into())) + .icon("GraphiteLogo") .on_commit(|_| FrontendMessage::TriggerVisitLink { url: "https://graphite.art".into() }.into()) .widget_instance(), #[cfg(target_os = "macos")] @@ -750,6 +750,6 @@ impl LayoutHolder for MenuBarMessageHandler { .widget_instance(), ]; - Layout(vec![LayoutGroup::Row { widgets: menu_bar_buttons }]) + Layout(vec![LayoutGroup::row(menu_bar_buttons)]) } } diff --git a/editor/src/messages/message.rs b/editor/src/messages/message.rs index cc24a00a..385b2b45 100644 --- a/editor/src/messages/message.rs +++ b/editor/src/messages/message.rs @@ -45,14 +45,6 @@ pub enum Message { NoOp, } -/// Provides an impl of `specta::Type` for `MessageDiscriminant`, the struct created by `impl_message`. -/// Specta isn't integrated with `impl_message`, so a remote impl must be provided using this struct. -impl specta::Type for MessageDiscriminant { - fn inline(_type_map: &mut specta::TypeCollection, _generics: specta::Generics) -> specta::DataType { - specta::DataType::Any - } -} - impl Message { pub fn message_tree() -> DebugMessageTree { Self::build_message_tree() diff --git a/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs b/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs index b0b9baca..b767b603 100644 --- a/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs +++ b/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs @@ -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 { fn label(x: impl Into) -> Vec { let error = vec![TextLabel::new(x).widget_instance()]; - vec![LayoutGroup::Row { widgets: error }] + vec![LayoutGroup::row(error)] } trait TableRowLayout { @@ -234,7 +234,7 @@ impl TableRowLayout for Vec { rows.insert(0, column_headings(&["", "element"])); - vec![LayoutGroup::Table { rows, unstyled: false }] + vec![LayoutGroup::table(rows, false)] } } @@ -276,7 +276,7 @@ impl TableRowLayout for Table { 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 { 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 { }); 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 { } fn element_page(&self, _data: &mut LayoutData) -> Vec { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { } fn element_page(&self, _data: &mut LayoutData) -> Vec { 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 { 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 { 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 { 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 { 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)] } } diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 76fbfa48..0f273978 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -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> 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> 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, }); } diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index ed7f4408..06c64faa 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -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::().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) }), diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index 6945296c..b7f6992f 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -847,7 +847,7 @@ impl<'a> MessageHandler> 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> 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> 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, diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index 09677f33..efa508b3 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -31,7 +31,7 @@ use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops, Gra pub(crate) fn string_properties(text: &str) -> Vec { let widget = TextLabel::new(text).widget_instance(); - vec![LayoutGroup::Row { widgets: vec![widget] }] + vec![LayoutGroup::row(vec![widget])] } fn optionally_update_value(value: impl Fn(&T) -> Option + '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 { @@ -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::::INDEX, true, context), - NumberInput::default().label("H").min(0.).unit(" px"), - ), - }; + let spacing = LayoutGroup::row(number_widget( + ParameterWidgetsInfo::new(node_id, SpacingInput::::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 { @@ -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 { @@ -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 = 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); } diff --git a/editor/src/messages/portfolio/document/node_graph/utility_types.rs b/editor/src/messages/portfolio/document/node_graph/utility_types.rs index 620481a2..67ac7a11 100644 --- a/editor/src/messages/portfolio/document/node_graph/utility_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/utility_types.rs @@ -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, } -#[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, } -#[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, @@ -189,7 +192,8 @@ pub struct FrontendClickTargets { pub modify_import_export: Vec, } -#[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, diff --git a/editor/src/messages/portfolio/document/overlays/grid_overlays.rs b/editor/src/messages/portfolio/document/overlays/grid_overlays.rs index 899fd767..f3683c24 100644 --- a/editor/src/messages/portfolio/document/overlays/grid_overlays.rs +++ b/editor/src/messages/portfolio/document/overlays/grid_overlays.rs @@ -228,42 +228,38 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec { }) }; - 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 { })) .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(), - ], - }); + ])); } } diff --git a/editor/src/messages/portfolio/document/overlays/utility_types_native.rs b/editor/src/messages/portfolio/document/overlays/utility_types_native.rs index 5f854a77..9991d39c 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types_native.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types_native.rs @@ -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>, pub viewport: ViewportMessageHandler, pub visibility_settings: OverlaysVisibilitySettings, diff --git a/editor/src/messages/portfolio/document/overlays/utility_types_web.rs b/editor/src/messages/portfolio/document/overlays/utility_types_web.rs index 3c249dcb..c03ba387 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types_web.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types_web.rs @@ -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, diff --git a/editor/src/messages/portfolio/document/utility_types/clipboards.rs b/editor/src/messages/portfolio/document/utility_types/clipboards.rs index ef4b00ae..12d9b546 100644 --- a/editor/src/messages/portfolio/document/utility_types/clipboards.rs +++ b/editor/src/messages/portfolio/document/utility_types/clipboards.rs @@ -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, diff --git a/editor/src/messages/portfolio/document/utility_types/document_metadata.rs b/editor/src/messages/portfolio/document/utility_types/document_metadata.rs index bc7b838e..328477a3 100644 --- a/editor/src/messages/portfolio/document/utility_types/document_metadata.rs +++ b/editor/src/messages/portfolio/document/utility_types/document_metadata.rs @@ -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 { diff --git a/editor/src/messages/portfolio/document/utility_types/misc.rs b/editor/src/messages/portfolio/document/utility_types/misc.rs index ef363ae1..a41e0ca9 100644 --- a/editor/src/messages/portfolio/document/utility_types/misc.rs +++ b/editor/src/messages/portfolio/document/utility_types/misc.rs @@ -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, diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index 5a1722d5..ac95dfce 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -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 { diff --git a/editor/src/messages/portfolio/document/utility_types/nodes.rs b/editor/src/messages/portfolio/document/utility_types/nodes.rs index 3f4e5671..bfa94ee2 100644 --- a/editor/src/messages/portfolio/document/utility_types/nodes.rs +++ b/editor/src/messages/portfolio/document/utility_types/nodes.rs @@ -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, } -#[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, + pub icon_name: Option, 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); 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); diff --git a/editor/src/messages/portfolio/document/utility_types/wires.rs b/editor/src/messages/portfolio/document/utility_types/wires.rs index 7aad6977..439962ad 100644 --- a/editor/src/messages/portfolio/document/utility_types/wires.rs +++ b/editor/src/messages/portfolio/document/utility_types/wires.rs @@ -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, } -#[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, diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index e73eb4ef..d45f832f 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -1011,12 +1011,11 @@ impl MessageHandler> for Portfolio PortfolioMessage::RequestWelcomeScreenButtonsLayout => { let donate = "https://graphite.art/donate/"; - let table = LayoutGroup::Table { - unstyled: true, - rows: vec![ + let table = LayoutGroup::table( + vec![ vec![ TextButton::new("New Document") - .icon(Some("File".into())) + .icon("File") .flush(true) .on_commit(|_| DialogMessage::RequestNewDocumentDialog.into()) .widget_instance(), @@ -1024,7 +1023,7 @@ impl MessageHandler> for Portfolio ], vec![ TextButton::new("Open Document") - .icon(Some("Folder".into())) + .icon("Folder") .flush(true) .on_commit(|_| PortfolioMessage::Open.into()) .widget_instance(), @@ -1032,20 +1031,21 @@ impl MessageHandler> for Portfolio ], vec![ TextButton::new("Open Demo Artwork") - .icon(Some("Image".into())) + .icon("Image") .flush(true) .on_commit(|_| DialogMessage::RequestDemoArtworkDialog.into()) .widget_instance(), ], vec![ TextButton::new("Support the Development Fund") - .icon(Some("Heart".into())) + .icon("Heart") .flush(true) .on_commit(move |_| FrontendMessage::TriggerVisitLink { url: donate.to_string() }.into()) .widget_instance(), ], ], - }; + true, + ); responses.add(LayoutMessage::DestroyLayout { layout_target: LayoutTarget::WelcomeScreenButtons, @@ -1061,7 +1061,7 @@ impl MessageHandler> for Portfolio #[cfg(target_family = "wasm")] let widgets = vec![]; - let row = LayoutGroup::Row { widgets }; + let row = LayoutGroup::row(widgets); responses.add(LayoutMessage::SendLayout { layout: Layout(vec![row]), diff --git a/editor/src/messages/preferences/preferences_message_handler.rs b/editor/src/messages/preferences/preferences_message_handler.rs index 9fb3b4cd..8431934b 100644 --- a/editor/src/messages/preferences/preferences_message_handler.rs +++ b/editor/src/messages/preferences/preferences_message_handler.rs @@ -11,7 +11,8 @@ pub struct PreferencesMessageContext<'a> { pub tool_message_handler: &'a ToolMessageHandler, } -#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize, specta::Type, ExtractField)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize, ExtractField)] #[serde(default)] pub struct PreferencesMessageHandler { pub selection_mode: SelectionMode, diff --git a/editor/src/messages/preferences/utility_types.rs b/editor/src/messages/preferences/utility_types.rs index dabcb5b7..2cada9cc 100644 --- a/editor/src/messages/preferences/utility_types.rs +++ b/editor/src/messages/preferences/utility_types.rs @@ -1,4 +1,5 @@ -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type, Hash)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash)] pub enum SelectionMode { #[default] Touched = 0, diff --git a/editor/src/messages/tool/common_functionality/color_selector.rs b/editor/src/messages/tool/common_functionality/color_selector.rs index 5b65c8c3..c9ac7467 100644 --- a/editor/src/messages/tool/common_functionality/color_selector.rs +++ b/editor/src/messages/tool/common_functionality/color_selector.rs @@ -4,7 +4,8 @@ use crate::messages::prelude::*; use graphene_std::Color; use graphene_std::vector::style::FillChoice; -#[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 ToolColorType { Primary, Secondary, diff --git a/editor/src/messages/tool/common_functionality/pivot.rs b/editor/src/messages/tool/common_functionality/pivot.rs index 7ba3e7c7..072a7435 100644 --- a/editor/src/messages/tool/common_functionality/pivot.rs +++ b/editor/src/messages/tool/common_functionality/pivot.rs @@ -150,7 +150,8 @@ impl PivotGizmo { } } -#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize)] pub enum PivotGizmoType { // Pivot #[default] @@ -161,7 +162,8 @@ pub enum PivotGizmoType { // TODO: Add "Individual" } -#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, Hash, serde::Serialize, serde::Deserialize)] pub struct PivotGizmoState { pub enabled: bool, pub gizmo_type: PivotGizmoType, diff --git a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs index 46baf0f1..6e1d3f22 100644 --- a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs +++ b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs @@ -23,7 +23,8 @@ use kurbo::{BezPath, PathEl, Shape}; use std::collections::VecDeque; use std::f64::consts::{PI, TAU}; -#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, serde::Serialize, serde::Deserialize)] pub enum ShapeType { #[default] Polygon = 0, diff --git a/editor/src/messages/tool/tool_messages/artboard_tool.rs b/editor/src/messages/tool/tool_messages/artboard_tool.rs index e257dc35..0df69405 100644 --- a/editor/src/messages/tool/tool_messages/artboard_tool.rs +++ b/editor/src/messages/tool/tool_messages/artboard_tool.rs @@ -22,7 +22,8 @@ pub struct ArtboardTool { } #[impl_message(Message, ToolMessage, Artboard)] -#[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 ArtboardToolMessage { // Standard messages Abort, diff --git a/editor/src/messages/tool/tool_messages/brush_tool.rs b/editor/src/messages/tool/tool_messages/brush_tool.rs index d1b84b49..5319e21a 100644 --- a/editor/src/messages/tool/tool_messages/brush_tool.rs +++ b/editor/src/messages/tool/tool_messages/brush_tool.rs @@ -13,7 +13,8 @@ use graphene_std::raster::BlendMode; const BRUSH_MAX_SIZE: f64 = 5000.; -#[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)] pub enum DrawMode { Draw = 0, Erase, @@ -52,7 +53,8 @@ impl Default for BrushOptions { } #[impl_message(Message, ToolMessage, Brush)] -#[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 BrushToolMessage { // Standard messages Abort, @@ -65,7 +67,8 @@ pub enum BrushToolMessage { UpdateOptions { options: BrushToolMessageOptionsUpdate }, } -#[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 BrushToolMessageOptionsUpdate { BlendMode(BlendMode), ChangeDiameter(f64), @@ -220,7 +223,7 @@ impl LayoutHolder for BrushTool { .widget_instance(), ); - Layout(vec![LayoutGroup::Row { widgets }]) + Layout(vec![LayoutGroup::row(widgets)]) } } diff --git a/editor/src/messages/tool/tool_messages/eyedropper_tool.rs b/editor/src/messages/tool/tool_messages/eyedropper_tool.rs index eddb7cb8..c18abc13 100644 --- a/editor/src/messages/tool/tool_messages/eyedropper_tool.rs +++ b/editor/src/messages/tool/tool_messages/eyedropper_tool.rs @@ -9,7 +9,8 @@ pub struct EyedropperTool { } #[impl_message(Message, ToolMessage, Eyedropper)] -#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)] pub enum EyedropperToolMessage { // Standard messages Abort, @@ -46,9 +47,9 @@ impl LayoutHolder for EyedropperTool { impl<'a> MessageHandler> for EyedropperTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, context: &mut ToolActionMessageContext<'a>) { if let ToolMessage::Eyedropper(EyedropperToolMessage::PreviewImage { data, width, height }) = message { - let image = EyedropperPreviewImage { data, width, height }; + let image = EyedropperPreviewImage { data: data.into(), width, height }; - update_cursor_preview_common(responses, Some(image), context.input, context.global_tool_data, self.data.color_choice.clone()); + update_cursor_preview_common(responses, Some(image), context.input, context.global_tool_data, self.data.color_choice); if !self.data.preview { disable_cursor_preview(responses, &mut self.data); @@ -87,10 +88,18 @@ enum EyedropperToolFsmState { SamplingSecondary, } +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub enum PrimarySecondary { + #[default] + Primary, + Secondary, +} + #[derive(Clone, Debug, Default)] struct EyedropperToolData { preview: bool, - color_choice: Option, + color_choice: Option, } impl Fsm for EyedropperToolFsmState { @@ -127,7 +136,11 @@ impl Fsm for EyedropperToolFsmState { } // Sampling -> Ready (EyedropperToolFsmState::SamplingPrimary, EyedropperToolMessage::SamplePrimaryColorEnd) | (EyedropperToolFsmState::SamplingSecondary, EyedropperToolMessage::SampleSecondaryColorEnd) => { - let set_color_choice = if self == EyedropperToolFsmState::SamplingPrimary { "Primary" } else { "Secondary" }.to_string(); + let set_color_choice = match self { + EyedropperToolFsmState::SamplingPrimary => PrimarySecondary::Primary, + EyedropperToolFsmState::SamplingSecondary => PrimarySecondary::Secondary, + _ => unreachable!(), + }; update_cursor_preview(responses, tool_data, input, global_tool_data, Some(set_color_choice)); disable_cursor_preview(responses, tool_data); @@ -185,7 +198,7 @@ fn update_cursor_preview( tool_data: &mut EyedropperToolData, _input: &InputPreprocessorMessageHandler, _global_tool_data: &DocumentToolData, - set_color_choice: Option, + set_color_choice: Option, ) { tool_data.preview = true; tool_data.color_choice = set_color_choice; @@ -198,7 +211,7 @@ fn update_cursor_preview( tool_data: &mut EyedropperToolData, input: &InputPreprocessorMessageHandler, global_tool_data: &DocumentToolData, - set_color_choice: Option, + set_color_choice: Option, ) { tool_data.preview = true; tool_data.color_choice = set_color_choice.clone(); @@ -211,7 +224,7 @@ fn update_cursor_preview_common( image: Option, input: &InputPreprocessorMessageHandler, global_tool_data: &DocumentToolData, - set_color_choice: Option, + set_color_choice: Option, ) { responses.add(FrontendMessage::UpdateEyedropperSamplingState { image, diff --git a/editor/src/messages/tool/tool_messages/fill_tool.rs b/editor/src/messages/tool/tool_messages/fill_tool.rs index 9a069683..c1a2a8fe 100644 --- a/editor/src/messages/tool/tool_messages/fill_tool.rs +++ b/editor/src/messages/tool/tool_messages/fill_tool.rs @@ -9,7 +9,8 @@ pub struct FillTool { } #[impl_message(Message, ToolMessage, Fill)] -#[derive(PartialEq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(PartialEq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)] pub enum FillToolMessage { // Standard messages Abort, diff --git a/editor/src/messages/tool/tool_messages/freehand_tool.rs b/editor/src/messages/tool/tool_messages/freehand_tool.rs index b3812b87..f6fc27ed 100644 --- a/editor/src/messages/tool/tool_messages/freehand_tool.rs +++ b/editor/src/messages/tool/tool_messages/freehand_tool.rs @@ -37,7 +37,8 @@ impl Default for FreehandOptions { } #[impl_message(Message, ToolMessage, Freehand)] -#[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 FreehandToolMessage { // Standard messages Overlays { context: OverlayContext }, @@ -51,7 +52,8 @@ pub enum FreehandToolMessage { UpdateOptions { options: FreehandOptionsUpdate }, } -#[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 FreehandOptionsUpdate { FillColor(Option), FillColorType(ToolColorType), @@ -151,7 +153,7 @@ impl LayoutHolder for FreehandTool { widgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance()); widgets.push(create_weight_widget(self.options.line_weight)); - Layout(vec![LayoutGroup::Row { widgets }]) + Layout(vec![LayoutGroup::row(widgets)]) } } diff --git a/editor/src/messages/tool/tool_messages/gradient_tool.rs b/editor/src/messages/tool/tool_messages/gradient_tool.rs index 1a3b11a5..8f16b2a2 100644 --- a/editor/src/messages/tool/tool_messages/gradient_tool.rs +++ b/editor/src/messages/tool/tool_messages/gradient_tool.rs @@ -24,7 +24,8 @@ pub struct GradientOptions { } #[impl_message(Message, ToolMessage, Gradient)] -#[derive(PartialEq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(PartialEq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)] pub enum GradientToolMessage { // Standard messages Abort, @@ -46,7 +47,8 @@ pub enum GradientToolMessage { UpdateOptions { options: GradientOptionsUpdate }, } -#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)] pub enum GradientOptionsUpdate { Type(GradientType), ReverseStops, @@ -199,7 +201,7 @@ impl LayoutHolder for GradientTool { widgets.push(reverse_direction); } - Layout(vec![LayoutGroup::Row { widgets }]) + Layout(vec![LayoutGroup::row(widgets)]) } } @@ -770,8 +772,8 @@ impl Fsm for GradientToolFsmState { if stop_index < gradient.stops.position.len() { let color = gradient.stops.color[stop_index].to_gamma_srgb(); let position = gradient.stops.position[stop_index]; - let DVec2 { x, y } = transform.transform_point2(gradient.start.lerp(gradient.end, position)); - responses.add(FrontendMessage::UpdateGradientStopColorPickerPosition { color, x, y }); + let position = transform.transform_point2(gradient.start.lerp(gradient.end, position)).into(); + responses.add(FrontendMessage::UpdateGradientStopColorPickerPosition { color, position }); } } @@ -824,13 +826,10 @@ impl Fsm for GradientToolFsmState { let viewport_pos = selected_gradient .transform .transform_point2(selected_gradient.gradient.start.lerp(selected_gradient.gradient.end, stop_pos)); + let position = viewport_pos.into(); let color = selected_gradient.gradient.stops.color[stop_index].to_gamma_srgb(); tool_data.color_picker_editing_color_stop = Some(stop_index); - responses.add(FrontendMessage::UpdateGradientStopColorPickerPosition { - color, - x: viewport_pos.x, - y: viewport_pos.y, - }); + responses.add(FrontendMessage::UpdateGradientStopColorPickerPosition { color, position }); } } _ => {} diff --git a/editor/src/messages/tool/tool_messages/navigate_tool.rs b/editor/src/messages/tool/tool_messages/navigate_tool.rs index 8809ccde..22021a37 100644 --- a/editor/src/messages/tool/tool_messages/navigate_tool.rs +++ b/editor/src/messages/tool/tool_messages/navigate_tool.rs @@ -7,7 +7,8 @@ pub struct NavigateTool { } #[impl_message(Message, ToolMessage, Navigate)] -#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)] pub enum NavigateToolMessage { // Standard messages Abort, diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index 9b46386e..80f0a6fa 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -50,7 +50,8 @@ pub struct PathToolOptions { } #[impl_message(Message, ToolMessage, Path)] -#[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 PathToolMessage { // Standard messages Abort, @@ -155,7 +156,8 @@ pub enum PathToolMessage { ToggleSegmentEditing, } -#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug, Default, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug, Default, serde::Serialize, serde::Deserialize)] pub enum PathOverlayMode { AllHandles = 0, #[default] @@ -178,7 +180,8 @@ impl Default for PathEditingMode { } } -#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)] pub enum PathOptionsUpdate { OverlayModeType(PathOverlayMode), PointEditingMode { enabled: bool }, @@ -326,7 +329,7 @@ impl LayoutHolder for PathTool { // Works only if a single layer is selected and its type is Vector let path_node_button = TextButton::new("Make Path Editable") - .icon(Some("NodeShape".into())) + .icon("NodeShape") .tooltip_label("Make Path Editable") .tooltip_description( "Enables the Pen and Path tools to directly edit layer geometry resulting from nondestructive operations. This inserts a 'Path' node as the last operation of the selected layer.", @@ -349,32 +352,30 @@ impl LayoutHolder for PathTool { let _pin_pivot = pin_pivot_widget(self.tool_data.pivot_gizmo.pin_active(), false, PivotToolSource::Path); - Layout(vec![LayoutGroup::Row { - widgets: vec![ - x_location, - related_seperator.clone(), - y_location, - unrelated_seperator.clone(), - colinear_handle_checkbox, - related_seperator.clone(), - colinear_handles_label, - unrelated_seperator.clone(), - point_editing_mode, - related_seperator.clone(), - segment_editing_mode, - unrelated_seperator.clone(), - path_overlay_mode_widget, - unrelated_seperator.clone(), - path_node_button, - // checkbox.clone(), - // related_seperator.clone(), - // dropdown.clone(), - // unrelated_seperator, - // pivot_reference, - // related_seperator.clone(), - // pin_pivot, - ], - }]) + Layout(vec![LayoutGroup::row(vec![ + x_location, + related_seperator.clone(), + y_location, + unrelated_seperator.clone(), + colinear_handle_checkbox, + related_seperator.clone(), + colinear_handles_label, + unrelated_seperator.clone(), + point_editing_mode, + related_seperator.clone(), + segment_editing_mode, + unrelated_seperator.clone(), + path_overlay_mode_widget, + unrelated_seperator.clone(), + path_node_button, + // checkbox.clone(), + // related_seperator.clone(), + // dropdown.clone(), + // unrelated_seperator, + // pivot_reference, + // related_seperator.clone(), + // pin_pivot, + ])]) } } diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index ceab12ba..b83bda18 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -44,7 +44,8 @@ impl Default for PenOptions { } #[impl_message(Message, ToolMessage, Pen)] -#[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 PenToolMessage { // Standard messages Abort, @@ -107,13 +108,15 @@ enum PenToolFsmState { GRSHandle, } -#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug, serde::Serialize, serde::Deserialize)] pub enum PenOverlayMode { AllHandles = 0, FrontierHandles = 1, } -#[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 PenOptionsUpdate { FillColor(Option), FillColorType(ToolColorType), @@ -238,7 +241,7 @@ impl LayoutHolder for PenTool { .widget_instance(), ); - Layout(vec![LayoutGroup::Row { widgets }]) + Layout(vec![LayoutGroup::row(widgets)]) } } diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index 61012e6a..2ddff084 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -42,7 +42,8 @@ pub struct SelectOptions { nested_selection_behavior: NestedSelectionBehavior, } -#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)] pub enum SelectOptionsUpdate { NestedSelectionBehavior(NestedSelectionBehavior), PivotGizmoType(PivotGizmoType), @@ -50,7 +51,8 @@ pub enum SelectOptionsUpdate { TogglePivotPinned, } -#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize)] pub enum NestedSelectionBehavior { #[default] Shallowest, @@ -66,7 +68,8 @@ impl fmt::Display for NestedSelectionBehavior { } } -#[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 SelectToolPointerKeys { pub axis_align: Key, pub snap_angle: Key, @@ -75,7 +78,8 @@ pub struct SelectToolPointerKeys { } #[impl_message(Message, ToolMessage, Select)] -#[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 SelectToolMessage { // Standard messages Abort, @@ -265,7 +269,7 @@ impl LayoutHolder for SelectTool { widgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance()); widgets.extend(self.boolean_widgets(self.tool_data.selected_layers_count)); - Layout(vec![LayoutGroup::Row { widgets }]) + Layout(vec![LayoutGroup::row(widgets)]) } } diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index 43895d0d..8e0ac4bc 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -68,7 +68,8 @@ impl Default for ShapeToolOptions { } } -#[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 ShapeOptionsUpdate { FillColor(Option), FillColorType(ToolColorType), @@ -88,7 +89,8 @@ pub enum ShapeOptionsUpdate { } #[impl_message(Message, ToolMessage, Shape)] -#[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 ShapeToolMessage { // Standard messages Overlays { context: OverlayContext }, @@ -423,7 +425,7 @@ impl LayoutHolder for ShapeTool { widgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance()); widgets.push(create_weight_widget(self.options.line_weight)); - Layout(vec![LayoutGroup::Row { widgets }]) + Layout(vec![LayoutGroup::row(widgets)]) } } diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index 345b5a05..b5ef6c6b 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -38,7 +38,8 @@ impl Default for SplineOptions { } #[impl_message(Message, ToolMessage, Spline)] -#[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 SplineToolMessage { // Standard messages Overlays { context: OverlayContext }, @@ -65,7 +66,8 @@ enum SplineToolFsmState { MergingEndpoints, } -#[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 SplineOptionsUpdate { FillColor(Option), FillColorType(ToolColorType), @@ -158,7 +160,7 @@ impl LayoutHolder for SplineTool { widgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance()); widgets.push(create_weight_widget(self.options.line_weight)); - Layout(vec![LayoutGroup::Row { widgets }]) + Layout(vec![LayoutGroup::row(widgets)]) } } diff --git a/editor/src/messages/tool/tool_messages/text_tool.rs b/editor/src/messages/tool/tool_messages/text_tool.rs index c85b94a4..c2a35bb6 100644 --- a/editor/src/messages/tool/tool_messages/text_tool.rs +++ b/editor/src/messages/tool/tool_messages/text_tool.rs @@ -55,7 +55,8 @@ impl Default for TextOptions { } #[impl_message(Message, ToolMessage, Text)] -#[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 TextToolMessage { // Standard messages Abort, @@ -75,7 +76,8 @@ pub enum TextToolMessage { RefreshEditingFontData, } -#[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 TextOptionsUpdate { FillColor(Option), FillColorType(ToolColorType), @@ -271,7 +273,7 @@ impl TextTool { }, )); - Layout(vec![LayoutGroup::Row { widgets }]) + Layout(vec![LayoutGroup::row(widgets)]) } } @@ -413,7 +415,7 @@ impl TextToolData { line_height_ratio: editing_text.typesetting.line_height_ratio, font_size: editing_text.typesetting.font_size, color: editing_text.color.map_or("#000000".to_string(), |color| format!("#{}", color.to_rgba_hex_srgb())), - font_data: font_cache.get(&editing_text.font).map(|(data, _)| data.clone()).unwrap_or_default(), + font_data: font_cache.get(&editing_text.font).map(|(data, _)| data.clone()).unwrap_or_default().into(), transform: editing_text.transform.to_cols_array(), max_width: editing_text.typesetting.max_width, max_height: editing_text.typesetting.max_height, @@ -925,7 +927,7 @@ impl Fsm for TextToolFsmState { (TextToolFsmState::Editing, TextToolMessage::RefreshEditingFontData) => { let font = Font::new(tool_options.font.font_family.clone(), tool_options.font.font_style.clone()); responses.add(FrontendMessage::DisplayEditableTextboxUpdateFontData { - font_data: font_cache.get(&font).map(|(data, _)| data.clone()).unwrap_or_default(), + font_data: font_cache.get(&font).map(|(data, _)| data.clone()).unwrap_or_default().into(), }); TextToolFsmState::Editing diff --git a/editor/src/messages/tool/utility_types.rs b/editor/src/messages/tool/utility_types.rs index ead8c522..c54f3c1c 100644 --- a/editor/src/messages/tool/utility_types.rs +++ b/editor/src/messages/tool/utility_types.rs @@ -128,23 +128,21 @@ pub struct DocumentToolData { impl DocumentToolData { pub fn update_working_colors(&self, responses: &mut VecDeque) { let layout = Layout(vec![ - LayoutGroup::Row { - widgets: vec![WorkingColorsInput::new(self.primary_color.to_gamma_srgb(), self.secondary_color.to_gamma_srgb()).widget_instance()], - }, - LayoutGroup::Row { - widgets: vec![ - IconButton::new("SwapVertical", 16) - .tooltip_label("Swap Working Colors") - .tooltip_shortcut(action_shortcut!(ToolMessageDiscriminant::SwapColors)) - .on_update(|_| ToolMessage::SwapColors.into()) - .widget_instance(), - IconButton::new("WorkingColors", 16) - .tooltip_label("Reset Working Colors") - .tooltip_shortcut(action_shortcut!(ToolMessageDiscriminant::ResetColors)) - .on_update(|_| ToolMessage::ResetColors.into()) - .widget_instance(), - ], - }, + LayoutGroup::row(vec![ + WorkingColorsInput::new(self.primary_color.to_gamma_srgb(), self.secondary_color.to_gamma_srgb()).widget_instance(), + ]), + LayoutGroup::row(vec![ + IconButton::new("SwapVertical", 16) + .tooltip_label("Swap Working Colors") + .tooltip_shortcut(action_shortcut!(ToolMessageDiscriminant::SwapColors)) + .on_update(|_| ToolMessage::SwapColors.into()) + .widget_instance(), + IconButton::new("WorkingColors", 16) + .tooltip_label("Reset Working Colors") + .tooltip_shortcut(action_shortcut!(ToolMessageDiscriminant::ResetColors)) + .on_update(|_| ToolMessage::ResetColors.into()) + .widget_instance(), + ]), ]); responses.add(LayoutMessage::SendLayout { @@ -308,7 +306,7 @@ impl ToolData { .skip(1) .collect(); - Layout(vec![LayoutGroup::Row { widgets: tool_groups_layout }]) + Layout(vec![LayoutGroup::row(tool_groups_layout)]) } } @@ -360,7 +358,8 @@ impl ToolFsmState { } #[repr(usize)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, Default, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, Default)] pub enum ToolType { // General tool group #[default] @@ -524,7 +523,8 @@ pub fn tool_type_to_activate_tool_message(tool_type: ToolType) -> ToolMessageDis } } -#[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct HintData(pub Vec); impl HintData { @@ -558,7 +558,7 @@ impl HintData { } } - Layout(vec![LayoutGroup::Row { widgets }]) + Layout(vec![LayoutGroup::row(widgets)]) } pub fn send_layout(&self, responses: &mut VecDeque) { @@ -576,10 +576,12 @@ impl HintData { } } -#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct HintGroup(pub Vec); -#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct HintInfo { /// A `KeysGroup` specifies all the keys pressed simultaneously to perform an action (like "Ctrl C" to copy). /// Usually at most one is given, but less commonly, multiple can be used to describe additional hotkeys not used simultaneously (like the four different arrow keys to nudge a layer). diff --git a/editor/src/messages/viewport/viewport_message_handler.rs b/editor/src/messages/viewport/viewport_message_handler.rs index a060a14e..8a319611 100644 --- a/editor/src/messages/viewport/viewport_message_handler.rs +++ b/editor/src/messages/viewport/viewport_message_handler.rs @@ -3,7 +3,8 @@ use std::ops::{Add, Div, Mul, Sub}; use crate::messages::prelude::*; use crate::messages::tool::tool_messages::tool_prelude::DVec2; -#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, specta::Type, ExtractField)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, ExtractField)] pub struct ViewportMessageHandler { bounds: Bounds, // Ratio of logical pixels to physical pixels @@ -157,7 +158,8 @@ pub trait Position { fn y(&self) -> f64; } -#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)] struct Point { x: f64, y: f64, @@ -183,7 +185,8 @@ impl Position for Point { } } -#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)] pub struct LogicalPoint { inner: Point, scale: f64, @@ -217,7 +220,8 @@ impl FromWithScale for LogicalPoint { } } -#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)] pub struct PhysicalPoint { inner: Point, scale: f64, @@ -258,7 +262,8 @@ pub trait Rect: Position { fn height(&self) -> f64; } -#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, specta::Type, ExtractField)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, ExtractField)] struct Bounds { offset: Point, size: Point, @@ -286,7 +291,8 @@ impl Rect for Bounds { } } -#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)] pub struct LogicalBounds { offset: Point, size: Point, @@ -338,7 +344,8 @@ impl FromWithScale for LogicalBounds { } } -#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, specta::Type)] +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)] pub struct PhysicalBounds { offset: Point, size: Point, diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index 175540b0..ce07aead 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -450,7 +450,10 @@ impl NodeGraphExecutor { .. }) => { if file_type == FileType::Svg { - responses.add(FrontendMessage::TriggerSaveFile { name, content: svg.into_bytes() }); + responses.add(FrontendMessage::TriggerSaveFile { + name, + content: svg.into_bytes().into(), + }); } else { let mime = file_type.to_mime().to_string(); let size = (size * scale_factor).into(); @@ -496,7 +499,7 @@ impl NodeGraphExecutor { } } - responses.add(FrontendMessage::TriggerSaveFile { name, content: encoded }); + responses.add(FrontendMessage::TriggerSaveFile { name, content: encoded.into() }); } _ => { return Err(format!("Incorrect render type for exporting to an SVG ({file_type:?}, {node_graph_output})")); diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs index 60b04c13..4c7b196d 100644 --- a/editor/src/node_graph_executor/runtime.rs +++ b/editor/src/node_graph_executor/runtime.rs @@ -56,7 +56,7 @@ pub struct NodeRuntime { thumbnail_renders: HashMap>, vector_modify: HashMap, - /// Cached surface for WASM viewport rendering (reused across frames) + /// Cached surface for Wasm viewport rendering (reused across frames) #[cfg(all(target_family = "wasm", feature = "gpu"))] wasm_viewport_surface: Option, } @@ -309,7 +309,7 @@ impl NodeRuntime { data: RenderOutputType::Texture(image_texture), metadata, })) if !render_config.for_export => { - // On WASM, for viewport rendering, blit the texture to a surface and return a CanvasFrame + // On Wasm, for viewport rendering, blit the texture to a surface and return a CanvasFrame let app_io = self.editor_api.application_io.as_ref().unwrap(); let executor = app_io.gpu_executor().expect("GPU executor should be available when we receive a texture"); diff --git a/frontend/README.md b/frontend/README.md index 3a75c833..c285691f 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -4,11 +4,7 @@ The Graphite frontend is a web app that provides the presentation for the editor ## Bundled assets: `assets/` -Icons and images that are used in components and embedded into the application bundle by the build system. - -## Public assets: `public/` - -Static content like favicons that are copied directly into the root of the build output by the build system. +Images that are used in components and embedded into the application bundle by the build system. ## Svelte/TypeScript source: `src/` @@ -18,22 +14,27 @@ Source code for the web app in the form of Svelte components and [TypeScript](ht Wraps the editor backend codebase (`/editor`) and provides a JS-centric API for the web app to use as an entry point, unburdened by Rust's complex data types that are incompatible with JS data types. Bindings (JS functions that call into the Wasm module) are provided by [wasm-bindgen](https://rustwasm.github.io/docs/wasm-bindgen/) in concert with [wasm-pack](https://github.com/rustwasm/wasm-pack). -## ESLint configurations: `.eslintrc.cjs` +## ESLint configuration: `eslint.config.js` -[ESLint](https://eslint.org/) is the tool which enforces style rules on the JS, TS, and Svelte files in our frontend codebase. As it is set up in this config file, ESLint will complain about bad practices and often help reformat code automatically when (in VS Code) the file is saved or `npm run lint` is executed. (If you don't use VS Code, remember to run this command before committing!) This config file for ESLint sets our style preferences and configures our usage of extensions/plugins for Svelte support and [Prettier](https://prettier.io/)'s role as a code formatter. +When you use `npm run check`, [ESLint](https://eslint.org/) checks the code in the frontend project for code quality. (The command also reports TS and Svelte errors.) The tool enforces style rules on the JS, TS, and Svelte (including its HTML and SCSS) files in our frontend codebase. As it is set up in this config file, ESLint will complain about bad practices and often help reformat code automatically when the file is saved in VS Code, or manually when `npm run fix` is executed. (If you don't use VS Code, remember to run this command before committing!) This config file for ESLint sets our style preferences and configures our usage of extensions/plugins for Svelte support and [Prettier](https://prettier.io/)'s role as a code formatter. + +## Svelte configuration: `svelte.config.js` + +Configures the Svelte compiler, including the preprocessor setup for SCSS and TypeScript support, and compiler warning filters. + +## TypeScript configuration: `tsconfig.json` + +Basic configuration options for the TypeScript build tool to do its job in our repository. + +## Vite configuration: `vite.config.ts` + +We use the [Vite](https://vitejs.dev/) bundler/build system. This file is where we configure Vite to set up plugins (like the third-party license checker/generator). Part of the license checker plugin setup includes some functions to format web package licenses, as well as Rust package licenses provided by [cargo-about](https://github.com/EmbarkStudios/cargo-about), into a text file that's distributed with the application to provide license notices for third-party code. ## npm ecosystem packages: `package.json` -While we don't use Node.js as a JS-based server, we do rely on its ecosystem of packages for our build system toolchain. If you're just getting started, make sure to install the latest LTS copy of [Node.js](https://nodejs.org/en/download). Our project's philosophy on third-party packages is to keep our dependency tree as light as possible, so adding anything new to our `package.json` should have overwhelming justification. Most of the packages are just development tooling (TypeScript, Vite, ESLint, Prettier, and [Sass](https://sass-lang.com/)) that run in your terminal during the build process. +While we don't use Node.js as a JS-based server, we do rely on its ecosystem of packages for our build system toolchain. Our project's philosophy on third-party packages is to keep our dependency tree as light as possible, so adding anything new to our `package.json` should have overwhelming justification. Most of the packages are just development tooling (TypeScript, Vite, ESLint, Prettier, Sass, etc.) that run in your terminal during the build process. ## npm package installed versions: `package-lock.json` Specifies the exact versions of packages installed in the npm dependency tree. While `package.json` specifies which packages to install and their minimum/maximum acceptable version numbers, `package-lock.json` represents the exact versions of each dependency and sub-dependency. Running `npm ci` will grab these exact versions to ensure you are using the same packages as everyone else working on Graphite. `npm update` will modify `package-lock.json` to specify newer versions of any updated (sub-)dependencies and download those, as long as they don't exceed the maximum version allowed in `package.json`. To check for newer versions that exceed the max version, run `npm outdated` to see a list. Unless you know why you are doing it, try to avoid committing updates to `package-lock.json` by mistake if your code changes don't pertain to package updates. And never manually modify the file. -## TypeScript configurations: `tsconfig.json` - -Basic configuration options for the TypeScript build tool to do its job in our repository. - -## Vite configurations: `vite.config.ts` - -We use the [Vite](https://vitejs.dev/) bundler/build system. This file is where we configure Vite to set up plugins (like the third-party license checker/generator). Part of the license checker plugin setup includes some functions to format web package licenses, as well as Rust package licenses provided by [cargo-about](https://github.com/EmbarkStudios/cargo-about), into a text file that's distributed with the application to provide license notices for third-party code. diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index 5cce2a0d..1680a240 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -72,7 +72,7 @@ export default defineConfig([ ], "@typescript-eslint/consistent-type-imports": "error", "@typescript-eslint/consistent-type-definitions": ["error", "type"], - "@typescript-eslint/consistent-type-assertions": ["error", { assertionStyle: "as", objectLiteralTypeAssertions: "never" }], + "@typescript-eslint/consistent-type-assertions": ["error", { assertionStyle: "never" }], "@typescript-eslint/consistent-indexed-object-style": ["error", "record"], "@typescript-eslint/consistent-generic-constructors": ["error", "constructor"], "@typescript-eslint/no-restricted-types": ["error", { types: { null: "Use `undefined` instead." } }], diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 1c7d063c..80266616 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -31,6 +31,7 @@ "process": "^0.11.10", "sass": "^1.97.2", "svelte": "5.47.1", + "svelte-check": "^4.4.4", "svelte-preprocess": "^6.0.3", "tar": "^7.5.4", "ts-node": "^10.9.2", @@ -5277,6 +5278,16 @@ "node": ">=10" } }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -6135,6 +6146,19 @@ "tslib": "^2.1.0" } }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/safe-array-concat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", @@ -6715,6 +6739,30 @@ "node": ">=18" } }, + "node_modules/svelte-check": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.4.4.tgz", + "integrity": "sha512-F1pGqXc710Oi/wTI4d/x7d6lgPwwfx1U6w3Q35n4xsC2e8C/yN2sM1+mWxjlMcpAfWucjlq4vPi+P4FZ8a14sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "chokidar": "^4.0.1", + "fdir": "^6.2.0", + "picocolors": "^1.0.0", + "sade": "^1.7.4" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": ">=5.0.0" + } + }, "node_modules/svelte-eslint-parser": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.4.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index fc70d5f7..8d3da8df 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,8 +15,8 @@ "build-native": "npm run setup && npm run native:build-production", "build-native-dev": "npm run setup && npm run native:build-dev", "---------- UTILITIES ----------": "", - "lint": "eslint . && tsc --noEmit", - "lint-fix": "eslint . --fix && tsc --noEmit", + "check": "svelte-check --fail-on-warnings && eslint", + "fix": "eslint --fix", "---------- INTERNAL ----------": "", "setup": "node package-installer.js && node branding-installer.js", "native:build-dev": "wasm-pack build ./wasm --dev --target=web --no-default-features --features native && vite build --mode native", @@ -45,6 +45,7 @@ "eslint-plugin-prettier": "^5.5.5", "eslint-plugin-svelte": "^3.14.0", "globals": "^17.0.0", + "svelte-check": "^4.4.4", "license-checker-rseidelsohn": "^4.4.2", "postcss": "^8.5.6", "prettier": "^3.8.0", diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index 8e103c54..6e98a39b 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -15,7 +15,7 @@ }); onDestroy(() => { - // Destroy the WASM editor handle + // Destroy the Wasm editor handle editor?.handle.free(); }); diff --git a/frontend/src/README.md b/frontend/src/README.md index 6206153e..7195b053 100644 --- a/frontend/src/README.md +++ b/frontend/src/README.md @@ -2,7 +2,7 @@ ## Svelte components: `components/` -Svelte components that build the Graphite editor GUI, which are mounted in `App.svelte`. These each contain a Svelte-templated HTML section, an SCSS (Stylus CSS) section, and a script section. The aim is to avoid implementing much editor business logic here, just enough to make things interactive and communicate to the backend where the real business logic should occur. +Svelte components that build the Graphite editor GUI. These each contain a TypeScript section, a Svelte-templated HTML template section, and an SCSS stylesheet section. The aim is to avoid implementing much editor business logic here, just enough to make things interactive and communicate to the backend where the real business logic should occur. ## I/O managers: `io-managers/` @@ -18,27 +18,23 @@ TypeScript files which provide reactive state and importable functions to Svelte In `Editor.svelte`, an instance of each of these are given to Svelte's `setContext()` function. This allows any component to access the state provider instance using `const exampleStateProvider = getContext("exampleStateProvider");`. -## _I/O managers vs. state providers_ +## *I/O managers vs. state providers* -_Some state providers, similarly to I/O managers, may subscribe to backend events, call functions from `editor_api.rs` into the backend, and interact with browser APIs and user input. The difference is that state providers are meant to be made available to components via `getContext()` to use them for reactive state, while I/O managers are meant to be self-contained systems that operate for the lifetime of the application and aren't touched by Svelte components._ +*Some state providers, similarly to I/O managers, may subscribe to backend events, call functions from `editor_api.rs` into the backend, and interact with browser APIs and user input. The difference is that state providers are meant to be made available to components via `getContext()` to use them for reactive state, while I/O managers are meant to be self-contained systems that operate for the lifetime of the application and aren't touched by Svelte components.* ## Utility functions: `utility-functions/` TypeScript files which define and `export` individual helper functions for use elsewhere in the codebase. These files should not persist state outside each function. -## WASM editor: `editor.ts` +## Wasm editor: `editor.ts` -Instantiates the WASM and editor backend instances. The function `initWasm()` asynchronously constructs and initializes an instance of the WASM bindings JS module provided by wasm-bindgen/wasm-pack. The function `createEditor()` constructs an instance of the editor backend. In theory there could be multiple editor instances sharing the same WASM module instance. The function returns an object where `raw` is the WASM module, `instance` is the editor, and `subscriptions` is the subscription router (described below). +Instantiates the Wasm and editor backend instances. The function `initWasm()` asynchronously constructs and initializes an instance of the Wasm bindings JS module provided by wasm-bindgen/wasm-pack. The function `createEditor()` constructs an instance of the editor backend. In theory there could be multiple editor instances sharing the same Wasm module instance. The function returns an object where `raw` is the Wasm memory, `handle` provides access to callable backend functions, and `subscriptions` is the subscription router (described below). -`initWasm()` occurs in `main.ts` right before the Svelte application exists, then `createEditor()` is run in `Editor.svelte` during the Svelte app's creation. Similarly to the state providers described above, the editor is given via `setContext()` so other components can get it via `getContext` and call functions on `editor.raw`, `editor.handle`, or `editor.subscriptions`. - -## Message definitions: `messages.ts` - -Defines the message formats and data types received from the backend. Since Rust and JS support different styles of data representation, this bridges the gap from Rust into JS land. Messages (and the data contained within) are serialized in Rust by `serde` into JSON, and these definitions are manually kept up-to-date to parallel the message structs and their data types. (However, directives like `#[serde(skip)]` or `#[serde(rename = "someOtherName")]` may cause the TypeScript format to look slightly different from the Rust structs.) These definitions are basically just for the sake of TypeScript to understand the format, although in some cases we may perform data conversion here using translation functions that we can provide. +`initWasm()` occurs in `main.ts` right before the Svelte application is mounted, then `createEditor()` is run in `Editor.svelte` during the Svelte app's creation. Similarly to the state providers described above, the editor is given via `setContext()` so other components can get it via `getContext` and call functions on `editor.handle` or `editor.subscriptions`. ## Subscription router: `subscription-router.ts` -Associates messages from the backend with subscribers in the frontend, and routes messages to subscriber callbacks. This module provides a `subscribeFrontendMessage(messageType, callback)` function which JS code throughout the frontend can call to be registered as the exclusive handler for a chosen message type. This file's other exported function, `handleFrontendMessage(messageType, messageData, wasm, instance)`, is called in `editor.ts` by the associated editor instance when the backend sends a `FrontendMessage`. When this occurs, the subscription router delivers the message to the subscriber for given `messageType` by executing its registered `callback` function. As an argument to the function, it provides the `messageData` payload transformed into its TypeScript-friendly format defined in `messages.ts`. +Associates messages from the backend with subscribers in the frontend, and routes messages to subscriber callbacks. This module provides a `subscribeFrontendMessage(messageType, callback)` function which JS code throughout the frontend can call to be registered as the exclusive handler for a chosen message type. The router's other function, `handleFrontendMessage(messageType, messageData)`, is called via the callback passed to `EditorHandle.create()` in `editor.ts` when the backend sends a `FrontendMessage`. When this occurs, the subscription router delivers the message to the subscriber by executing its registered `callback` function. ## Svelte app entry point: `App.svelte` @@ -48,6 +44,10 @@ The entry point for the Svelte application. This is where we define global CSS style rules, create/destroy the editor instance, construct/destruct the I/O managers, and construct and `setContext()` the state providers. +## Global type augmentations: `global.d.ts` + +Extends built-in browser type definitions using TypeScript's interface merging. This includes Graphite's custom properties on the `window` object, custom events like `pointerlockmove`, and experimental browser APIs not yet in TypeScript's standard library. New custom events or non-standard browser APIs used by the frontend should be declared here. + ## JS bundle entry point: `main.ts` -The entry point for the entire project's code bundle. Here we simply initialize the Svelte application with `export default new App({ target: document.body });`. +The entry point for the entire project's code bundle. Here we simply mount the Svelte application with `export default mount(App, { target: document.body });`. diff --git a/frontend/src/components/Editor.svelte b/frontend/src/components/Editor.svelte index 307f215a..0f144082 100644 --- a/frontend/src/components/Editor.svelte +++ b/frontend/src/components/Editor.svelte @@ -19,7 +19,7 @@ import MainWindow from "@graphite/components/window/MainWindow.svelte"; - // Graphite WASM editor + // Graphite Wasm editor export let editor: Editor; setContext("editor", editor); diff --git a/frontend/src/components/README.md b/frontend/src/components/README.md index bcf02e63..6669aaba 100644 --- a/frontend/src/components/README.md +++ b/frontend/src/components/README.md @@ -1,6 +1,6 @@ # Overview of `/frontend/src/components/` -Each component represents a (usually reusable) part of the Graphite editor GUI. These all get mounted in `Editor.svelte` (in the `/src` directory above this one). +Each component represents a (usually reusable) part of the Graphite editor GUI. ## Floating Menus: `floating-menus/` @@ -12,7 +12,11 @@ Useful containers that control the flow of content held within. ## Panels: `panels/` -The dockable tabbed regions like the Document, Properties, Layers, and Node Graph panels. +The dockable tabbed regions like the Document, Properties, Layers, Data, and Welcome panels. + +## Views: `views/` + +Content views rendered within panels, such as the node graph. ## Widgets: `widgets/` diff --git a/frontend/src/components/floating-menus/ColorPicker.svelte b/frontend/src/components/floating-menus/ColorPicker.svelte index ac6ccb31..8dee3b78 100644 --- a/frontend/src/components/floating-menus/ColorPicker.svelte +++ b/frontend/src/components/floating-menus/ColorPicker.svelte @@ -1,15 +1,14 @@ @@ -28,8 +29,9 @@ - - + {#if $dialog.icon} + + {/if} {$dialog.title} @@ -104,10 +106,13 @@ .icon-label { width: 24px; height: 24px; + + + .text-label { + margin-left: 12px; + } } .text-label { - margin-left: 12px; line-height: 24px; } } @@ -134,7 +139,7 @@ } .text-label.multiline { - -webkit-user-select: text; // Still required by Safari as of 2025 + -webkit-user-select: text; // Still required by Safari as of 2026 user-select: text; } diff --git a/frontend/src/components/floating-menus/MenuList.svelte b/frontend/src/components/floating-menus/MenuList.svelte index bde25b57..aeb196f7 100644 --- a/frontend/src/components/floating-menus/MenuList.svelte +++ b/frontend/src/components/floating-menus/MenuList.svelte @@ -3,7 +3,7 @@ {#each layout as layoutGroup} - {#if isWidgetSpanRow(layoutGroup) || isWidgetSpanColumn(layoutGroup)} - - {:else if isWidgetSection(layoutGroup)} - - {:else if isWidgetTable(layoutGroup)} - + {#if "Row" in layoutGroup} + + {:else if "Column" in layoutGroup} + + {:else if "Section" in layoutGroup} + + {:else if "Table" in layoutGroup} + {/if} {/each} diff --git a/frontend/src/components/widgets/WidgetSection.svelte b/frontend/src/components/widgets/WidgetSection.svelte index c71a562c..0314eb31 100644 --- a/frontend/src/components/widgets/WidgetSection.svelte +++ b/frontend/src/components/widgets/WidgetSection.svelte @@ -1,9 +1,8 @@
{#each widgets as widget, widgetIndex} - {@const config = widgetRegistry[widget.props.kind]} - {@const props = config?.getProps(widget.props, widgetIndex)} - {@const slot = config?.getSlotContent?.(widget.props)} - {#if props !== undefined && slot !== undefined} - {slot} - {:else if props !== undefined} - + {@const unwrapped = unwrapWidget(widget)} + {#if unwrapped} + {@const { component, props, slot } = resolveWidget(unwrapped, widgetIndex)} + {#if props !== undefined && slot !== undefined} + {slot} + {:else if props !== undefined} + + {/if} {/if} {/each}
diff --git a/frontend/src/components/widgets/WidgetTable.svelte b/frontend/src/components/widgets/WidgetTable.svelte index 009d649b..ffb1dc1b 100644 --- a/frontend/src/components/widgets/WidgetTable.svelte +++ b/frontend/src/components/widgets/WidgetTable.svelte @@ -1,22 +1,21 @@ - +
{#each widgetData.tableWidgets as row} {#each row as cell} {/each} diff --git a/frontend/src/components/widgets/buttons/BreadcrumbTrailButtons.svelte b/frontend/src/components/widgets/buttons/BreadcrumbTrailButtons.svelte index 60b0255c..3c70b746 100644 --- a/frontend/src/components/widgets/buttons/BreadcrumbTrailButtons.svelte +++ b/frontend/src/components/widgets/buttons/BreadcrumbTrailButtons.svelte @@ -1,5 +1,5 @@ diff --git a/frontend/src/components/widgets/inputs/ColorInput.svelte b/frontend/src/components/widgets/inputs/ColorInput.svelte index 1e0b401d..7b606f43 100644 --- a/frontend/src/components/widgets/inputs/ColorInput.svelte +++ b/frontend/src/components/widgets/inputs/ColorInput.svelte @@ -1,9 +1,8 @@ diff --git a/frontend/src/components/widgets/inputs/CurveInput.svelte b/frontend/src/components/widgets/inputs/CurveInput.svelte index 7d994ccb..9fc746ca 100644 --- a/frontend/src/components/widgets/inputs/CurveInput.svelte +++ b/frontend/src/components/widgets/inputs/CurveInput.svelte @@ -1,7 +1,7 @@ diff --git a/frontend/src/components/widgets/inputs/FieldInput.svelte b/frontend/src/components/widgets/inputs/FieldInput.svelte index 4d55656b..7ae945cd 100644 --- a/frontend/src/components/widgets/inputs/FieldInput.svelte +++ b/frontend/src/components/widgets/inputs/FieldInput.svelte @@ -1,7 +1,7 @@ - - diff --git a/frontend/src/components/widgets/labels/TextLabel.svelte b/frontend/src/components/widgets/labels/TextLabel.svelte index d26c0dd7..e0ac6c2e 100644 --- a/frontend/src/components/widgets/labels/TextLabel.svelte +++ b/frontend/src/components/widgets/labels/TextLabel.svelte @@ -1,7 +1,7 @@ - -
- +