diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4985abfe20..51a8feed24 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 3b8fb54eeb..e4bb6d1a8e 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 d1c8a4ae1d..f9831b6d0b 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 2fd2e3d2b0..853d170aa2 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 2277419d99..a7bd74b4f0 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 ca81b234ac..b411f461d1 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 fe430ad44a..f9c2bd1df1 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 ca246dfadc..03ecadce77 100644 --- a/desktop/wrapper/src/utils.rs +++ b/desktop/wrapper/src/utils.rs @@ -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 diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 4472507149..4719d5430a 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 4088322347..6c312ed6a2 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 659b860f16..0000000000 --- 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 2c59945136..df7382343f 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 fd7b05f630..3c4d4ad656 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 5eb151d11a..a99962d269 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 a1fd90583b..e16a2120f6 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 dd114bc713..e4d50a86d3 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 f9817dcf37..8dae9ceb7b 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 b02cb5c3d9..262250d75c 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 9e9f56d812..6f8cf66260 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 17856f9b8c..5cf55b4874 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 7b261b5382..7b702c5f1f 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 a47c676acf..e92541a28b 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 ed74893241..3bdf5fc6cb 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 890803bb00..842e185745 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 5126b9ccb7..ba5484a64b 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 81d963f56a..8ab9dfb30c 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 31d2866fd1..979a3d0221 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 e8c9bc533f..b6a2424c52 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 6fba502779..420a3bdd46 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 2470e7a719..ea0bfc0049 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 de3d1ab396..73dfa2ae9d 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 fef1061f27..ac136311d2 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 54a1e5ba1d..cd5f984054 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 c4b8773a09..a1062f9cde 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 deea81e0b1..8a3512ab89 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 b52cc25907..6dae07fc0f 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 cc24a00afc..385b2b455d 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 b0b9bacacb..b767b60305 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 76fbfa4828..0f2739789a 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 ed7f440839..06c64faa64 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 6945296c25..b7f6992fc5 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 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 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 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 09677f3364..efa508b3ff 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 620481a2c1..67ac7a1197 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 899fd767c9..f3683c24da 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(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 { - widgets: vec![ - TextLabel::new("Origin").table_align(true).widget_instance(), + match grid.grid_type { + 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(grid.origin.x)) + NumberInput::new(Some(spacing.x)) .label("X") .unit(" px") + .min(0.) .min_width(98) - .on_update(update_origin(grid, |grid| Some(&mut grid.origin.x))) + .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(grid.origin.y)) + NumberInput::new(Some(spacing.y)) .label("Y") .unit(" px") + .min(0.) .min_width(98) - .on_update(update_origin(grid, |grid| Some(&mut grid.origin.y))) + .on_update(update_origin(grid, |grid| grid.grid_type.rectangular_spacing().map(|spacing| &mut spacing.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::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 5f854a7796..9991d39c57 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 3c249dcbf2..c03ba387d3 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 ef4b00ae7c..12d9b54654 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 bc7b838eeb..328477a3c7 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 ef363ae108..a41e0ca957 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 5a1722d53d..ac95dfce84 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 3f4e56715e..bfa94ee2e5 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 7aad6977db..439962ad98 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 e73eb4ef98..d45f832f82 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 9fb3b4cd16..8431934b4e 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 dabcb5b7e4..2cada9cced 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 5b65c8c3a6..c9ac746768 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 7ba3e7c760..072a743545 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 46baf0f1d4..6e1d3f22dc 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 e257dc359c..0df694050e 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 d1b84b49a4..5319e21aa4 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 eddb7cb863..c18abc1362 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 9a069683be..c1a2a8fe75 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 b3812b87cd..f6fc27ed09 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 1a3b11a545..8f16b2a226 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 8809ccdebd..22021a3792 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 9b46386e6e..80f0a6faa4 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 ceab12bad3..b83bda1817 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 61012e6a0c..2ddff084e5 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 43895d0d70..8e0ac4bca9 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 345b5a058e..b5ef6c6b7a 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 c85b94a4aa..c2a35bb6cf 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 ead8c52214..c54f3c1cd3 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 a060a14efa..8a3196119b 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 175540b05b..ce07aead89 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 60b04c13e3..4c7b196d72 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 3a75c83380..c285691ff4 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](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. - -## npm ecosystem packages: `package.json` +## ESLint configuration: `eslint.config.js` -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. +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. -## npm package installed versions: `package-lock.json` +## Svelte configuration: `svelte.config.js` -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. +Configures the Svelte compiler, including the preprocessor setup for SCSS and TypeScript support, and compiler warning filters. -## TypeScript configurations: `tsconfig.json` +## TypeScript configuration: `tsconfig.json` Basic configuration options for the TypeScript build tool to do its job in our repository. -## Vite configurations: `vite.config.ts` +## 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. 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. + diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index 5cce2a0d4e..1680a24038 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 1c7d063cd8..8026661605 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 fc70d5f72c..8d3da8df7d 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 8e103c5464..6e98a39bc5 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 6206153ed5..7195b05372 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 307f215ac1..0f14408298 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 bcf02e639d..6669aaba17 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 ac6ccb3180..8dee3b7881 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 bde25b57df..aeb196f736 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 c71a562c45..0314eb3114 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 009d649b11..ffb1dc1bfe 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 60b0255c0b..3c70b74681 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 1e0b401de8..7b606f4394 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 7d994ccbd9..9fc746ca2b 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 4d55656bf5..7ae945cd53 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 d26c0dd79e..e0ac6c2e4b 100644 --- a/frontend/src/components/widgets/labels/TextLabel.svelte +++ b/frontend/src/components/widgets/labels/TextLabel.svelte @@ -1,7 +1,7 @@ - -
- +