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

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

* Adopt the generated FillColor/Color/GradientStops

* Fix widget typing

* Separate WidgetGroup enum variants into wrapper structs

* Small rename

* Simplify widgets further

* Clean up message type references

* Switch type imports to the auto-generated file

* Remove lowercase serde rename

* Fix FillChoice deserialization

* Fix small regression from #3837

* Improve type safety

* Make WidgetSpan type-safe

* More cleanup and type safety

* More type safety

* More type safety

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

* Cargo fmt

* Fix imports

* Update outdated readme info

* Fix lint command rename references

* Fix typos

* One more typos fix

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

* Remove excess parts from Cargo.toml

* Fix compiling on desktop

* Revert "Remove excess parts from Cargo.toml"

This reverts commit 6b711117b3a5d5d8a3ee20f36a43bc74930b7c82.

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