Code cleanup and refactoring to enhance consistency (#1695)

- Move message handler payload data into structs
- Organize the file structure used by `editor/src/messages/portfolio/document` `/node_graph` and `/graph_operation`
- Make derive attributes use `serde::Serialize, serde::Deserialize` consistently instead of `use serde::{Deserialize, Serialize};` imports
- Various other code cleanup and refactoring
This commit is contained in:
Keavon Chambers 2024-03-20 21:28:51 -07:00 committed by GitHub
parent ed3f7acdd7
commit 0a9bd41be1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
134 changed files with 1860 additions and 1865 deletions

View file

@ -52,20 +52,22 @@ pub fn commit_info_localized(localized_commit_date: &str) -> String {
#[cfg(test)]
mod test {
use crate::messages::{input_mapper::utility_types::input_mouse::ViewportBounds, prelude::*};
use crate::messages::input_mapper::utility_types::input_mouse::ViewportBounds;
use crate::messages::prelude::*;
// TODO: Fix and reenable
#[ignore]
#[test]
fn debug_ub() {
use super::Message;
let mut editor = super::Editor::new();
let mut responses = Vec::new();
use super::Message::*;
let messages: Vec<Message> = vec![
Init,
Preferences(PreferencesMessage::Load {
preferences: r#"{"imaginate_server_hostname":"https://exchange-encoding-watched-insured.trycloudflare.com/","imaginate_refresh_frequency":1,"zoom_with_scroll":false}"#.to_string(),
Message::Init,
Message::Preferences(PreferencesMessage::Load {
preferences: r#"{ "imaginate_server_hostname": "http://localhost:7860/", "imaginate_refresh_frequency": 1, "zoom_with_scroll": false }"#.to_string(),
}),
PortfolioMessage::OpenDocumentFileWithId {
document_id: DocumentId(0),

View file

@ -1,6 +1,6 @@
use crate::consts::{DEFAULT_FONT_FAMILY, DEFAULT_FONT_STYLE};
use crate::messages::debug::utility_types::MessageLoggingVerbosity;
use crate::messages::dialog::DialogData;
use crate::messages::dialog::DialogMessageData;
use crate::messages::prelude::*;
use graphene_core::text::Font;
@ -58,8 +58,6 @@ impl Dispatcher {
}
pub fn handle_message<T: Into<Message>>(&mut self, message: T) {
use Message::*;
self.message_queues.push(VecDeque::from_iter([message.into()]));
while let Some(message) = self.message_queues.last_mut().and_then(VecDeque::pop_front) {
@ -86,8 +84,8 @@ impl Dispatcher {
// Process the action by forwarding it to the relevant message handler, or saving the FrontendMessage to be sent to the frontend
match message {
NoOp => {}
Init => {
Message::NoOp => {}
Message::Init => {
// Load persistent data from the browser database
queue.add(FrontendMessage::TriggerLoadAutoSaveDocuments);
queue.add(FrontendMessage::TriggerLoadPreferences);
@ -100,18 +98,18 @@ impl Dispatcher {
queue.add(FrontendMessage::TriggerFontLoad { font, is_default: true });
}
Broadcast(message) => self.message_handlers.broadcast_message_handler.process_message(message, &mut queue, ()),
Debug(message) => {
Message::Broadcast(message) => self.message_handlers.broadcast_message_handler.process_message(message, &mut queue, ()),
Message::Debug(message) => {
self.message_handlers.debug_message_handler.process_message(message, &mut queue, ());
}
Dialog(message) => {
let data = DialogData {
Message::Dialog(message) => {
let data = DialogMessageData {
portfolio: &self.message_handlers.portfolio_message_handler,
preferences: &self.message_handlers.preferences_message_handler,
};
self.message_handlers.dialog_message_handler.process_message(message, &mut queue, data);
}
Frontend(message) => {
Message::Frontend(message) => {
// Handle these messages immediately by returning early
if let FrontendMessage::TriggerFontLoad { .. } | FrontendMessage::TriggerRefreshBoundsOfViewports = message {
self.responses.push(message);
@ -124,54 +122,56 @@ impl Dispatcher {
self.responses.push(message);
}
}
Globals(message) => {
Message::Globals(message) => {
self.message_handlers.globals_message_handler.process_message(message, &mut queue, ());
}
InputPreprocessor(message) => {
Message::InputPreprocessor(message) => {
let keyboard_platform = GLOBAL_PLATFORM.get().copied().unwrap_or_default().as_keyboard_platform_layout();
self.message_handlers.input_preprocessor_message_handler.process_message(message, &mut queue, keyboard_platform);
self.message_handlers
.input_preprocessor_message_handler
.process_message(message, &mut queue, InputPreprocessorMessageData { keyboard_platform });
}
KeyMapping(message) => {
Message::KeyMapping(message) => {
let input = &self.message_handlers.input_preprocessor_message_handler;
let actions = self.collect_actions();
self.message_handlers
.key_mapping_message_handler
.process_message(message, &mut queue, (&self.message_handlers.input_preprocessor_message_handler, actions));
.process_message(message, &mut queue, KeyMappingMessageData { input, actions });
}
Layout(message) => {
Message::Layout(message) => {
let action_input_mapping = &|action_to_find: &MessageDiscriminant| self.message_handlers.key_mapping_message_handler.action_input_mapping(action_to_find);
self.message_handlers.layout_message_handler.process_message(message, &mut queue, action_input_mapping);
}
Portfolio(message) => {
self.message_handlers.portfolio_message_handler.process_message(
message,
&mut queue,
(&self.message_handlers.input_preprocessor_message_handler, &self.message_handlers.preferences_message_handler),
);
Message::Portfolio(message) => {
let ipp = &self.message_handlers.input_preprocessor_message_handler;
let preferences = &self.message_handlers.preferences_message_handler;
self.message_handlers
.portfolio_message_handler
.process_message(message, &mut queue, PortfolioMessageData { ipp, preferences });
}
Preferences(message) => {
Message::Preferences(message) => {
self.message_handlers.preferences_message_handler.process_message(message, &mut queue, ());
}
Tool(message) => {
Message::Tool(message) => {
if let Some(document) = self.message_handlers.portfolio_message_handler.active_document() {
self.message_handlers.tool_message_handler.process_message(
message,
&mut queue,
(
document,
self.message_handlers.portfolio_message_handler.active_document_id().unwrap(),
&self.message_handlers.input_preprocessor_message_handler,
&self.message_handlers.portfolio_message_handler.persistent_data,
&self.message_handlers.portfolio_message_handler.executor,
),
);
let data = ToolMessageData {
document_id: self.message_handlers.portfolio_message_handler.active_document_id().unwrap(),
document: document,
input: &self.message_handlers.input_preprocessor_message_handler,
persistent_data: &self.message_handlers.portfolio_message_handler.persistent_data,
node_graph: &self.message_handlers.portfolio_message_handler.executor,
};
self.message_handlers.tool_message_handler.process_message(message, &mut queue, data);
} else {
warn!("Called ToolMessage without an active document.\nGot {message:?}");
}
}
Workspace(message) => {
Message::Workspace(message) => {
self.message_handlers.workspace_message_handler.process_message(message, &mut queue, ());
}
}

View file

@ -1,8 +1,6 @@
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, Hash)]
#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize, Hash)]
#[impl_message(Message, BroadcastMessage, TriggerEvent)]
pub enum BroadcastEvent {
AnimationFrame,

View file

@ -1,9 +1,7 @@
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
#[impl_message(Message, Broadcast)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum BroadcastMessage {
// Sub-messages
#[child]

View file

@ -1,9 +1,7 @@
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
#[impl_message(Message, Debug)]
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]
pub enum DebugMessage {
ToggleTraceLogs,
MessageOff,

View file

@ -1,9 +1,7 @@
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
#[impl_message(Message, Dialog)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum DialogMessage {
// Sub-messages
#[child]

View file

@ -3,6 +3,11 @@ use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name;
pub struct DialogMessageData<'a> {
pub portfolio: &'a PortfolioMessageHandler,
pub preferences: &'a PreferencesMessageHandler,
}
/// Stores the dialogs which require state. These are the ones that have their own message handlers, and are not the ones defined in `simple_dialogs`.
#[derive(Debug, Default, Clone)]
pub struct DialogMessageHandler {
@ -11,17 +16,14 @@ pub struct DialogMessageHandler {
preferences_dialog: PreferencesDialogMessageHandler,
}
pub struct DialogData<'a> {
pub portfolio: &'a PortfolioMessageHandler,
pub preferences: &'a PreferencesMessageHandler,
}
impl MessageHandler<DialogMessage, DialogMessageData<'_>> for DialogMessageHandler {
fn process_message(&mut self, message: DialogMessage, responses: &mut VecDeque<Message>, data: DialogMessageData) {
let DialogMessageData { portfolio, preferences } = data;
impl MessageHandler<DialogMessage, DialogData<'_>> for DialogMessageHandler {
fn process_message(&mut self, message: DialogMessage, responses: &mut VecDeque<Message>, DialogData { portfolio, preferences }: DialogData) {
match message {
DialogMessage::ExportDialog(message) => self.export_dialog.process_message(message, responses, portfolio),
DialogMessage::ExportDialog(message) => self.export_dialog.process_message(message, responses, ExportDialogMessageData { portfolio }),
DialogMessage::NewDocumentDialog(message) => self.new_document_dialog.process_message(message, responses, ()),
DialogMessage::PreferencesDialog(message) => self.preferences_dialog.process_message(message, responses, preferences),
DialogMessage::PreferencesDialog(message) => self.preferences_dialog.process_message(message, responses, PreferencesDialogMessageData { preferences }),
DialogMessage::CloseAllDocumentsWithConfirmation => {
let dialog = simple_dialogs::CloseAllDocumentsDialog {

View file

@ -1,10 +1,8 @@
use crate::messages::frontend::utility_types::{ExportBounds, FileType};
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
#[impl_message(Message, DialogMessage, ExportDialog)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum ExportDialogMessage {
FileType(FileType),
ScaleFactor(f64),

View file

@ -3,6 +3,10 @@ use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::prelude::*;
pub struct ExportDialogMessageData<'a> {
pub portfolio: &'a PortfolioMessageHandler,
}
/// A dialog to allow users to customize their file export.
#[derive(Debug, Clone, Default)]
pub struct ExportDialogMessageHandler {
@ -14,8 +18,10 @@ pub struct ExportDialogMessageHandler {
pub has_selection: bool,
}
impl MessageHandler<ExportDialogMessage, &PortfolioMessageHandler> for ExportDialogMessageHandler {
fn process_message(&mut self, message: ExportDialogMessage, responses: &mut VecDeque<Message>, portfolio: &PortfolioMessageHandler) {
impl MessageHandler<ExportDialogMessage, ExportDialogMessageData<'_>> for ExportDialogMessageHandler {
fn process_message(&mut self, message: ExportDialogMessage, responses: &mut VecDeque<Message>, data: ExportDialogMessageData) {
let ExportDialogMessageData { portfolio } = data;
match message {
ExportDialogMessage::FileType(export_type) => self.file_type = export_type,
ExportDialogMessage::ScaleFactor(factor) => self.scale_factor = factor,

View file

@ -4,4 +4,4 @@ mod export_dialog_message_handler;
#[doc(inline)]
pub use export_dialog_message::{ExportDialogMessage, ExportDialogMessageDiscriminant};
#[doc(inline)]
pub use export_dialog_message_handler::ExportDialogMessageHandler;
pub use export_dialog_message_handler::{ExportDialogMessageData, ExportDialogMessageHandler};

View file

@ -16,4 +16,4 @@ pub mod simple_dialogs;
#[doc(inline)]
pub use dialog_message::{DialogMessage, DialogMessageDiscriminant};
#[doc(inline)]
pub use dialog_message_handler::*;
pub use dialog_message_handler::{DialogMessageData, DialogMessageHandler};

View file

@ -1,9 +1,7 @@
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
#[impl_message(Message, DialogMessage, NewDocumentDialog)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum NewDocumentDialogMessage {
Name(String),
Infinite(bool),

View file

@ -21,7 +21,6 @@ impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHa
NewDocumentDialogMessage::Infinite(infinite) => self.infinite = infinite,
NewDocumentDialogMessage::DimensionsX(x) => self.dimensions.x = x as u32,
NewDocumentDialogMessage::DimensionsY(y) => self.dimensions.y = y as u32,
NewDocumentDialogMessage::Submit => {
responses.add(PortfolioMessage::NewDocumentWithName { name: self.name.clone() });

View file

@ -4,4 +4,4 @@ mod preferences_dialog_message_handler;
#[doc(inline)]
pub use preferences_dialog_message::{PreferencesDialogMessage, PreferencesDialogMessageDiscriminant};
#[doc(inline)]
pub use preferences_dialog_message_handler::PreferencesDialogMessageHandler;
pub use preferences_dialog_message_handler::{PreferencesDialogMessageData, PreferencesDialogMessageHandler};

View file

@ -1,9 +1,7 @@
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
#[impl_message(Message, DialogMessage, PreferencesDialog)]
#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum PreferencesDialogMessage {
Confirm,
}

View file

@ -1,12 +1,18 @@
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
pub struct PreferencesDialogMessageData<'a> {
pub preferences: &'a PreferencesMessageHandler,
}
/// A dialog to allow users to customize Graphite editor options
#[derive(Debug, Clone, Default)]
pub struct PreferencesDialogMessageHandler {}
impl MessageHandler<PreferencesDialogMessage, &PreferencesMessageHandler> for PreferencesDialogMessageHandler {
fn process_message(&mut self, message: PreferencesDialogMessage, responses: &mut VecDeque<Message>, preferences: &PreferencesMessageHandler) {
impl MessageHandler<PreferencesDialogMessage, PreferencesDialogMessageData<'_>> for PreferencesDialogMessageHandler {
fn process_message(&mut self, message: PreferencesDialogMessage, responses: &mut VecDeque<Message>, data: PreferencesDialogMessageData) {
let PreferencesDialogMessageData { preferences } = data;
match message {
PreferencesDialogMessage::Confirm => {}
}

View file

@ -1,6 +1,6 @@
use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::{FrontendNode, FrontendNodeLink, FrontendNodeType};
use crate::messages::portfolio::document::node_graph::utility_types::{FrontendNode, FrontendNodeLink, FrontendNodeType};
use crate::messages::portfolio::document::utility_types::nodes::{JsRawBuffer, LayerPanelEntry, RawBuffer};
use crate::messages::prelude::*;
use crate::messages::tool::utility_types::HintData;
@ -9,10 +9,8 @@ use graph_craft::document::NodeId;
use graphene_core::raster::color::Color;
use graphene_core::text::Font;
use serde::{Deserialize, Serialize};
#[impl_message(Message, Frontend)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum FrontendMessage {
// Display prefix: make the frontend show something, like a dialog
DisplayDialog {

View file

@ -1,9 +1,7 @@
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendDocumentDetails {
#[serde(rename = "isAutoSaved")]
pub is_auto_saved: bool,
@ -13,7 +11,7 @@ pub struct FrontendDocumentDetails {
pub id: DocumentId,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize, specta::Type)]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum MouseCursorIcon {
#[default]
Default,
@ -31,7 +29,7 @@ pub enum MouseCursorIcon {
Rotate,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize, specta::Type)]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum FileType {
#[default]
Png,
@ -49,7 +47,7 @@ impl FileType {
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize, specta::Type)]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum ExportBounds {
#[default]
AllArtwork,

View file

@ -1,10 +1,8 @@
use crate::messages::portfolio::utility_types::Platform;
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
#[impl_message(Message, Globals)]
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum GlobalsMessage {
SetPlatform { platform: Platform },
}

View file

@ -40,9 +40,9 @@ pub fn default_mapping() -> Mapping {
refresh_keys=[Control],
action_dispatch=NavigationMessage::PointerMove { snap_angle: Control, wait_for_snap_angle_release: true, snap_zoom: Control, zoom_from_viewport: None },
),
entry!(KeyDown(Lmb); action_dispatch=NavigationMessage::TransformFromMenuEnd { commit_key: Key::Lmb }),
entry!(KeyDown(Mmb); action_dispatch=NavigationMessage::TransformFromMenuEnd { commit_key: Key::Mmb }),
entry!(KeyDown(Rmb); action_dispatch=NavigationMessage::TransformFromMenuEnd { commit_key: Key::Rmb }),
entry!(KeyDown(Lmb); action_dispatch=NavigationMessage::TransformFromMenuEnd { commit_key: Lmb }),
entry!(KeyDown(Mmb); action_dispatch=NavigationMessage::TransformFromMenuEnd { commit_key: Mmb }),
entry!(KeyDown(Rmb); action_dispatch=NavigationMessage::TransformFromMenuEnd { commit_key: Rmb }),
// ===============
// NORMAL PRIORITY
// ===============

View file

@ -1,10 +1,9 @@
use crate::messages::input_mapper::utility_types::{input_keyboard::Key, input_mouse::MouseButton};
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
use crate::messages::input_mapper::utility_types::input_mouse::MouseButton;
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
#[impl_message(Message, KeyMappingMessage, Lookup)]
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]
pub enum InputMapperMessage {
// Sub-messages
#[child]

View file

@ -6,13 +6,20 @@ use crate::messages::prelude::*;
use std::fmt::Write;
pub struct InputMapperMessageData<'a> {
pub input: &'a InputPreprocessorMessageHandler,
pub actions: ActionList,
}
#[derive(Debug, Default)]
pub struct InputMapperMessageHandler {
mapping: Mapping,
}
impl MessageHandler<InputMapperMessage, (&InputPreprocessorMessageHandler, ActionList)> for InputMapperMessageHandler {
fn process_message(&mut self, message: InputMapperMessage, responses: &mut VecDeque<Message>, (input, actions): (&InputPreprocessorMessageHandler, ActionList)) {
impl MessageHandler<InputMapperMessage, InputMapperMessageData<'_>> for InputMapperMessageHandler {
fn process_message(&mut self, message: InputMapperMessage, responses: &mut VecDeque<Message>, data: InputMapperMessageData) {
let InputMapperMessageData { input, actions } = data;
if let Some(message) = self.mapping.match_input_message(message, &input.keyboard, actions) {
responses.add(message);
}

View file

@ -1,9 +1,7 @@
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
#[impl_message(Message, KeyMapping)]
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]
pub enum KeyMappingMessage {
#[child]
Lookup(InputMapperMessage),
@ -12,7 +10,7 @@ pub enum KeyMappingMessage {
}
#[impl_message(Message, KeyMappingMessage, ModifyMapping)]
#[derive(PartialEq, Eq, Clone, Debug, Default, Hash, Serialize, Deserialize)]
#[derive(PartialEq, Eq, Clone, Debug, Default, Hash, serde::Serialize, serde::Deserialize)]
pub enum MappingVariant {
#[default]
Default,

View file

@ -1,15 +1,23 @@
use crate::messages::input_mapper::input_mapper_message_handler::InputMapperMessageData;
use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup;
use crate::messages::prelude::*;
pub struct KeyMappingMessageData<'a> {
pub input: &'a InputPreprocessorMessageHandler,
pub actions: ActionList,
}
#[derive(Debug, Default)]
pub struct KeyMappingMessageHandler {
mapping_handler: InputMapperMessageHandler,
}
impl MessageHandler<KeyMappingMessage, (&InputPreprocessorMessageHandler, ActionList)> for KeyMappingMessageHandler {
fn process_message(&mut self, message: KeyMappingMessage, responses: &mut VecDeque<Message>, data: (&InputPreprocessorMessageHandler, ActionList)) {
impl MessageHandler<KeyMappingMessage, KeyMappingMessageData<'_>> for KeyMappingMessageHandler {
fn process_message(&mut self, message: KeyMappingMessage, responses: &mut VecDeque<Message>, data: KeyMappingMessageData) {
let KeyMappingMessageData { input, actions } = data;
match message {
KeyMappingMessage::Lookup(input) => self.mapping_handler.process_message(input, responses, data),
KeyMappingMessage::Lookup(input_message) => self.mapping_handler.process_message(input_message, responses, InputMapperMessageData { input, actions }),
KeyMappingMessage::ModifyMapping(new_layout) => self.mapping_handler.set_mapping(new_layout.into()),
}
}

View file

@ -4,4 +4,4 @@ mod key_mapping_message_handler;
#[doc(inline)]
pub use key_mapping_message::{KeyMappingMessage, KeyMappingMessageDiscriminant, MappingVariant, MappingVariantDiscriminant};
#[doc(inline)]
pub use key_mapping_message_handler::KeyMappingMessageHandler;
pub use key_mapping_message_handler::{KeyMappingMessageData, KeyMappingMessageHandler};

View file

@ -8,4 +8,4 @@ pub mod utility_types;
#[doc(inline)]
pub use input_mapper_message::{InputMapperMessage, InputMapperMessageDiscriminant};
#[doc(inline)]
pub use input_mapper_message_handler::InputMapperMessageHandler;
pub use input_mapper_message_handler::{InputMapperMessageData, InputMapperMessageHandler};

View file

@ -2,7 +2,7 @@ use crate::messages::portfolio::utility_types::KeyboardPlatformLayout;
use crate::messages::prelude::*;
use bitflags::bitflags;
use serde::{Deserialize, Serialize};
use std::fmt::{self, Display, Formatter};
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign};
@ -31,7 +31,7 @@ pub enum KeyPosition {
}
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
#[repr(transparent)]
#[serde(transparent)]
pub struct ModifierKeys: u8 {
@ -49,7 +49,7 @@ 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, Deserialize, Serialize, specta::Type, num_enum::TryFromPrimitive)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, specta::Type, num_enum::TryFromPrimitive)]
#[repr(u8)]
pub enum Key {
// Writing system keys
@ -305,7 +305,7 @@ impl From<Key> for LayoutKey {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, specta::Type)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
struct LayoutKey {
key: String,
label: String,
@ -328,7 +328,7 @@ impl Serialize for Key {
pub const NUMBER_OF_KEYS: usize = Key::NumKeys as usize;
/// Only `Key`s that exist on a physical keyboard should be used.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct KeysGroup(pub Vec<Key>);
impl fmt::Display for KeysGroup {
@ -366,7 +366,7 @@ impl From<KeysGroup> for String {
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, specta::Type)]
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct LayoutKeysGroup(Vec<LayoutKey>);
impl From<KeysGroup> for LayoutKeysGroup {
@ -375,7 +375,7 @@ impl From<KeysGroup> for LayoutKeysGroup {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, specta::Type)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum MouseMotion {
None,
Lmb,

View file

@ -3,14 +3,14 @@ use crate::messages::prelude::*;
use bitflags::bitflags;
use glam::DVec2;
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
// Origin is top left
pub type ViewportPosition = DVec2;
pub type EditorPosition = DVec2;
#[derive(PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct ViewportBounds {
pub top_left: DVec2,
pub bottom_right: DVec2,
@ -37,7 +37,7 @@ impl ViewportBounds {
}
}
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub struct ScrollDelta {
// TODO: Switch these to `f64` values (not trivial because floats don't provide PartialEq, Eq, and Hash)
pub x: i32,
@ -60,7 +60,7 @@ impl ScrollDelta {
}
}
#[derive(Debug, Copy, Clone, Default, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Copy, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct MouseState {
pub position: ViewportPosition,
pub mouse_keys: MouseKeys,
@ -98,7 +98,7 @@ impl MouseState {
}
}
#[derive(Debug, Copy, Clone, Default, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Copy, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct EditorMouseState {
pub editor_position: EditorPosition,
pub mouse_keys: MouseKeys,
@ -138,7 +138,7 @@ impl EditorMouseState {
}
bitflags! {
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(transparent)]
pub struct MouseKeys: u8 {
const LEFT = 0b0000_0001;
@ -148,7 +148,7 @@ bitflags! {
}
#[impl_message(Message, InputMapperMessage, DoubleClick)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize, specta::Type, num_enum::TryFromPrimitive)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, specta::Type, num_enum::TryFromPrimitive)]
#[repr(u8)]
pub enum MouseButton {
Left,

View file

@ -5,7 +5,6 @@ use crate::messages::input_mapper::utility_types::input_mouse::NUMBER_OF_MOUSE_B
use crate::messages::prelude::*;
use core::time::Duration;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
pub struct Mapping {
@ -118,7 +117,7 @@ pub struct MappingEntry {
pub modifiers: KeyStates,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, specta::Type)]
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum ActionKeys {
Action(MessageDiscriminant),
#[serde(rename = "keys")]
@ -148,7 +147,7 @@ impl ActionKeys {
}
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct FrameTimeInfo {
timestamp: Duration,
prev_timestamp: Option<Duration>,

View file

@ -3,10 +3,9 @@ use crate::messages::input_mapper::utility_types::input_mouse::{EditorMouseState
use crate::messages::prelude::*;
use core::time::Duration;
use serde::{Deserialize, Serialize};
#[impl_message(Message, InputPreprocessor)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum InputPreprocessorMessage {
BoundsOfViewports { bounds_of_viewports: Vec<ViewportBounds> },
DoubleClick { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },

View file

@ -6,6 +6,10 @@ use crate::messages::prelude::*;
use glam::DVec2;
pub struct InputPreprocessorMessageData {
pub keyboard_platform: KeyboardPlatformLayout,
}
#[derive(Debug, Default)]
pub struct InputPreprocessorMessageHandler {
pub frame_time: FrameTimeInfo,
@ -14,8 +18,10 @@ pub struct InputPreprocessorMessageHandler {
pub viewport_bounds: ViewportBounds,
}
impl MessageHandler<InputPreprocessorMessage, KeyboardPlatformLayout> for InputPreprocessorMessageHandler {
fn process_message(&mut self, message: InputPreprocessorMessage, responses: &mut VecDeque<Message>, keyboard_platform: KeyboardPlatformLayout) {
impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageData> for InputPreprocessorMessageHandler {
fn process_message(&mut self, message: InputPreprocessorMessage, responses: &mut VecDeque<Message>, data: InputPreprocessorMessageData) {
let InputPreprocessorMessageData { keyboard_platform } = data;
match message {
InputPreprocessorMessage::BoundsOfViewports { bounds_of_viewports } => {
assert_eq!(bounds_of_viewports.len(), 1, "Only one viewport is currently supported");
@ -189,7 +195,10 @@ mod test {
let mut responses = VecDeque::new();
input_preprocessor.process_message(message, &mut responses, KeyboardPlatformLayout::Standard);
let data = InputPreprocessorMessageData {
keyboard_platform: KeyboardPlatformLayout::Standard,
};
input_preprocessor.process_message(message, &mut responses, data);
assert!(input_preprocessor.keyboard.get(Key::Alt as usize));
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::Alt).into()));
@ -205,7 +214,10 @@ mod test {
let mut responses = VecDeque::new();
input_preprocessor.process_message(message, &mut responses, KeyboardPlatformLayout::Standard);
let data = InputPreprocessorMessageData {
keyboard_platform: KeyboardPlatformLayout::Standard,
};
input_preprocessor.process_message(message, &mut responses, data);
assert!(input_preprocessor.keyboard.get(Key::Control as usize));
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::Control).into()));
@ -221,7 +233,10 @@ mod test {
let mut responses = VecDeque::new();
input_preprocessor.process_message(message, &mut responses, KeyboardPlatformLayout::Standard);
let data = InputPreprocessorMessageData {
keyboard_platform: KeyboardPlatformLayout::Standard,
};
input_preprocessor.process_message(message, &mut responses, data);
assert!(input_preprocessor.keyboard.get(Key::Shift as usize));
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::Shift).into()));
@ -239,7 +254,10 @@ mod test {
let mut responses = VecDeque::new();
input_preprocessor.process_message(message, &mut responses, KeyboardPlatformLayout::Standard);
let data = InputPreprocessorMessageData {
keyboard_platform: KeyboardPlatformLayout::Standard,
};
input_preprocessor.process_message(message, &mut responses, data);
assert!(!input_preprocessor.keyboard.get(Key::Control as usize));
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyUp(Key::Control).into()));
@ -256,7 +274,10 @@ mod test {
let mut responses = VecDeque::new();
input_preprocessor.process_message(message, &mut responses, KeyboardPlatformLayout::Standard);
let data = InputPreprocessorMessageData {
keyboard_platform: KeyboardPlatformLayout::Standard,
};
input_preprocessor.process_message(message, &mut responses, data);
assert!(input_preprocessor.keyboard.get(Key::Control as usize));
assert!(input_preprocessor.keyboard.get(Key::Shift as usize));

View file

@ -4,4 +4,4 @@ mod input_preprocessor_message_handler;
#[doc(inline)]
pub use input_preprocessor_message::{InputPreprocessorMessage, InputPreprocessorMessageDiscriminant};
#[doc(inline)]
pub use input_preprocessor_message_handler::InputPreprocessorMessageHandler;
pub use input_preprocessor_message_handler::{InputPreprocessorMessageData, InputPreprocessorMessageHandler};

View file

@ -1,10 +1,8 @@
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
#[impl_message(Message, Layout)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum LayoutMessage {
ResendActiveWidget {
layout_target: LayoutTarget,

View file

@ -272,9 +272,8 @@ impl LayoutMessageHandler {
impl<F: Fn(&MessageDiscriminant) -> Vec<KeysGroup>> MessageHandler<LayoutMessage, F> for LayoutMessageHandler {
fn process_message(&mut self, message: LayoutMessage, responses: &mut std::collections::VecDeque<Message>, action_input_mapping: F) {
use LayoutMessage::*;
match message {
ResendActiveWidget { layout_target, widget_id } => {
LayoutMessage::ResendActiveWidget { layout_target, widget_id } => {
// Find the updated diff based on the specified layout target
let Some(diff) = (match &self.layouts[layout_target as usize] {
Layout::MenuLayout(_) => return,
@ -289,15 +288,15 @@ impl<F: Fn(&MessageDiscriminant) -> Vec<KeysGroup>> MessageHandler<LayoutMessage
// Resend that diff
self.send_diff(vec![diff], layout_target, responses, &action_input_mapping);
}
SendLayout { layout, layout_target } => {
LayoutMessage::SendLayout { layout, layout_target } => {
self.diff_and_send_layout_to_frontend(layout_target, layout, responses, &action_input_mapping);
}
WidgetValueCommit { layout_target, widget_id, value } => {
LayoutMessage::WidgetValueCommit { layout_target, widget_id, value } => {
self.handle_widget_callback(layout_target, widget_id, value, WidgetValueAction::Commit, responses);
}
WidgetValueUpdate { layout_target, widget_id, value } => {
LayoutMessage::WidgetValueUpdate { layout_target, widget_id, value } => {
self.handle_widget_callback(layout_target, widget_id, value, WidgetValueAction::Update, responses);
responses.add(ResendActiveWidget { layout_target, widget_id });
responses.add(LayoutMessage::ResendActiveWidget { layout_target, widget_id });
}
}
}

View file

@ -7,7 +7,6 @@ use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup;
use crate::messages::input_mapper::utility_types::misc::ActionKeys;
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[repr(transparent)]
@ -20,7 +19,7 @@ impl core::fmt::Display for WidgetId {
}
}
#[derive(PartialEq, Clone, Debug, Hash, Eq, Copy, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, Hash, Eq, Copy, serde::Serialize, serde::Deserialize, specta::Type)]
#[repr(u8)]
pub enum LayoutTarget {
/// Contains the action buttons at the bottom of the dialog. Must be shown with the `FrontendMessage::DisplayDialog` message.
@ -100,7 +99,7 @@ pub trait DialogLayoutHolder: LayoutHolder {
}
/// Wraps a choice of layout type. The chosen layout contains an arrangement of widgets mounted somewhere specific in the frontend.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, specta::Type)]
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum Layout {
WidgetLayout(WidgetLayout),
MenuLayout(MenuLayout),
@ -157,7 +156,7 @@ impl Default for Layout {
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, specta::Type)]
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize, PartialEq, specta::Type)]
pub struct WidgetLayout {
pub layout: SubLayout,
}
@ -290,7 +289,7 @@ impl<'a> Iterator for WidgetIterMut<'a> {
pub type SubLayout = Vec<LayoutGroup>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, specta::Type)]
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum LayoutGroup {
#[serde(rename = "column")]
Column {
@ -420,7 +419,7 @@ impl LayoutGroup {
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, specta::Type)]
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct WidgetHolder {
#[serde(rename = "widgetId")]
pub widget_id: WidgetId,
@ -472,7 +471,7 @@ impl<T> Default for WidgetCallback<T> {
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, specta::Type)]
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum Widget {
BreadcrumbTrailButtons(BreadcrumbTrailButtons),
CheckboxInput(CheckboxInput),
@ -498,7 +497,7 @@ 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, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
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.
@ -513,7 +512,7 @@ pub struct WidgetDiff {
/// The new value of the UI, sent as part of a diff.
///
/// An update can represent a single widget or an entire SubLayout, or just a single layout group.
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum DiffUpdate {
#[serde(rename = "subLayout")]
SubLayout(SubLayout),

View file

@ -1,14 +1,13 @@
use crate::messages::input_mapper::utility_types::misc::ActionKeys;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::FrontendGraphDataType;
use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType;
use graphene_core::raster::color::Color;
use graphite_proc_macros::WidgetBuilder;
use derivative::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Default, Derivative, Serialize, Deserialize, WidgetBuilder, specta::Type)]
#[derive(Clone, Default, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq)]
pub struct IconButton {
#[widget_builder(constructor)]
@ -36,7 +35,7 @@ pub struct IconButton {
pub on_commit: WidgetCallback<()>,
}
#[derive(Clone, Serialize, Deserialize, Derivative, WidgetBuilder, specta::Type)]
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq, Default)]
pub struct PopoverButton {
pub style: Option<String>,
@ -65,7 +64,7 @@ pub struct PopoverButton {
pub tooltip_shortcut: Option<ActionKeys>,
}
#[derive(Clone, Serialize, Deserialize, Derivative, Default, WidgetBuilder, specta::Type)]
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq)]
pub struct ParameterExposeButton {
pub exposed: bool,
@ -88,7 +87,7 @@ pub struct ParameterExposeButton {
pub on_commit: WidgetCallback<()>,
}
#[derive(Clone, Serialize, Deserialize, Derivative, Default, WidgetBuilder, specta::Type)]
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq)]
pub struct TextButton {
#[widget_builder(constructor)]
@ -123,7 +122,7 @@ pub struct TextButton {
pub on_commit: WidgetCallback<()>,
}
#[derive(Clone, Derivative, Serialize, Deserialize, WidgetBuilder, specta::Type)]
#[derive(Clone, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq, Default)]
pub struct ColorButton {
#[widget_builder(constructor)]
@ -156,7 +155,7 @@ pub struct ColorButton {
pub on_commit: WidgetCallback<()>,
}
#[derive(Clone, Serialize, Deserialize, Derivative, Default, WidgetBuilder, specta::Type)]
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq)]
pub struct BreadcrumbTrailButtons {
#[widget_builder(constructor)]

View file

@ -6,9 +6,8 @@ use graphite_proc_macros::WidgetBuilder;
use derivative::*;
use glam::DVec2;
use serde::{Deserialize, Serialize};
#[derive(Clone, Derivative, Serialize, Deserialize, WidgetBuilder, specta::Type)]
#[derive(Clone, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq)]
pub struct CheckboxInput {
#[widget_builder(constructor)]
@ -47,7 +46,7 @@ impl Default for CheckboxInput {
}
}
#[derive(Clone, Serialize, Deserialize, Derivative, WidgetBuilder, specta::Type)]
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq, Default)]
pub struct DropdownInput {
#[widget_builder(constructor)]
@ -76,7 +75,7 @@ pub struct DropdownInput {
pub type MenuListEntrySections = Vec<Vec<MenuListEntry>>;
#[derive(Clone, Serialize, Deserialize, Derivative, Default, WidgetBuilder, specta::Type)]
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq)]
#[widget_builder(not_widget_holder)]
pub struct MenuListEntry {
@ -106,7 +105,7 @@ pub struct MenuListEntry {
pub on_commit: WidgetCallback<()>,
}
#[derive(Clone, Serialize, Deserialize, Derivative, WidgetBuilder, specta::Type)]
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq, Default)]
pub struct FontInput {
#[serde(rename = "fontFamily")]
@ -140,7 +139,7 @@ pub struct FontInput {
/// This widget allows for the flexible use of the layout system.
/// In a custom layout, one can define a widget that is just used to trigger code on the backend.
/// This is used in MenuLayout to pipe the triggering of messages from the frontend to backend.
#[derive(Clone, Serialize, Deserialize, Derivative, Default, WidgetBuilder, specta::Type)]
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq)]
pub struct InvisibleStandinInput {
#[serde(skip)]
@ -152,7 +151,7 @@ pub struct InvisibleStandinInput {
pub on_commit: WidgetCallback<()>,
}
#[derive(Clone, Serialize, Deserialize, Derivative, WidgetBuilder, specta::Type)]
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq, Default)]
pub struct NumberInput {
// Label
@ -259,7 +258,7 @@ impl NumberInput {
}
}
#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq, Eq, specta::Type)]
#[derive(Clone, serde::Serialize, serde::Deserialize, Debug, Default, PartialEq, Eq, specta::Type)]
pub enum NumberInputIncrementBehavior {
#[default]
Add,
@ -267,14 +266,14 @@ pub enum NumberInputIncrementBehavior {
Callback,
}
#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq, Eq, specta::Type)]
#[derive(Clone, serde::Serialize, serde::Deserialize, Debug, Default, PartialEq, Eq, specta::Type)]
pub enum NumberInputMode {
#[default]
Increment,
Range,
}
#[derive(Clone, Default, Derivative, Serialize, Deserialize, WidgetBuilder, specta::Type)]
#[derive(Clone, Default, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq)]
pub struct RadioInput {
#[widget_builder(constructor)]
@ -290,7 +289,7 @@ pub struct RadioInput {
pub min_width: u32,
}
#[derive(Clone, Default, Derivative, Serialize, Deserialize, WidgetBuilder, specta::Type)]
#[derive(Clone, Default, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq)]
#[widget_builder(not_widget_holder)]
pub struct RadioEntryData {
@ -316,7 +315,7 @@ pub struct RadioEntryData {
pub on_commit: WidgetCallback<()>,
}
#[derive(Clone, Serialize, Deserialize, Derivative, WidgetBuilder, specta::Type)]
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq, Default)]
pub struct WorkingColorsInput {
#[widget_builder(constructor)]
@ -326,7 +325,7 @@ pub struct WorkingColorsInput {
pub secondary: Color,
}
#[derive(Clone, Serialize, Deserialize, Derivative, WidgetBuilder, specta::Type)]
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq, Default)]
pub struct TextAreaInput {
#[widget_builder(constructor)]
@ -348,7 +347,7 @@ pub struct TextAreaInput {
pub on_commit: WidgetCallback<()>,
}
#[derive(Clone, Serialize, Deserialize, Derivative, WidgetBuilder, specta::Type)]
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq, Default)]
pub struct TextInput {
#[widget_builder(constructor)]
@ -375,7 +374,7 @@ pub struct TextInput {
pub on_commit: WidgetCallback<()>,
}
#[derive(Clone, Serialize, Deserialize, Derivative, WidgetBuilder, specta::Type)]
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq, Default)]
pub struct CurveInput {
#[widget_builder(constructor)]
@ -395,7 +394,7 @@ pub struct CurveInput {
pub on_commit: WidgetCallback<()>,
}
#[derive(Clone, Default, Derivative, Serialize, Deserialize, WidgetBuilder, specta::Type)]
#[derive(Clone, Default, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq)]
pub struct PivotInput {
#[widget_builder(constructor)]
@ -413,7 +412,7 @@ pub struct PivotInput {
pub on_commit: WidgetCallback<()>,
}
#[derive(Clone, Copy, Serialize, Deserialize, Debug, Default, PartialEq, Eq, specta::Type)]
#[derive(Clone, Copy, serde::Serialize, serde::Deserialize, Debug, Default, PartialEq, Eq, specta::Type)]
pub enum PivotPosition {
#[default]
None,

View file

@ -1,8 +1,7 @@
use derivative::*;
use graphite_proc_macros::WidgetBuilder;
use serde::{Deserialize, Serialize};
#[derive(Clone, Serialize, Deserialize, Derivative, Debug, Default, PartialEq, Eq, WidgetBuilder, specta::Type)]
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Debug, Default, PartialEq, Eq, WidgetBuilder, specta::Type)]
pub struct IconLabel {
#[widget_builder(constructor)]
pub icon: String,
@ -12,7 +11,7 @@ pub struct IconLabel {
pub tooltip: String,
}
#[derive(Clone, Serialize, Deserialize, Derivative, Debug, Default, PartialEq, Eq, WidgetBuilder, specta::Type)]
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Debug, Default, PartialEq, Eq, WidgetBuilder, specta::Type)]
pub struct ImageLabel {
#[widget_builder(constructor)]
pub image: String,
@ -24,7 +23,7 @@ pub struct ImageLabel {
pub tooltip: String,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, WidgetBuilder, specta::Type)]
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, WidgetBuilder, specta::Type)]
pub struct Separator {
pub direction: SeparatorDirection,
@ -33,14 +32,14 @@ pub struct Separator {
pub separator_type: SeparatorType,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, specta::Type)]
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum SeparatorDirection {
#[default]
Horizontal,
Vertical,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, specta::Type)]
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum SeparatorType {
Related,
#[default]
@ -48,7 +47,7 @@ pub enum SeparatorType {
Section,
}
#[derive(Clone, Serialize, Deserialize, Derivative, Debug, PartialEq, Eq, Default, WidgetBuilder, specta::Type)]
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Debug, PartialEq, Eq, Default, WidgetBuilder, specta::Type)]
pub struct TextLabel {
pub disabled: bool,

View file

@ -3,11 +3,9 @@ use crate::messages::input_mapper::utility_types::misc::ActionKeys;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
use super::input_widgets::InvisibleStandinInput;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, specta::Type)]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Default, specta::Type)]
pub struct MenuBarEntryChildren(pub Vec<Vec<MenuBarEntry>>);
impl MenuBarEntryChildren {
@ -29,7 +27,7 @@ impl MenuBarEntryChildren {
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, specta::Type)]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, specta::Type)]
pub struct MenuBarEntry {
pub label: String,
pub icon: Option<String>,
@ -71,7 +69,7 @@ impl Default for MenuBarEntry {
}
}
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize, specta::Type)]
#[derive(Debug, Default, Clone, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct MenuLayout {
pub layout: Vec<MenuBarEntry>,
}

View file

@ -2,10 +2,8 @@ use crate::messages::prelude::*;
use graphite_proc_macros::*;
use serde::{Deserialize, Serialize};
#[impl_message]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum Message {
NoOp,
Init,

View file

@ -11,22 +11,22 @@ use graphene_core::vector::style::ViewMode;
use graphene_core::Color;
use glam::DAffine2;
use serde::{Deserialize, Serialize};
#[impl_message(Message, PortfolioMessage, Document)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum DocumentMessage {
Noop,
// Sub-messages
#[child]
GraphOperation(GraphOperationMessage),
#[child]
Navigation(NavigationMessage),
#[child]
NodeGraph(NodeGraphMessage),
#[child]
Overlays(OverlaysMessage),
#[child]
PropertiesPanel(PropertiesPanelMessage),
#[child]
NodeGraph(NodeGraphMessage),
#[child]
GraphOperation(GraphOperationMessage),
// Messages
AbortTransaction,

View file

@ -5,7 +5,8 @@ use crate::application::{generate_uuid, GRAPHITE_GIT_COMMIT_HASH};
use crate::consts::{ASYMPTOTIC_EFFECT, DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX, SCALE_EFFECT, SCROLLBAR_SPACING};
use crate::messages::input_mapper::utility_types::macros::action_keys;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::{GraphOperationHandlerData, NodeGraphHandlerData};
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::node_graph::NodeGraphHandlerData;
use crate::messages::portfolio::document::overlays::grid_overlays::{grid_overlay, overlay_options};
use crate::messages::portfolio::document::properties_panel::utility_types::PropertiesPanelMessageHandlerData;
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
@ -29,19 +30,26 @@ use graphene_core::{concrete, generic, ProtoNodeIdentifier};
use graphene_std::wasm_application_io::WasmEditorApi;
use glam::{DAffine2, DVec2};
use serde::{Deserialize, Serialize};
use std::vec;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DocumentMessageData<'a> {
pub document_id: DocumentId,
pub ipp: &'a InputPreprocessorMessageHandler,
pub persistent_data: &'a PersistentData,
pub executor: &'a mut NodeGraphExecutor,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct DocumentMessageHandler {
// ======================
// Child message handlers
// ======================
#[serde(skip)]
node_graph_handler: NodeGraphMessageHandler,
#[serde(skip)]
navigation_handler: NavigationMessageHandler,
#[serde(skip)]
node_graph_handler: NodeGraphMessageHandler,
#[serde(skip)]
overlays_message_handler: OverlaysMessageHandler,
#[serde(skip)]
properties_panel_message_handler: PropertiesPanelMessageHandler,
@ -92,172 +100,34 @@ pub struct DocumentMessageHandler {
pub metadata: DocumentMetadata,
}
impl Default for DocumentMessageHandler {
fn default() -> Self {
Self {
// ======================
// Child message handlers
// ======================
node_graph_handler: Default::default(),
navigation_handler: NavigationMessageHandler::default(),
overlays_message_handler: OverlaysMessageHandler::default(),
properties_panel_message_handler: PropertiesPanelMessageHandler,
// ============================================
// Fields that are saved in the document format
// ============================================
network: root_network(),
selected_nodes: SelectedNodes::default(),
collapsed: CollapsedLayers::default(),
name: DEFAULT_DOCUMENT_NAME.to_string(),
commit_hash: GRAPHITE_GIT_COMMIT_HASH.to_string(),
navigation: PTZ::default(),
document_mode: DocumentMode::DesignMode,
view_mode: ViewMode::default(),
overlays_visible: true,
rulers_visible: true,
// =============================================
// Fields omitted from the saved document format
// =============================================
document_undo_history: VecDeque::new(),
document_redo_history: VecDeque::new(),
saved_hash: None,
auto_saved_hash: None,
undo_in_progress: false,
graph_view_overlay_open: false,
snapping_state: SnappingState::default(),
layer_range_selection_reference: None,
metadata: Default::default(),
}
}
}
#[inline(always)]
fn default_network() -> NodeNetwork {
DocumentMessageHandler::default().network
}
#[inline(always)]
fn default_selected_nodes() -> SelectedNodes {
DocumentMessageHandler::default().selected_nodes
}
#[inline(always)]
fn default_collapsed() -> CollapsedLayers {
DocumentMessageHandler::default().collapsed
}
#[inline(always)]
fn default_name() -> String {
DocumentMessageHandler::default().name
}
#[inline(always)]
fn default_commit_hash() -> String {
DocumentMessageHandler::default().commit_hash
}
#[inline(always)]
fn default_pan_tilt_zoom() -> PTZ {
DocumentMessageHandler::default().navigation
}
#[inline(always)]
fn default_document_mode() -> DocumentMode {
DocumentMessageHandler::default().document_mode
}
#[inline(always)]
fn default_view_mode() -> ViewMode {
DocumentMessageHandler::default().view_mode
}
#[inline(always)]
fn default_overlays_visible() -> bool {
DocumentMessageHandler::default().overlays_visible
}
#[inline(always)]
fn default_rulers_visible() -> bool {
DocumentMessageHandler::default().rulers_visible
}
fn root_network() -> NodeNetwork {
{
let mut network = NodeNetwork::default();
let node = graph_craft::document::DocumentNode {
name: "Output".into(),
inputs: vec![NodeInput::value(TaggedValue::GraphicGroup(Default::default()), true), NodeInput::Network(concrete!(WasmEditorApi))],
implementation: graph_craft::document::DocumentNodeImplementation::Network(NodeNetwork {
imports: vec![NodeId(3), NodeId(0)],
exports: vec![NodeOutput::new(NodeId(3), 0)],
nodes: [
DocumentNode {
name: "EditorApi".to_string(),
inputs: vec![NodeInput::Network(concrete!(WasmEditorApi))],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")),
..Default::default()
},
DocumentNode {
name: "Create Canvas".to_string(),
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")),
skip_deduplication: true,
..Default::default()
},
DocumentNode {
name: "Cache".to_string(),
manual_composition: Some(concrete!(())),
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")),
..Default::default()
},
DocumentNode {
name: "RenderNode".to_string(),
inputs: vec![
NodeInput::node(NodeId(0), 0),
NodeInput::Network(graphene_core::Type::Fn(Box::new(concrete!(Footprint)), Box::new(generic!(T)))),
NodeInput::node(NodeId(2), 0),
],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::RenderNode<_, _, _>")),
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
}),
metadata: DocumentNodeMetadata::position((8, 4)),
..Default::default()
};
network.push_node(node);
network
}
}
pub struct DocumentInputs<'a> {
pub document_id: DocumentId,
pub ipp: &'a InputPreprocessorMessageHandler,
pub persistent_data: &'a PersistentData,
pub executor: &'a mut NodeGraphExecutor,
}
impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHandler {
fn process_message(&mut self, message: DocumentMessage, responses: &mut VecDeque<Message>, document_inputs: DocumentInputs) {
let DocumentInputs {
impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessageHandler {
fn process_message(&mut self, message: DocumentMessage, responses: &mut VecDeque<Message>, data: DocumentMessageData) {
let DocumentMessageData {
document_id,
ipp,
persistent_data,
executor,
} = document_inputs;
use DocumentMessage::*;
} = data;
match message {
// Sub-messages
Navigation(message) => {
DocumentMessage::Navigation(message) => {
let document_bounds = self.metadata().document_bounds_viewport_space();
self.navigation_handler.process_message(
message,
responses,
(&self.metadata, document_bounds, ipp, self.selected_visible_layers_bounding_box_viewport(), &mut self.navigation),
);
let data = NavigationMessageData {
metadata: &self.metadata,
document_bounds,
ipp,
selection_bounds: self.selected_visible_layers_bounding_box_viewport(),
ptz: &mut self.navigation,
};
self.navigation_handler.process_message(message, responses, data);
}
Overlays(message) => {
self.overlays_message_handler.process_message(message, responses, (self.overlays_visible, ipp));
DocumentMessage::Overlays(message) => {
let overlays_visible = self.overlays_visible;
self.overlays_message_handler.process_message(message, responses, OverlaysMessageData { overlays_visible, ipp });
}
PropertiesPanel(message) => {
DocumentMessage::PropertiesPanel(message) => {
let properties_panel_message_handler_data = PropertiesPanelMessageHandlerData {
node_graph_message_handler: &self.node_graph_handler,
executor,
@ -269,7 +139,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
self.properties_panel_message_handler
.process_message(message, responses, (persistent_data, properties_panel_message_handler_data));
}
NodeGraph(message) => {
DocumentMessage::NodeGraph(message) => {
self.node_graph_handler.process_message(
message,
responses,
@ -285,26 +155,26 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
},
);
}
GraphOperation(message) => GraphOperationMessageHandler.process_message(
message,
responses,
GraphOperationHandlerData {
DocumentMessage::GraphOperation(message) => {
let data = GraphOperationMessageData {
document_network: &mut self.network,
document_metadata: &mut self.metadata,
selected_nodes: &mut self.selected_nodes,
collapsed: &mut self.collapsed,
node_graph: &mut self.node_graph_handler,
},
),
};
let mut graph_operation_message_handler = GraphOperationMessageHandler {};
graph_operation_message_handler.process_message(message, responses, data);
}
// Messages
AbortTransaction => {
DocumentMessage::AbortTransaction => {
if !self.undo_in_progress {
self.undo(responses);
responses.add(OverlaysMessage::Draw);
}
}
AlignSelectedLayers { axis, aggregate } => {
DocumentMessage::AlignSelectedLayers { axis, aggregate } => {
self.backup(responses);
let axis = match axis {
@ -338,12 +208,12 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
});
}
}
BackupDocument { network } => self.backup_with_document(network, responses),
ClearArtboards => {
DocumentMessage::BackupDocument { network } => self.backup_with_document(network, responses),
DocumentMessage::ClearArtboards => {
self.backup(responses);
responses.add(GraphOperationMessage::ClearArtboards);
}
ClearLayersPanel => {
DocumentMessage::ClearLayersPanel => {
// Send an empty layer list
let data_buffer: RawBuffer = Self::default().serialize_root();
responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer });
@ -354,8 +224,8 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
layout_target: LayoutTarget::LayersPanelOptions,
});
}
CommitTransaction => (),
CreateEmptyFolder => {
DocumentMessage::CommitTransaction => (),
DocumentMessage::CreateEmptyFolder => {
let id = NodeId(generate_uuid());
let parent = self
@ -378,14 +248,14 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
});
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![id] });
}
DebugPrintDocument => {
DocumentMessage::DebugPrintDocument => {
info!("{:#?}", self.network);
}
DeleteLayer { id } => {
DocumentMessage::DeleteLayer { id } => {
responses.add(GraphOperationMessage::DeleteLayer { id });
responses.add_front(BroadcastEvent::ToolAbort);
}
DeleteSelectedLayers => {
DocumentMessage::DeleteSelectedLayers => {
self.backup(responses);
responses.add_front(BroadcastEvent::SelectionChanged);
@ -393,20 +263,20 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
responses.add_front(DocumentMessage::DeleteLayer { id: path.last().unwrap().to_node() });
}
}
DeselectAllLayers => {
DocumentMessage::DeselectAllLayers => {
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
self.layer_range_selection_reference = None;
}
DocumentHistoryBackward => self.undo_with_history(responses),
DocumentHistoryForward => self.redo_with_history(responses),
DocumentStructureChanged => {
DocumentMessage::DocumentHistoryBackward => self.undo_with_history(responses),
DocumentMessage::DocumentHistoryForward => self.redo_with_history(responses),
DocumentMessage::DocumentStructureChanged => {
self.update_layers_panel_options_bar_widgets(responses);
self.metadata.load_structure(&self.network, &mut self.selected_nodes);
let data_buffer: RawBuffer = self.serialize_root();
responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer });
}
DuplicateSelectedLayers => {
DocumentMessage::DuplicateSelectedLayers => {
self.backup(responses);
for layer_ancestors in self.metadata.shallowest_unique_layers(self.selected_nodes.selected_layers(&self.metadata)) {
let Some(layer) = layer_ancestors.last().copied() else { continue };
@ -437,7 +307,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
});
}
}
FlipSelectedLayers { flip_axis } => {
DocumentMessage::FlipSelectedLayers { flip_axis } => {
self.backup(responses);
let scale = match flip_axis {
FlipAxis::X => DVec2::new(-1., 1.),
@ -456,7 +326,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
}
}
}
GraphViewOverlay { open } => {
DocumentMessage::GraphViewOverlay { open } => {
self.graph_view_overlay_open = open;
if open {
@ -464,25 +334,25 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
}
responses.add(FrontendMessage::TriggerGraphViewOverlay { open });
}
GraphViewOverlayToggle => {
DocumentMessage::GraphViewOverlayToggle => {
responses.add(DocumentMessage::GraphViewOverlay { open: !self.graph_view_overlay_open });
}
GridOptions(grid) => {
DocumentMessage::GridOptions(grid) => {
self.snapping_state.grid = grid;
self.snapping_state.grid_snapping = true;
responses.add(OverlaysMessage::Draw);
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
GridOverlays(mut overlay_context) => {
DocumentMessage::GridOverlays(mut overlay_context) => {
if self.snapping_state.grid_snapping {
grid_overlay(self, &mut overlay_context)
}
}
GridVisible(enabled) => {
DocumentMessage::GridVisible(enabled) => {
self.snapping_state.grid_snapping = enabled;
responses.add(OverlaysMessage::Draw);
}
GroupSelectedLayers => {
DocumentMessage::GroupSelectedLayers => {
let parent = self
.metadata()
.deepest_common_ancestor(self.selected_nodes.selected_layers(self.metadata()), true)
@ -521,8 +391,8 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
});
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![folder_id] });
}
ImaginateGenerate => responses.add(PortfolioMessage::SubmitGraphRender { document_id }),
ImaginateRandom { imaginate_node, then_generate } => {
DocumentMessage::ImaginateGenerate => responses.add(PortfolioMessage::SubmitGraphRender { document_id }),
DocumentMessage::ImaginateRandom { imaginate_node, then_generate } => {
// Generate a random seed. We only want values between -2^53 and 2^53, because integer values
// outside of this range can get rounded in f64
let random_bits = generate_uuid();
@ -542,7 +412,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
responses.add(DocumentMessage::ImaginateGenerate);
}
}
ImportSvg {
DocumentMessage::ImportSvg {
id,
svg,
transform,
@ -558,7 +428,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
insert_index,
});
}
MoveSelectedLayersTo { parent, insert_index } => {
DocumentMessage::MoveSelectedLayersTo { parent, insert_index } => {
let selected_layers = self.selected_nodes.selected_layers(self.metadata()).collect::<Vec<_>>();
// Disallow trying to insert into self
@ -576,7 +446,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
insert_index,
});
}
NudgeSelectedLayers {
DocumentMessage::NudgeSelectedLayers {
delta_x,
delta_y,
resize,
@ -628,7 +498,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
};
}
}
PasteImage { image, mouse } => {
DocumentMessage::PasteImage { image, mouse } => {
// All the image's pixels have been converted to 0..=1, linear, and premultiplied by `Color::from_rgba8_srgb`
let image_size = DVec2::new(image.width as f64, image.height as f64);
@ -665,7 +535,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
// Force chosen tool to be Select Tool after importing image.
responses.add(ToolMessage::ActivateTool { tool_type: ToolType::Select });
}
PasteSvg { svg, mouse } => {
DocumentMessage::PasteSvg { svg, mouse } => {
use crate::messages::tool::common_functionality::graph_modification_utils;
let viewport_location = mouse.map_or(ipp.viewport_bounds.center() + ipp.viewport_bounds.top_left, |pos| pos.into());
let center_in_viewport = DAffine2::from_translation(self.metadata().document_to_viewport.inverse().transform_point2(viewport_location - ipp.viewport_bounds.top_left));
@ -673,18 +543,18 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });
responses.add(ToolMessage::ActivateTool { tool_type: ToolType::Select });
}
Redo => {
DocumentMessage::Redo => {
responses.add(SelectToolMessage::Abort);
responses.add(DocumentMessage::DocumentHistoryForward);
responses.add(ToolMessage::Redo);
responses.add(OverlaysMessage::Draw);
}
RenameDocument { new_name } => {
DocumentMessage::RenameDocument { new_name } => {
self.name = new_name;
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
responses.add(NodeGraphMessage::UpdateNewNodeGraph);
}
RenderRulers => {
DocumentMessage::RenderRulers => {
let document_transform_scale = self.navigation_handler.snapped_scale(self.navigation.zoom);
let ruler_origin = self.metadata().document_to_viewport.transform_point2(DVec2::ZERO);
@ -699,7 +569,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
visible: self.rulers_visible,
});
}
RenderScrollbars => {
DocumentMessage::RenderScrollbars => {
let document_transform_scale = self.navigation_handler.snapped_scale(self.navigation.zoom);
let scale = 0.5 + ASYMPTOTIC_EFFECT + document_transform_scale * SCALE_EFFECT;
@ -720,7 +590,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
multiplier: scrollbar_multiplier.into(),
});
}
SaveDocument => {
DocumentMessage::SaveDocument => {
self.set_save_state(true);
responses.add(PortfolioMessage::AutoSaveActiveDocument);
// Update the save status of the just saved document
@ -735,28 +605,28 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
name,
})
}
SelectAllLayers => {
DocumentMessage::SelectAllLayers => {
let metadata = self.metadata();
let all_layers_except_artboards = metadata.all_layers().filter(move |&layer| !metadata.is_artboard(layer));
let nodes = all_layers_except_artboards.map(|layer| layer.to_node()).collect();
responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
}
SelectedLayersLower => {
DocumentMessage::SelectedLayersLower => {
responses.add(DocumentMessage::SelectedLayersReorder { relative_index_offset: 1 });
}
SelectedLayersLowerToBack => {
DocumentMessage::SelectedLayersLowerToBack => {
responses.add(DocumentMessage::SelectedLayersReorder { relative_index_offset: isize::MAX });
}
SelectedLayersRaise => {
DocumentMessage::SelectedLayersRaise => {
responses.add(DocumentMessage::SelectedLayersReorder { relative_index_offset: -1 });
}
SelectedLayersRaiseToFront => {
DocumentMessage::SelectedLayersRaiseToFront => {
responses.add(DocumentMessage::SelectedLayersReorder { relative_index_offset: isize::MIN });
}
SelectedLayersReorder { relative_index_offset } => {
DocumentMessage::SelectedLayersReorder { relative_index_offset } => {
self.selected_layers_reorder(relative_index_offset, responses);
}
SelectLayer { id, ctrl, shift } => {
DocumentMessage::SelectLayer { id, ctrl, shift } => {
let layer = LayerNodeIdentifier::new(id, self.network());
let mut nodes = vec![];
@ -800,13 +670,13 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
}
}
}
SetBlendModeForSelectedLayers { blend_mode } => {
DocumentMessage::SetBlendModeForSelectedLayers { blend_mode } => {
self.backup(responses);
for layer in self.selected_nodes.selected_layers_except_artboards(self.metadata()) {
responses.add(GraphOperationMessage::BlendModeSet { layer, blend_mode });
}
}
SetOpacityForSelectedLayers { opacity } => {
DocumentMessage::SetOpacityForSelectedLayers { opacity } => {
self.backup(responses);
let opacity = opacity.clamp(0., 1.);
@ -814,15 +684,15 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
responses.add(GraphOperationMessage::OpacitySet { layer, opacity });
}
}
SetOverlaysVisibility { visible } => {
DocumentMessage::SetOverlaysVisibility { visible } => {
self.overlays_visible = visible;
responses.add(BroadcastEvent::ToolAbort);
responses.add(OverlaysMessage::Draw);
}
SetRangeSelectionLayer { new_layer } => {
DocumentMessage::SetRangeSelectionLayer { new_layer } => {
self.layer_range_selection_reference = new_layer;
}
SetSnapping {
DocumentMessage::SetSnapping {
snapping_enabled,
bounding_box_snapping,
geometry_snapping,
@ -837,12 +707,12 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
self.snapping_state.geometry_snapping = state
};
}
SetViewMode { view_mode } => {
DocumentMessage::SetViewMode { view_mode } => {
self.view_mode = view_mode;
responses.add_front(NodeGraphMessage::RunDocumentGraph);
}
StartTransaction => self.backup(responses),
ToggleLayerExpansion { id } => {
DocumentMessage::StartTransaction => self.backup(responses),
DocumentMessage::ToggleLayerExpansion { id } => {
let layer = LayerNodeIdentifier::new(id, self.network());
if self.collapsed.0.contains(&layer) {
self.collapsed.0.retain(|&collapsed_layer| collapsed_layer != layer);
@ -851,7 +721,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
}
responses.add(NodeGraphMessage::RunDocumentGraph);
}
Undo => {
DocumentMessage::Undo => {
self.undo_in_progress = true;
responses.add(ToolMessage::PreUndo);
responses.add(DocumentMessage::DocumentHistoryBackward);
@ -859,8 +729,8 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
responses.add(DocumentMessage::UndoFinished);
responses.add(ToolMessage::Undo);
}
UndoFinished => self.undo_in_progress = false,
UngroupSelectedLayers => {
DocumentMessage::UndoFinished => self.undo_in_progress = false,
DocumentMessage::UngroupSelectedLayers => {
responses.add(DocumentMessage::StartTransaction);
let folder_paths = self.metadata().folders_sorted_by_most_nested(self.selected_nodes.selected_layers(self.metadata()));
@ -911,25 +781,25 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
}
responses.add(DocumentMessage::CommitTransaction);
}
UpdateDocumentTransform { transform } => {
DocumentMessage::UpdateDocumentTransform { transform } => {
self.metadata.document_to_viewport = transform;
responses.add(DocumentMessage::RenderRulers);
responses.add(DocumentMessage::RenderScrollbars);
responses.add(NodeGraphMessage::RunDocumentGraph);
}
ZoomCanvasTo100Percent => {
DocumentMessage::ZoomCanvasTo100Percent => {
responses.add_front(NavigationMessage::SetCanvasZoom { zoom_factor: 1. });
}
ZoomCanvasTo200Percent => {
DocumentMessage::ZoomCanvasTo200Percent => {
responses.add_front(NavigationMessage::SetCanvasZoom { zoom_factor: 2. });
}
ZoomCanvasToFitAll => {
DocumentMessage::ZoomCanvasToFitAll => {
if let Some(bounds) = self.metadata().document_bounds_document_space(true) {
responses.add(NavigationMessage::SetCanvasTilt { angle_radians: 0. });
responses.add(NavigationMessage::FitViewportToBounds { bounds, prevent_zoom_past_100: true });
}
}
Noop => (),
DocumentMessage::Noop => (),
}
}
@ -1603,3 +1473,138 @@ impl DocumentMessageHandler {
common
}
}
impl Default for DocumentMessageHandler {
fn default() -> Self {
Self {
// ======================
// Child message handlers
// ======================
navigation_handler: NavigationMessageHandler::default(),
node_graph_handler: NodeGraphMessageHandler::default(),
overlays_message_handler: OverlaysMessageHandler::default(),
properties_panel_message_handler: PropertiesPanelMessageHandler::default(),
// ============================================
// Fields that are saved in the document format
// ============================================
network: root_network(),
selected_nodes: SelectedNodes::default(),
collapsed: CollapsedLayers::default(),
name: DEFAULT_DOCUMENT_NAME.to_string(),
commit_hash: GRAPHITE_GIT_COMMIT_HASH.to_string(),
navigation: PTZ::default(),
document_mode: DocumentMode::DesignMode,
view_mode: ViewMode::default(),
overlays_visible: true,
rulers_visible: true,
// =============================================
// Fields omitted from the saved document format
// =============================================
document_undo_history: VecDeque::new(),
document_redo_history: VecDeque::new(),
saved_hash: None,
auto_saved_hash: None,
undo_in_progress: false,
graph_view_overlay_open: false,
snapping_state: SnappingState::default(),
layer_range_selection_reference: None,
metadata: Default::default(),
}
}
}
#[inline(always)]
fn default_network() -> NodeNetwork {
DocumentMessageHandler::default().network
}
#[inline(always)]
fn default_selected_nodes() -> SelectedNodes {
DocumentMessageHandler::default().selected_nodes
}
#[inline(always)]
fn default_collapsed() -> CollapsedLayers {
DocumentMessageHandler::default().collapsed
}
#[inline(always)]
fn default_name() -> String {
DocumentMessageHandler::default().name
}
#[inline(always)]
fn default_commit_hash() -> String {
DocumentMessageHandler::default().commit_hash
}
#[inline(always)]
fn default_pan_tilt_zoom() -> PTZ {
DocumentMessageHandler::default().navigation
}
#[inline(always)]
fn default_document_mode() -> DocumentMode {
DocumentMessageHandler::default().document_mode
}
#[inline(always)]
fn default_view_mode() -> ViewMode {
DocumentMessageHandler::default().view_mode
}
#[inline(always)]
fn default_overlays_visible() -> bool {
DocumentMessageHandler::default().overlays_visible
}
#[inline(always)]
fn default_rulers_visible() -> bool {
DocumentMessageHandler::default().rulers_visible
}
fn root_network() -> NodeNetwork {
{
let mut network = NodeNetwork::default();
let node = graph_craft::document::DocumentNode {
name: "Output".into(),
inputs: vec![NodeInput::value(TaggedValue::GraphicGroup(Default::default()), true), NodeInput::Network(concrete!(WasmEditorApi))],
implementation: graph_craft::document::DocumentNodeImplementation::Network(NodeNetwork {
imports: vec![NodeId(3), NodeId(0)],
exports: vec![NodeOutput::new(NodeId(3), 0)],
nodes: [
DocumentNode {
name: "EditorApi".to_string(),
inputs: vec![NodeInput::Network(concrete!(WasmEditorApi))],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")),
..Default::default()
},
DocumentNode {
name: "Create Canvas".to_string(),
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")),
skip_deduplication: true,
..Default::default()
},
DocumentNode {
name: "Cache".to_string(),
manual_composition: Some(concrete!(())),
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")),
..Default::default()
},
DocumentNode {
name: "RenderNode".to_string(),
inputs: vec![
NodeInput::node(NodeId(0), 0),
NodeInput::Network(graphene_core::Type::Fn(Box::new(concrete!(Footprint)), Box::new(generic!(T)))),
NodeInput::node(NodeId(2), 0),
],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::RenderNode<_, _, _>")),
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
}),
metadata: DocumentNodeMetadata::position((8, 4)),
..Default::default()
};
network.push_node(node);
network
}
}

View file

@ -1,3 +1,5 @@
use super::utility_types::TransformIn;
use super::utility_types::VectorDataModification;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::prelude::*;
@ -10,7 +12,6 @@ use graphene_core::text::Font;
use graphene_core::uuid::ManipulatorGroupId;
use graphene_core::vector::brush_stroke::BrushStroke;
use graphene_core::vector::style::{Fill, Stroke};
use graphene_core::vector::ManipulatorPointId;
use graphene_core::{Artboard, Color};
use glam::{DAffine2, DVec2, IVec2};
@ -39,7 +40,6 @@ pub enum GraphOperationMessage {
layer: LayerNodeIdentifier,
stroke: Stroke,
},
TransformChange {
layer: LayerNodeIdentifier,
transform: DAffine2,
@ -56,7 +56,6 @@ pub enum GraphOperationMessage {
layer: LayerNodeIdentifier,
pivot: DVec2,
},
Vector {
layer: LayerNodeIdentifier,
modification: VectorDataModification,
@ -65,7 +64,6 @@ pub enum GraphOperationMessage {
layer: LayerNodeIdentifier,
strokes: Vec<BrushStroke>,
},
NewArtboard {
id: NodeId,
artboard: Artboard,
@ -117,26 +115,3 @@ pub enum GraphOperationMessage {
insert_index: isize,
},
}
#[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
pub enum TransformIn {
Local,
Scope { scope: DAffine2 },
Viewport,
}
type ManipulatorGroup = bezier_rs::ManipulatorGroup<ManipulatorGroupId>;
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum VectorDataModification {
AddEndManipulatorGroup { subpath_index: usize, manipulator_group: ManipulatorGroup },
AddManipulatorGroup { manipulator_group: ManipulatorGroup, after_id: ManipulatorGroupId },
AddStartManipulatorGroup { subpath_index: usize, manipulator_group: ManipulatorGroup },
RemoveManipulatorGroup { id: ManipulatorGroupId },
RemoveManipulatorPoint { point: ManipulatorPointId },
SetClosed { index: usize, closed: bool },
SetManipulatorColinearHandlesState { id: ManipulatorGroupId, colinear: bool },
SetManipulatorPosition { point: ManipulatorPointId, position: DVec2 },
ToggleManipulatorColinearHandlesState { id: ManipulatorGroupId },
UpdateSubpaths { subpaths: Vec<Subpath<ManipulatorGroupId>> },
}

View file

@ -0,0 +1,456 @@
use super::transform_utils::{self, LayerBounds};
use super::utility_types::ModifyInputsContext;
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, SelectedNodes};
use crate::messages::prelude::*;
use bezier_rs::{ManipulatorGroup, Subpath};
use graph_craft::document::{generate_uuid, NodeId, NodeInput, NodeNetwork};
use graphene_core::renderer::Quad;
use graphene_core::text::Font;
use graphene_core::uuid::ManipulatorGroupId;
use graphene_core::vector::style::{Fill, Gradient, GradientType, LineCap, LineJoin, Stroke};
use graphene_core::Color;
use glam::{DAffine2, DVec2, IVec2};
pub struct GraphOperationMessageData<'a> {
pub document_network: &'a mut NodeNetwork,
pub document_metadata: &'a mut DocumentMetadata,
pub selected_nodes: &'a mut SelectedNodes,
pub collapsed: &'a mut CollapsedLayers,
pub node_graph: &'a mut NodeGraphMessageHandler,
}
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
pub struct GraphOperationMessageHandler {}
impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for GraphOperationMessageHandler {
fn process_message(&mut self, message: GraphOperationMessage, responses: &mut VecDeque<Message>, data: GraphOperationMessageData) {
let GraphOperationMessageData {
document_network,
document_metadata,
selected_nodes,
collapsed,
node_graph,
} = data;
match message {
GraphOperationMessage::FillSet { layer, fill } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.fill_set(fill);
}
}
GraphOperationMessage::OpacitySet { layer, opacity } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.opacity_set(opacity);
}
}
GraphOperationMessage::BlendModeSet { layer, blend_mode } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.blend_mode_set(blend_mode);
}
}
GraphOperationMessage::UpdateBounds { layer, old_bounds, new_bounds } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.update_bounds(old_bounds, new_bounds);
}
}
GraphOperationMessage::StrokeSet { layer, stroke } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.stroke_set(stroke);
}
}
GraphOperationMessage::TransformChange {
layer,
transform,
transform_in,
skip_rerender,
} => {
let parent_transform = document_metadata.downstream_transform_to_viewport(layer);
let bounds = LayerBounds::new(document_metadata, layer);
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.transform_change(transform, transform_in, parent_transform, bounds, skip_rerender);
}
}
GraphOperationMessage::TransformSet {
layer,
transform,
transform_in,
skip_rerender,
} => {
let parent_transform = document_metadata.downstream_transform_to_viewport(layer);
let current_transform = Some(document_metadata.transform_to_viewport(layer));
let bounds = LayerBounds::new(document_metadata, layer);
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.transform_set(transform, transform_in, parent_transform, current_transform, bounds, skip_rerender);
}
}
GraphOperationMessage::TransformSetPivot { layer, pivot } => {
let bounds = LayerBounds::new(document_metadata, layer);
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.pivot_set(pivot, bounds);
}
}
GraphOperationMessage::Vector { layer, modification } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.vector_modify(modification);
}
}
GraphOperationMessage::Brush { layer, strokes } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.brush_modify(strokes);
}
}
GraphOperationMessage::NewArtboard { id, artboard } => {
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.document_network.original_outputs()[0].node_id, 0, 0) {
modify_inputs.insert_artboard(artboard, layer);
}
load_network_structure(document_network, document_metadata, selected_nodes, collapsed);
}
GraphOperationMessage::NewBitmapLayer {
id,
image_frame,
parent,
insert_index,
} => {
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
if let Some(layer) = modify_inputs.create_layer_with_insert_index(id, insert_index, parent) {
modify_inputs.insert_image_data(image_frame, layer);
}
}
GraphOperationMessage::NewCustomLayer {
id,
nodes,
parent,
insert_index,
alias,
} => {
trace!("Inserting new layer {id} as a child of {parent:?} at index {insert_index}");
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
if let Some(layer) = modify_inputs.create_layer_with_insert_index(id, insert_index, parent) {
let new_ids: HashMap<_, _> = nodes.iter().map(|(&id, _)| (id, NodeId(generate_uuid()))).collect();
if let Some(node) = modify_inputs.document_network.nodes.get_mut(&id) {
node.alias = alias.clone();
}
let shift = nodes
.get(&NodeId(0))
.and_then(|node| {
modify_inputs
.document_network
.nodes
.get(&layer)
.map(|layer| layer.metadata.position - node.metadata.position + IVec2::new(-8, 0))
})
.unwrap_or_default();
for (old_id, mut document_node) in nodes {
// Shift copied node
document_node.metadata.position += shift;
// Get the new, non-conflicting id
let node_id = *new_ids.get(&old_id).unwrap();
document_node = document_node.map_ids(NodeGraphMessageHandler::default_node_input, &new_ids);
// Insert node into network
modify_inputs.document_network.nodes.insert(node_id, document_node);
}
if let Some(layer_node) = modify_inputs.document_network.nodes.get_mut(&layer) {
if let Some(&input) = new_ids.get(&NodeId(0)) {
layer_node.inputs[0] = NodeInput::node(input, 0)
}
}
modify_inputs.responses.add(NodeGraphMessage::RunDocumentGraph);
}
load_network_structure(document_network, document_metadata, selected_nodes, collapsed);
}
GraphOperationMessage::NewVectorLayer { id, subpaths, parent, insert_index } => {
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
if let Some(layer) = modify_inputs.create_layer_with_insert_index(id, insert_index, parent) {
modify_inputs.insert_vector_data(subpaths, layer);
}
load_network_structure(document_network, document_metadata, selected_nodes, collapsed);
}
GraphOperationMessage::NewTextLayer {
id,
text,
font,
size,
parent,
insert_index,
} => {
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
if let Some(layer) = modify_inputs.create_layer_with_insert_index(id, insert_index, parent) {
modify_inputs.insert_text(text, font, size, layer);
}
load_network_structure(document_network, document_metadata, selected_nodes, collapsed);
}
GraphOperationMessage::ResizeArtboard { id, location, dimensions } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(id, document_network, document_metadata, node_graph, responses) {
modify_inputs.resize_artboard(location, dimensions);
}
}
GraphOperationMessage::DeleteLayer { id } => {
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
modify_inputs.delete_layer(id, selected_nodes, false);
load_network_structure(document_network, document_metadata, selected_nodes, collapsed);
}
GraphOperationMessage::DeleteArtboard { id } => {
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
if let Some(artboard_id) = modify_inputs.document_network.nodes.get(&id).and_then(|node| node.inputs[0].as_node()) {
modify_inputs.delete_artboard(artboard_id, selected_nodes);
} else {
warn!("Artboard does not exist");
}
modify_inputs.delete_layer(id, selected_nodes, true);
load_network_structure(document_network, document_metadata, selected_nodes, collapsed);
}
GraphOperationMessage::ClearArtboards => {
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
let layer_nodes = modify_inputs.document_network.nodes.iter().filter(|(_, node)| node.is_layer()).map(|(id, _)| *id).collect::<Vec<_>>();
for layer in layer_nodes {
let artboards = modify_inputs
.document_network
.upstream_flow_back_from_nodes(vec![layer], true)
.filter_map(|(node, _id)| if node.is_artboard() { Some(_id) } else { None })
.collect::<Vec<_>>();
if artboards.is_empty() {
continue;
}
for artboard in artboards {
modify_inputs.delete_artboard(artboard, selected_nodes);
}
modify_inputs.delete_layer(layer, selected_nodes, true);
}
load_network_structure(document_network, document_metadata, selected_nodes, collapsed);
}
GraphOperationMessage::NewSvg {
id,
svg,
transform,
parent,
insert_index,
} => {
let tree = match usvg::Tree::from_str(&svg, &usvg::Options::default()) {
Ok(t) => t,
Err(e) => {
responses.add(DocumentMessage::DocumentHistoryBackward);
responses.add(DialogMessage::DisplayDialogError {
title: "SVG parsing failed".to_string(),
description: e.to_string(),
});
return;
}
};
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
import_usvg_node(&mut modify_inputs, &usvg::Node::Group(Box::new(tree.root)), transform, id, parent, insert_index);
load_network_structure(document_network, document_metadata, selected_nodes, collapsed);
}
}
}
fn actions(&self) -> ActionList {
actions!(GraphOperationMessage;)
}
}
pub fn load_network_structure(document_network: &NodeNetwork, document_metadata: &mut DocumentMetadata, selected_nodes: &mut SelectedNodes, collapsed: &mut CollapsedLayers) {
document_metadata.load_structure(document_network, selected_nodes);
collapsed.0.retain(|&layer| document_metadata.layer_exists(layer));
}
fn usvg_color(c: usvg::Color, a: f32) -> Color {
Color::from_rgbaf32_unchecked(c.red as f32 / 255., c.green as f32 / 255., c.blue as f32 / 255., a)
}
fn usvg_transform(c: usvg::Transform) -> DAffine2 {
DAffine2::from_cols_array(&[c.sx as f64, c.ky as f64, c.kx as f64, c.sy as f64, c.tx as f64, c.ty as f64])
}
fn import_usvg_node(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node, transform: DAffine2, id: NodeId, parent: LayerNodeIdentifier, insert_index: isize) {
let Some(layer) = modify_inputs.create_layer_with_insert_index(id, insert_index, parent) else {
return;
};
modify_inputs.layer_node = Some(layer);
match node {
usvg::Node::Group(group) => {
for child in &group.children {
import_usvg_node(modify_inputs, child, transform, NodeId(generate_uuid()), LayerNodeIdentifier::new_unchecked(layer), -1);
}
modify_inputs.layer_node = Some(layer);
}
usvg::Node::Path(path) => {
let subpaths = convert_usvg_path(path);
let bounds = subpaths.iter().filter_map(|subpath| subpath.bounding_box()).reduce(Quad::combine_bounds).unwrap_or_default();
let transformed_bounds = subpaths
.iter()
.filter_map(|subpath| subpath.bounding_box_with_transform(transform * usvg_transform(node.abs_transform())))
.reduce(Quad::combine_bounds)
.unwrap_or_default();
modify_inputs.insert_vector_data(subpaths, layer);
let center = DAffine2::from_translation((bounds[0] + bounds[1]) / 2.);
modify_inputs.modify_inputs("Transform", true, |inputs, _node_id, _metadata| {
transform_utils::update_transform(inputs, center.inverse() * transform * usvg_transform(node.abs_transform()) * center);
});
let bounds_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
let transformed_bound_transform = DAffine2::from_scale_angle_translation(transformed_bounds[1] - transformed_bounds[0], 0., transformed_bounds[0]);
apply_usvg_fill(
&path.fill,
modify_inputs,
transform * usvg_transform(node.abs_transform()),
bounds_transform,
transformed_bound_transform,
);
apply_usvg_stroke(&path.stroke, modify_inputs);
}
usvg::Node::Image(_image) => {
warn!("Skip image")
}
usvg::Node::Text(text) => {
let font = Font::new(crate::consts::DEFAULT_FONT_FAMILY.to_string(), crate::consts::DEFAULT_FONT_STYLE.to_string());
modify_inputs.insert_text(text.chunks.iter().map(|chunk| chunk.text.clone()).collect(), font, 24., layer);
modify_inputs.fill_set(Fill::Solid(Color::BLACK));
}
}
}
fn apply_usvg_stroke(stroke: &Option<usvg::Stroke>, modify_inputs: &mut ModifyInputsContext) {
if let Some(stroke) = stroke {
if let usvg::Paint::Color(color) = &stroke.paint {
modify_inputs.stroke_set(Stroke {
color: Some(usvg_color(*color, stroke.opacity.get())),
weight: stroke.width.get() as f64,
dash_lengths: stroke.dasharray.as_ref().map(|lengths| lengths.iter().map(|&length| length as f64).collect()).unwrap_or_default(),
dash_offset: stroke.dashoffset as f64,
line_cap: match stroke.linecap {
usvg::LineCap::Butt => LineCap::Butt,
usvg::LineCap::Round => LineCap::Round,
usvg::LineCap::Square => LineCap::Square,
},
line_join: match stroke.linejoin {
usvg::LineJoin::Miter => LineJoin::Miter,
usvg::LineJoin::MiterClip => LineJoin::Miter,
usvg::LineJoin::Round => LineJoin::Round,
usvg::LineJoin::Bevel => LineJoin::Bevel,
},
line_join_miter_limit: stroke.miterlimit.get() as f64,
})
} else {
warn!("Skip non-solid stroke")
}
}
}
fn apply_usvg_fill(fill: &Option<usvg::Fill>, modify_inputs: &mut ModifyInputsContext, transform: DAffine2, bounds_transform: DAffine2, transformed_bound_transform: DAffine2) {
if let Some(fill) = &fill {
modify_inputs.fill_set(match &fill.paint {
usvg::Paint::Color(color) => Fill::solid(usvg_color(*color, fill.opacity.get())),
usvg::Paint::LinearGradient(linear) => {
let local = [DVec2::new(linear.x1 as f64, linear.y1 as f64), DVec2::new(linear.x2 as f64, linear.y2 as f64)];
let to_doc_transform = if linear.base.units == usvg::Units::UserSpaceOnUse {
transform
} else {
transformed_bound_transform
};
let to_doc = to_doc_transform * usvg_transform(linear.transform);
let document = [to_doc.transform_point2(local[0]), to_doc.transform_point2(local[1])];
let layer = [transform.inverse().transform_point2(document[0]), transform.inverse().transform_point2(document[1])];
let [start, end] = [bounds_transform.inverse().transform_point2(layer[0]), bounds_transform.inverse().transform_point2(layer[1])];
Fill::Gradient(Gradient {
start,
end,
transform: DAffine2::IDENTITY,
gradient_type: GradientType::Linear,
positions: linear.stops.iter().map(|stop| (stop.offset.get() as f64, usvg_color(stop.color, stop.opacity.get()))).collect(),
})
}
usvg::Paint::RadialGradient(radial) => {
let local = [DVec2::new(radial.cx as f64, radial.cy as f64), DVec2::new(radial.fx as f64, radial.fy as f64)];
let to_doc_transform = if radial.base.units == usvg::Units::UserSpaceOnUse {
transform
} else {
transformed_bound_transform
};
let to_doc = to_doc_transform * usvg_transform(radial.transform);
let document = [to_doc.transform_point2(local[0]), to_doc.transform_point2(local[1])];
let layer = [transform.inverse().transform_point2(document[0]), transform.inverse().transform_point2(document[1])];
let [start, end] = [bounds_transform.inverse().transform_point2(layer[0]), bounds_transform.inverse().transform_point2(layer[1])];
Fill::Gradient(Gradient {
start,
end,
transform: DAffine2::IDENTITY,
gradient_type: GradientType::Radial,
positions: radial.stops.iter().map(|stop| (stop.offset.get() as f64, usvg_color(stop.color, stop.opacity.get()))).collect(),
})
}
usvg::Paint::Pattern(_) => {
warn!("Skip pattern");
return;
}
});
}
}
fn convert_usvg_path(path: &usvg::Path) -> Vec<Subpath<ManipulatorGroupId>> {
let mut subpaths = Vec::new();
let mut groups = Vec::new();
let mut points = path.data.points().iter();
let to_vec = |p: &usvg::tiny_skia_path::Point| DVec2::new(p.x as f64, p.y as f64);
for verb in path.data.verbs() {
match verb {
usvg::tiny_skia_path::PathVerb::Move => {
subpaths.push(Subpath::new(std::mem::take(&mut groups), false));
let Some(start) = points.next().map(to_vec) else { continue };
groups.push(ManipulatorGroup::new(start, Some(start), Some(start)));
}
usvg::tiny_skia_path::PathVerb::Line => {
let Some(end) = points.next().map(to_vec) else { continue };
groups.push(ManipulatorGroup::new(end, Some(end), Some(end)));
}
usvg::tiny_skia_path::PathVerb::Quad => {
let Some(handle) = points.next().map(to_vec) else { continue };
let Some(end) = points.next().map(to_vec) else { continue };
if let Some(last) = groups.last_mut() {
last.out_handle = Some(last.anchor + (2. / 3.) * (handle - last.anchor));
}
groups.push(ManipulatorGroup::new(end, Some(end + (2. / 3.) * (handle - end)), Some(end)));
}
usvg::tiny_skia_path::PathVerb::Cubic => {
let Some(first_handle) = points.next().map(to_vec) else { continue };
let Some(second_handle) = points.next().map(to_vec) else { continue };
let Some(end) = points.next().map(to_vec) else { continue };
if let Some(last) = groups.last_mut() {
last.out_handle = Some(first_handle);
}
groups.push(ManipulatorGroup::new(end, Some(second_handle), Some(end)));
}
usvg::tiny_skia_path::PathVerb::Close => {
subpaths.push(Subpath::new(std::mem::take(&mut groups), true));
}
}
}
subpaths.push(Subpath::new(groups, false));
subpaths
}

View file

@ -0,0 +1,9 @@
mod graph_operation_message;
pub mod graph_operation_message_handler;
pub mod transform_utils;
pub mod utility_types;
#[doc(inline)]
pub use graph_operation_message::*;
#[doc(inline)]
pub use graph_operation_message_handler::*;

View file

@ -1,4 +1,3 @@
use crate::messages::portfolio::document::node_graph::VectorDataModification;
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use bezier_rs::{ManipulatorGroup, Subpath};
@ -8,6 +7,8 @@ use graphene_core::vector::{ManipulatorPointId, SelectedType};
use glam::{DAffine2, DVec2};
use super::utility_types::VectorDataModification;
/// Convert an affine transform into the tuple `(scale, angle, translation, shear)` assuming `shear.y = 0`.
pub fn compute_scale_angle_translation_shear(transform: DAffine2) -> (DVec2, f64, DVec2, DVec2) {
let x_axis = transform.matrix2.x_axis;

View file

@ -1,38 +1,58 @@
use super::{resolve_document_node_type, VectorDataModification};
use crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type;
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, SelectedNodes};
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
use crate::messages::prelude::*;
use bezier_rs::{ManipulatorGroup, Subpath};
use bezier_rs::Subpath;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{generate_uuid, DocumentNode, NodeId, NodeInput, NodeNetwork, NodeOutput};
use graphene_core::raster::{BlendMode, ImageFrame};
use graphene_core::renderer::Quad;
use graphene_core::text::Font;
use graphene_core::uuid::ManipulatorGroupId;
use graphene_core::vector::brush_stroke::BrushStroke;
use graphene_core::vector::style::{Fill, FillType, Gradient, GradientType, LineCap, LineJoin, Stroke};
use graphene_core::vector::style::{Fill, FillType, Stroke};
use graphene_core::{Artboard, Color};
use transform_utils::LayerBounds;
use graphene_std::vector::ManipulatorPointId;
use glam::{DAffine2, DVec2, IVec2};
pub mod transform_utils;
use super::transform_utils::{self, LayerBounds};
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
pub struct GraphOperationMessageHandler;
struct ModifyInputsContext<'a> {
document_metadata: &'a mut DocumentMetadata,
document_network: &'a mut NodeNetwork,
node_graph: &'a mut NodeGraphMessageHandler,
responses: &'a mut VecDeque<Message>,
outwards_links: HashMap<NodeId, Vec<NodeId>>,
layer_node: Option<NodeId>,
#[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
pub enum TransformIn {
Local,
Scope { scope: DAffine2 },
Viewport,
}
type ManipulatorGroup = bezier_rs::ManipulatorGroup<ManipulatorGroupId>;
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum VectorDataModification {
AddEndManipulatorGroup { subpath_index: usize, manipulator_group: ManipulatorGroup },
AddManipulatorGroup { manipulator_group: ManipulatorGroup, after_id: ManipulatorGroupId },
AddStartManipulatorGroup { subpath_index: usize, manipulator_group: ManipulatorGroup },
RemoveManipulatorGroup { id: ManipulatorGroupId },
RemoveManipulatorPoint { point: ManipulatorPointId },
SetClosed { index: usize, closed: bool },
SetManipulatorColinearHandlesState { id: ManipulatorGroupId, colinear: bool },
SetManipulatorPosition { point: ManipulatorPointId, position: DVec2 },
ToggleManipulatorColinearHandlesState { id: ManipulatorGroupId },
UpdateSubpaths { subpaths: Vec<Subpath<ManipulatorGroupId>> },
}
pub struct ModifyInputsContext<'a> {
pub document_metadata: &'a mut DocumentMetadata,
pub document_network: &'a mut NodeNetwork,
pub node_graph: &'a mut NodeGraphMessageHandler,
pub responses: &'a mut VecDeque<Message>,
pub outwards_links: HashMap<NodeId, Vec<NodeId>>,
pub layer_node: Option<NodeId>,
}
impl<'a> ModifyInputsContext<'a> {
/// Get the node network from the document
fn new(document_network: &'a mut NodeNetwork, document_metadata: &'a mut DocumentMetadata, node_graph: &'a mut NodeGraphMessageHandler, responses: &'a mut VecDeque<Message>) -> Self {
pub fn new(document_network: &'a mut NodeNetwork, document_metadata: &'a mut DocumentMetadata, node_graph: &'a mut NodeGraphMessageHandler, responses: &'a mut VecDeque<Message>) -> Self {
Self {
outwards_links: document_network.collect_outwards_links(),
document_network,
@ -43,7 +63,7 @@ impl<'a> ModifyInputsContext<'a> {
}
}
fn new_with_layer(
pub fn new_with_layer(
id: NodeId,
document_network: &'a mut NodeNetwork,
document_metadata: &'a mut DocumentMetadata,
@ -62,7 +82,7 @@ impl<'a> ModifyInputsContext<'a> {
}
/// Updates the input of an existing node
fn modify_existing_node_inputs(&mut self, node_id: NodeId, update_input: impl FnOnce(&mut Vec<NodeInput>, NodeId, &DocumentMetadata)) {
pub fn modify_existing_node_inputs(&mut self, node_id: NodeId, update_input: impl FnOnce(&mut Vec<NodeInput>, NodeId, &DocumentMetadata)) {
let document_node = self.document_network.nodes.get_mut(&node_id).unwrap();
update_input(&mut document_node.inputs, node_id, self.document_metadata);
}
@ -160,7 +180,7 @@ impl<'a> ModifyInputsContext<'a> {
new_id
}
fn create_layer_with_insert_index(&mut self, new_id: NodeId, insert_index: isize, parent: LayerNodeIdentifier) -> Option<NodeId> {
pub fn create_layer_with_insert_index(&mut self, new_id: NodeId, insert_index: isize, parent: LayerNodeIdentifier) -> Option<NodeId> {
let skip_layer_nodes = if insert_index < 0 { (-1 - insert_index) as usize } else { insert_index as usize };
let output_node_id = if parent == LayerNodeIdentifier::ROOT {
@ -171,7 +191,7 @@ impl<'a> ModifyInputsContext<'a> {
self.create_layer(new_id, output_node_id, 0, skip_layer_nodes)
}
fn insert_artboard(&mut self, artboard: Artboard, layer: NodeId) -> Option<NodeId> {
pub fn insert_artboard(&mut self, artboard: Artboard, layer: NodeId) -> Option<NodeId> {
let artboard_node = resolve_document_node_type("Artboard").expect("Node").to_document_node_default_inputs(
[
None,
@ -186,7 +206,7 @@ impl<'a> ModifyInputsContext<'a> {
self.insert_node_before(NodeId(generate_uuid()), layer, 0, artboard_node, IVec2::new(-8, 0))
}
fn insert_vector_data(&mut self, subpaths: Vec<Subpath<ManipulatorGroupId>>, layer: NodeId) {
pub fn insert_vector_data(&mut self, subpaths: Vec<Subpath<ManipulatorGroupId>>, layer: NodeId) {
let shape = {
let node_type = resolve_document_node_type("Shape").expect("Shape node does not exist");
node_type.to_document_node_default_inputs([Some(NodeInput::value(TaggedValue::Subpaths(subpaths), false))], Default::default())
@ -206,7 +226,7 @@ impl<'a> ModifyInputsContext<'a> {
self.responses.add(NodeGraphMessage::RunDocumentGraph);
}
fn insert_text(&mut self, text: String, font: Font, size: f64, layer: NodeId) {
pub fn insert_text(&mut self, text: String, font: Font, size: f64, layer: NodeId) {
let text = resolve_document_node_type("Text").expect("Text node does not exist").to_document_node(
[
NodeInput::Network(graph_craft::concrete!(graphene_std::wasm_application_io::WasmEditorApi)),
@ -231,7 +251,7 @@ impl<'a> ModifyInputsContext<'a> {
self.responses.add(NodeGraphMessage::RunDocumentGraph);
}
fn insert_image_data(&mut self, image_frame: ImageFrame<Color>, layer: NodeId) {
pub fn insert_image_data(&mut self, image_frame: ImageFrame<Color>, layer: NodeId) {
let image = {
let node_type = resolve_document_node_type("Image").expect("Image node does not exist");
node_type.to_document_node_default_inputs([Some(NodeInput::value(TaggedValue::ImageFrame(image_frame), false))], Default::default())
@ -247,7 +267,7 @@ impl<'a> ModifyInputsContext<'a> {
self.responses.add(NodeGraphMessage::RunDocumentGraph);
}
fn shift_upstream(&mut self, node_id: NodeId, shift: IVec2) {
pub fn shift_upstream(&mut self, node_id: NodeId, shift: IVec2) {
let mut shift_nodes = HashSet::new();
let mut stack = vec![node_id];
while let Some(node_id) = stack.pop() {
@ -268,7 +288,7 @@ impl<'a> ModifyInputsContext<'a> {
}
/// Inserts a new node and modifies the inputs
fn modify_new_node(&mut self, name: &'static str, update_input: impl FnOnce(&mut Vec<NodeInput>, NodeId, &DocumentMetadata)) {
pub fn modify_new_node(&mut self, name: &'static str, update_input: impl FnOnce(&mut Vec<NodeInput>, NodeId, &DocumentMetadata)) {
let output_node_id = self.layer_node.unwrap_or(self.document_network.exports[0].node_id);
let Some(output_node) = self.document_network.nodes.get_mut(&output_node_id) else {
warn!("Output node doesn't exist");
@ -297,7 +317,7 @@ impl<'a> ModifyInputsContext<'a> {
}
/// Changes the inputs of a specific node
fn modify_inputs(&mut self, name: &'static str, skip_rerender: bool, update_input: impl FnOnce(&mut Vec<NodeInput>, NodeId, &DocumentMetadata)) {
pub fn modify_inputs(&mut self, name: &'static str, skip_rerender: bool, update_input: impl FnOnce(&mut Vec<NodeInput>, NodeId, &DocumentMetadata)) {
let existing_node_id = self
.document_network
.upstream_flow_back_from_nodes(
@ -322,7 +342,7 @@ impl<'a> ModifyInputsContext<'a> {
}
/// Changes the inputs of a all of the existing instances of a node name
fn modify_all_node_inputs(&mut self, name: &'static str, skip_rerender: bool, mut update_input: impl FnMut(&mut Vec<NodeInput>, NodeId, &DocumentMetadata)) {
pub fn modify_all_node_inputs(&mut self, name: &'static str, skip_rerender: bool, mut update_input: impl FnMut(&mut Vec<NodeInput>, NodeId, &DocumentMetadata)) {
let existing_nodes: Vec<_> = self
.document_network
.upstream_flow_back_from_nodes(
@ -346,7 +366,7 @@ impl<'a> ModifyInputsContext<'a> {
}
}
fn fill_set(&mut self, fill: Fill) {
pub fn fill_set(&mut self, fill: Fill) {
self.modify_inputs("Fill", false, |inputs, _node_id, _metadata| {
let fill_type = match fill {
Fill::None | Fill::Solid(_) => FillType::Solid,
@ -367,19 +387,19 @@ impl<'a> ModifyInputsContext<'a> {
});
}
fn opacity_set(&mut self, opacity: f64) {
pub fn opacity_set(&mut self, opacity: f64) {
self.modify_inputs("Opacity", false, |inputs, _node_id, _metadata| {
inputs[1] = NodeInput::value(TaggedValue::F64(opacity * 100.), false);
});
}
fn blend_mode_set(&mut self, blend_mode: BlendMode) {
pub fn blend_mode_set(&mut self, blend_mode: BlendMode) {
self.modify_inputs("Blend Mode", false, |inputs, _node_id, _metadata| {
inputs[1] = NodeInput::value(TaggedValue::BlendMode(blend_mode), false);
});
}
fn stroke_set(&mut self, stroke: Stroke) {
pub fn stroke_set(&mut self, stroke: Stroke) {
self.modify_inputs("Stroke", false, |inputs, _node_id, _metadata| {
inputs[1] = NodeInput::value(TaggedValue::OptionalColor(stroke.color), false);
inputs[2] = NodeInput::value(TaggedValue::F64(stroke.weight), false);
@ -391,7 +411,7 @@ impl<'a> ModifyInputsContext<'a> {
});
}
fn transform_change(&mut self, transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, bounds: LayerBounds, skip_rerender: bool) {
pub fn transform_change(&mut self, transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, bounds: LayerBounds, skip_rerender: bool) {
self.modify_inputs("Transform", skip_rerender, |inputs, node_id, metadata| {
let layer_transform = transform_utils::get_current_transform(inputs);
let upstream_transform = metadata.upstream_transform(node_id);
@ -406,7 +426,7 @@ impl<'a> ModifyInputsContext<'a> {
});
}
fn transform_set(&mut self, mut transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, current_transform: Option<DAffine2>, bounds: LayerBounds, skip_rerender: bool) {
pub fn transform_set(&mut self, mut transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, current_transform: Option<DAffine2>, bounds: LayerBounds, skip_rerender: bool) {
self.modify_inputs("Transform", skip_rerender, |inputs, node_id, metadata| {
let upstream_transform = metadata.upstream_transform(node_id);
@ -428,7 +448,7 @@ impl<'a> ModifyInputsContext<'a> {
});
}
fn pivot_set(&mut self, new_pivot: DVec2, bounds: LayerBounds) {
pub fn pivot_set(&mut self, new_pivot: DVec2, bounds: LayerBounds) {
self.modify_inputs("Transform", false, |inputs, node_id, metadata| {
let layer_transform = transform_utils::get_current_transform(inputs);
let upstream_transform = metadata.upstream_transform(node_id);
@ -440,7 +460,7 @@ impl<'a> ModifyInputsContext<'a> {
});
}
fn update_bounds(&mut self, [old_bounds_min, old_bounds_max]: [DVec2; 2], [new_bounds_min, new_bounds_max]: [DVec2; 2]) {
pub fn update_bounds(&mut self, [old_bounds_min, old_bounds_max]: [DVec2; 2], [new_bounds_min, new_bounds_max]: [DVec2; 2]) {
self.modify_all_node_inputs("Transform", false, |inputs, node_id, metadata| {
let upstream_transform = metadata.upstream_transform(node_id);
let layer_transform = transform_utils::get_current_transform(inputs);
@ -456,7 +476,7 @@ impl<'a> ModifyInputsContext<'a> {
});
}
fn vector_modify(&mut self, modification: VectorDataModification) {
pub fn vector_modify(&mut self, modification: VectorDataModification) {
let [mut old_bounds_min, mut old_bounds_max] = [DVec2::ZERO, DVec2::ONE];
let [mut new_bounds_min, mut new_bounds_max] = [DVec2::ZERO, DVec2::ONE];
let mut empty = false;
@ -497,20 +517,20 @@ impl<'a> ModifyInputsContext<'a> {
}
}
fn brush_modify(&mut self, strokes: Vec<BrushStroke>) {
pub fn brush_modify(&mut self, strokes: Vec<BrushStroke>) {
self.modify_inputs("Brush", false, |inputs, _node_id, _metadata| {
inputs[2] = NodeInput::value(TaggedValue::BrushStrokes(strokes), false);
});
}
fn resize_artboard(&mut self, location: IVec2, dimensions: IVec2) {
pub fn resize_artboard(&mut self, location: IVec2, dimensions: IVec2) {
self.modify_inputs("Artboard", false, |inputs, _node_id, _metadata| {
inputs[1] = NodeInput::value(TaggedValue::IVec2(location), false);
inputs[2] = NodeInput::value(TaggedValue::IVec2(dimensions), false);
});
}
fn delete_layer(&mut self, id: NodeId, selected_nodes: &mut SelectedNodes, is_artboard_layer: bool) {
pub fn delete_layer(&mut self, id: NodeId, selected_nodes: &mut SelectedNodes, is_artboard_layer: bool) {
let Some(node) = self.document_network.nodes.get(&id) else {
warn!("Deleting layer node that does not exist");
return;
@ -627,7 +647,7 @@ impl<'a> ModifyInputsContext<'a> {
self.responses.add(NodeGraphMessage::RunDocumentGraph);
}
fn delete_artboard(&mut self, id: NodeId, selected_nodes: &mut SelectedNodes) {
pub fn delete_artboard(&mut self, id: NodeId, selected_nodes: &mut SelectedNodes) {
let Some(node) = self.document_network.nodes.get(&id) else {
warn!("Deleting artboard node that does not exist");
return;
@ -661,441 +681,3 @@ impl<'a> ModifyInputsContext<'a> {
self.responses.add(NodeGraphMessage::RunDocumentGraph);
}
}
pub struct GraphOperationHandlerData<'a> {
pub document_network: &'a mut NodeNetwork,
pub document_metadata: &'a mut DocumentMetadata,
pub selected_nodes: &'a mut SelectedNodes,
pub collapsed: &'a mut CollapsedLayers,
pub node_graph: &'a mut NodeGraphMessageHandler,
}
impl MessageHandler<GraphOperationMessage, GraphOperationHandlerData<'_>> for GraphOperationMessageHandler {
fn process_message(&mut self, message: GraphOperationMessage, responses: &mut VecDeque<Message>, data: GraphOperationHandlerData) {
let GraphOperationHandlerData {
document_network,
document_metadata,
selected_nodes,
collapsed,
node_graph,
} = data;
match message {
GraphOperationMessage::FillSet { layer, fill } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.fill_set(fill);
}
}
GraphOperationMessage::OpacitySet { layer, opacity } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.opacity_set(opacity);
}
}
GraphOperationMessage::BlendModeSet { layer, blend_mode } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.blend_mode_set(blend_mode);
}
}
GraphOperationMessage::UpdateBounds { layer, old_bounds, new_bounds } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.update_bounds(old_bounds, new_bounds);
}
}
GraphOperationMessage::StrokeSet { layer, stroke } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.stroke_set(stroke);
}
}
GraphOperationMessage::TransformChange {
layer,
transform,
transform_in,
skip_rerender,
} => {
let parent_transform = document_metadata.downstream_transform_to_viewport(layer);
let bounds = LayerBounds::new(document_metadata, layer);
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.transform_change(transform, transform_in, parent_transform, bounds, skip_rerender);
}
}
GraphOperationMessage::TransformSet {
layer,
transform,
transform_in,
skip_rerender,
} => {
let parent_transform = document_metadata.downstream_transform_to_viewport(layer);
let current_transform = Some(document_metadata.transform_to_viewport(layer));
let bounds = LayerBounds::new(document_metadata, layer);
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.transform_set(transform, transform_in, parent_transform, current_transform, bounds, skip_rerender);
}
}
GraphOperationMessage::TransformSetPivot { layer, pivot } => {
let bounds = LayerBounds::new(document_metadata, layer);
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.pivot_set(pivot, bounds);
}
}
GraphOperationMessage::Vector { layer, modification } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.vector_modify(modification);
}
}
GraphOperationMessage::Brush { layer, strokes } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.brush_modify(strokes);
}
}
GraphOperationMessage::NewArtboard { id, artboard } => {
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.document_network.original_outputs()[0].node_id, 0, 0) {
modify_inputs.insert_artboard(artboard, layer);
}
load_network_structure(document_network, document_metadata, selected_nodes, collapsed);
}
GraphOperationMessage::NewBitmapLayer {
id,
image_frame,
parent,
insert_index,
} => {
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
if let Some(layer) = modify_inputs.create_layer_with_insert_index(id, insert_index, parent) {
modify_inputs.insert_image_data(image_frame, layer);
}
}
GraphOperationMessage::NewCustomLayer {
id,
nodes,
parent,
insert_index,
alias,
} => {
trace!("Inserting new layer {id} as a child of {parent:?} at index {insert_index}");
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
if let Some(layer) = modify_inputs.create_layer_with_insert_index(id, insert_index, parent) {
let new_ids: HashMap<_, _> = nodes.iter().map(|(&id, _)| (id, NodeId(generate_uuid()))).collect();
if let Some(node) = modify_inputs.document_network.nodes.get_mut(&id) {
node.alias = alias.clone();
}
let shift = nodes
.get(&NodeId(0))
.and_then(|node| {
modify_inputs
.document_network
.nodes
.get(&layer)
.map(|layer| layer.metadata.position - node.metadata.position + IVec2::new(-8, 0))
})
.unwrap_or_default();
for (old_id, mut document_node) in nodes {
// Shift copied node
document_node.metadata.position += shift;
// Get the new, non-conflicting id
let node_id = *new_ids.get(&old_id).unwrap();
document_node = document_node.map_ids(NodeGraphMessageHandler::default_node_input, &new_ids);
// Insert node into network
modify_inputs.document_network.nodes.insert(node_id, document_node);
}
if let Some(layer_node) = modify_inputs.document_network.nodes.get_mut(&layer) {
if let Some(&input) = new_ids.get(&NodeId(0)) {
layer_node.inputs[0] = NodeInput::node(input, 0)
}
}
modify_inputs.responses.add(NodeGraphMessage::RunDocumentGraph);
}
load_network_structure(document_network, document_metadata, selected_nodes, collapsed);
}
GraphOperationMessage::NewVectorLayer { id, subpaths, parent, insert_index } => {
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
if let Some(layer) = modify_inputs.create_layer_with_insert_index(id, insert_index, parent) {
modify_inputs.insert_vector_data(subpaths, layer);
}
load_network_structure(document_network, document_metadata, selected_nodes, collapsed);
}
GraphOperationMessage::NewTextLayer {
id,
text,
font,
size,
parent,
insert_index,
} => {
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
if let Some(layer) = modify_inputs.create_layer_with_insert_index(id, insert_index, parent) {
modify_inputs.insert_text(text, font, size, layer);
}
load_network_structure(document_network, document_metadata, selected_nodes, collapsed);
}
GraphOperationMessage::ResizeArtboard { id, location, dimensions } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(id, document_network, document_metadata, node_graph, responses) {
modify_inputs.resize_artboard(location, dimensions);
}
}
GraphOperationMessage::DeleteLayer { id } => {
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
modify_inputs.delete_layer(id, selected_nodes, false);
load_network_structure(document_network, document_metadata, selected_nodes, collapsed);
}
GraphOperationMessage::DeleteArtboard { id } => {
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
if let Some(artboard_id) = modify_inputs.document_network.nodes.get(&id).and_then(|node| node.inputs[0].as_node()) {
modify_inputs.delete_artboard(artboard_id, selected_nodes);
} else {
warn!("Artboard does not exist");
}
modify_inputs.delete_layer(id, selected_nodes, true);
load_network_structure(document_network, document_metadata, selected_nodes, collapsed);
}
GraphOperationMessage::ClearArtboards => {
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
let layer_nodes = modify_inputs.document_network.nodes.iter().filter(|(_, node)| node.is_layer()).map(|(id, _)| *id).collect::<Vec<_>>();
for layer in layer_nodes {
let artboards = modify_inputs
.document_network
.upstream_flow_back_from_nodes(vec![layer], true)
.filter_map(|(node, _id)| if node.is_artboard() { Some(_id) } else { None })
.collect::<Vec<_>>();
if artboards.is_empty() {
continue;
}
for artboard in artboards {
modify_inputs.delete_artboard(artboard, selected_nodes);
}
modify_inputs.delete_layer(layer, selected_nodes, true);
}
load_network_structure(document_network, document_metadata, selected_nodes, collapsed);
}
GraphOperationMessage::NewSvg {
id,
svg,
transform,
parent,
insert_index,
} => {
let tree = match usvg::Tree::from_str(&svg, &usvg::Options::default()) {
Ok(t) => t,
Err(e) => {
responses.add(DocumentMessage::DocumentHistoryBackward);
responses.add(DialogMessage::DisplayDialogError {
title: "SVG parsing failed".to_string(),
description: e.to_string(),
});
return;
}
};
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
import_usvg_node(&mut modify_inputs, &usvg::Node::Group(Box::new(tree.root)), transform, id, parent, insert_index);
load_network_structure(document_network, document_metadata, selected_nodes, collapsed);
}
}
}
fn actions(&self) -> ActionList {
actions!(GraphOperationMessage; )
}
}
pub fn load_network_structure(document_network: &NodeNetwork, document_metadata: &mut DocumentMetadata, selected_nodes: &mut SelectedNodes, collapsed: &mut CollapsedLayers) {
document_metadata.load_structure(document_network, selected_nodes);
collapsed.0.retain(|&layer| document_metadata.layer_exists(layer));
}
fn usvg_color(c: usvg::Color, a: f32) -> Color {
Color::from_rgbaf32_unchecked(c.red as f32 / 255., c.green as f32 / 255., c.blue as f32 / 255., a)
}
fn usvg_transform(c: usvg::Transform) -> DAffine2 {
DAffine2::from_cols_array(&[c.sx as f64, c.ky as f64, c.kx as f64, c.sy as f64, c.tx as f64, c.ty as f64])
}
fn import_usvg_node(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node, transform: DAffine2, id: NodeId, parent: LayerNodeIdentifier, insert_index: isize) {
let Some(layer) = modify_inputs.create_layer_with_insert_index(id, insert_index, parent) else {
return;
};
modify_inputs.layer_node = Some(layer);
match node {
usvg::Node::Group(group) => {
for child in &group.children {
import_usvg_node(modify_inputs, child, transform, NodeId(generate_uuid()), LayerNodeIdentifier::new_unchecked(layer), -1);
}
modify_inputs.layer_node = Some(layer);
}
usvg::Node::Path(path) => {
let subpaths = convert_usvg_path(path);
let bounds = subpaths.iter().filter_map(|subpath| subpath.bounding_box()).reduce(Quad::combine_bounds).unwrap_or_default();
let transformed_bounds = subpaths
.iter()
.filter_map(|subpath| subpath.bounding_box_with_transform(transform * usvg_transform(node.abs_transform())))
.reduce(Quad::combine_bounds)
.unwrap_or_default();
modify_inputs.insert_vector_data(subpaths, layer);
let center = DAffine2::from_translation((bounds[0] + bounds[1]) / 2.);
modify_inputs.modify_inputs("Transform", true, |inputs, _node_id, _metadata| {
transform_utils::update_transform(inputs, center.inverse() * transform * usvg_transform(node.abs_transform()) * center);
});
let bounds_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
let transformed_bound_transform = DAffine2::from_scale_angle_translation(transformed_bounds[1] - transformed_bounds[0], 0., transformed_bounds[0]);
apply_usvg_fill(
&path.fill,
modify_inputs,
transform * usvg_transform(node.abs_transform()),
bounds_transform,
transformed_bound_transform,
);
apply_usvg_stroke(&path.stroke, modify_inputs);
}
usvg::Node::Image(_image) => {
warn!("Skip image")
}
usvg::Node::Text(text) => {
let font = Font::new(crate::consts::DEFAULT_FONT_FAMILY.to_string(), crate::consts::DEFAULT_FONT_STYLE.to_string());
modify_inputs.insert_text(text.chunks.iter().map(|chunk| chunk.text.clone()).collect(), font, 24., layer);
modify_inputs.fill_set(Fill::Solid(Color::BLACK));
}
}
}
fn apply_usvg_stroke(stroke: &Option<usvg::Stroke>, modify_inputs: &mut ModifyInputsContext) {
if let Some(stroke) = stroke {
if let usvg::Paint::Color(color) = &stroke.paint {
modify_inputs.stroke_set(Stroke {
color: Some(usvg_color(*color, stroke.opacity.get())),
weight: stroke.width.get() as f64,
dash_lengths: stroke.dasharray.as_ref().map(|lengths| lengths.iter().map(|&length| length as f64).collect()).unwrap_or_default(),
dash_offset: stroke.dashoffset as f64,
line_cap: match stroke.linecap {
usvg::LineCap::Butt => LineCap::Butt,
usvg::LineCap::Round => LineCap::Round,
usvg::LineCap::Square => LineCap::Square,
},
line_join: match stroke.linejoin {
usvg::LineJoin::Miter => LineJoin::Miter,
usvg::LineJoin::MiterClip => LineJoin::Miter,
usvg::LineJoin::Round => LineJoin::Round,
usvg::LineJoin::Bevel => LineJoin::Bevel,
},
line_join_miter_limit: stroke.miterlimit.get() as f64,
})
} else {
warn!("Skip non-solid stroke")
}
}
}
fn apply_usvg_fill(fill: &Option<usvg::Fill>, modify_inputs: &mut ModifyInputsContext, transform: DAffine2, bounds_transform: DAffine2, transformed_bound_transform: DAffine2) {
if let Some(fill) = &fill {
modify_inputs.fill_set(match &fill.paint {
usvg::Paint::Color(color) => Fill::solid(usvg_color(*color, fill.opacity.get())),
usvg::Paint::LinearGradient(linear) => {
let local = [DVec2::new(linear.x1 as f64, linear.y1 as f64), DVec2::new(linear.x2 as f64, linear.y2 as f64)];
let to_doc_transform = if linear.base.units == usvg::Units::UserSpaceOnUse {
transform
} else {
transformed_bound_transform
};
let to_doc = to_doc_transform * usvg_transform(linear.transform);
let document = [to_doc.transform_point2(local[0]), to_doc.transform_point2(local[1])];
let layer = [transform.inverse().transform_point2(document[0]), transform.inverse().transform_point2(document[1])];
let [start, end] = [bounds_transform.inverse().transform_point2(layer[0]), bounds_transform.inverse().transform_point2(layer[1])];
Fill::Gradient(Gradient {
start,
end,
transform: DAffine2::IDENTITY,
gradient_type: GradientType::Linear,
positions: linear.stops.iter().map(|stop| (stop.offset.get() as f64, usvg_color(stop.color, stop.opacity.get()))).collect(),
})
}
usvg::Paint::RadialGradient(radial) => {
let local = [DVec2::new(radial.cx as f64, radial.cy as f64), DVec2::new(radial.fx as f64, radial.fy as f64)];
let to_doc_transform = if radial.base.units == usvg::Units::UserSpaceOnUse {
transform
} else {
transformed_bound_transform
};
let to_doc = to_doc_transform * usvg_transform(radial.transform);
let document = [to_doc.transform_point2(local[0]), to_doc.transform_point2(local[1])];
let layer = [transform.inverse().transform_point2(document[0]), transform.inverse().transform_point2(document[1])];
let [start, end] = [bounds_transform.inverse().transform_point2(layer[0]), bounds_transform.inverse().transform_point2(layer[1])];
Fill::Gradient(Gradient {
start,
end,
transform: DAffine2::IDENTITY,
gradient_type: GradientType::Radial,
positions: radial.stops.iter().map(|stop| (stop.offset.get() as f64, usvg_color(stop.color, stop.opacity.get()))).collect(),
})
}
usvg::Paint::Pattern(_) => {
warn!("Skip pattern");
return;
}
});
}
}
fn convert_usvg_path(path: &usvg::Path) -> Vec<Subpath<ManipulatorGroupId>> {
let mut subpaths = Vec::new();
let mut groups = Vec::new();
let mut points = path.data.points().iter();
let to_vec = |p: &usvg::tiny_skia_path::Point| DVec2::new(p.x as f64, p.y as f64);
for verb in path.data.verbs() {
match verb {
usvg::tiny_skia_path::PathVerb::Move => {
subpaths.push(Subpath::new(std::mem::take(&mut groups), false));
let Some(start) = points.next().map(to_vec) else { continue };
groups.push(ManipulatorGroup::new(start, Some(start), Some(start)));
}
usvg::tiny_skia_path::PathVerb::Line => {
let Some(end) = points.next().map(to_vec) else { continue };
groups.push(ManipulatorGroup::new(end, Some(end), Some(end)));
}
usvg::tiny_skia_path::PathVerb::Quad => {
let Some(handle) = points.next().map(to_vec) else { continue };
let Some(end) = points.next().map(to_vec) else { continue };
if let Some(last) = groups.last_mut() {
last.out_handle = Some(last.anchor + (2. / 3.) * (handle - last.anchor));
}
groups.push(ManipulatorGroup::new(end, Some(end + (2. / 3.) * (handle - end)), Some(end)));
}
usvg::tiny_skia_path::PathVerb::Cubic => {
let Some(first_handle) = points.next().map(to_vec) else { continue };
let Some(second_handle) = points.next().map(to_vec) else { continue };
let Some(end) = points.next().map(to_vec) else { continue };
if let Some(last) = groups.last_mut() {
last.out_handle = Some(first_handle);
}
groups.push(ManipulatorGroup::new(end, Some(second_handle), Some(end)));
}
usvg::tiny_skia_path::PathVerb::Close => {
subpaths.push(Subpath::new(std::mem::take(&mut groups), true));
}
}
}
subpaths.push(Subpath::new(groups, false));
subpaths
}

View file

@ -1,6 +1,7 @@
mod document_message;
mod document_message_handler;
pub mod graph_operation;
pub mod navigation;
pub mod node_graph;
pub mod overlays;
@ -10,4 +11,4 @@ pub mod utility_types;
#[doc(inline)]
pub use document_message::{DocumentMessage, DocumentMessageDiscriminant};
#[doc(inline)]
pub use document_message_handler::{DocumentInputs, DocumentMessageHandler};
pub use document_message_handler::{DocumentMessageData, DocumentMessageHandler};

View file

@ -1,7 +1,8 @@
mod navigation_message;
mod navigation_message_handler;
pub mod utility_types;
#[doc(inline)]
pub use navigation_message::{NavigationMessage, NavigationMessageDiscriminant};
#[doc(inline)]
pub use navigation_message_handler::NavigationMessageHandler;
pub use navigation_message_handler::{NavigationMessageData, NavigationMessageHandler};

View file

@ -2,10 +2,9 @@ use crate::messages::input_mapper::utility_types::input_keyboard::Key;
use crate::messages::prelude::*;
use glam::DVec2;
use serde::{Deserialize, Serialize};
#[impl_message(Message, DocumentMessage, Navigation)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum NavigationMessage {
// Messages
DecreaseCanvasZoom {

View file

@ -5,69 +5,49 @@ use crate::consts::{
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion};
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
use crate::messages::portfolio::document::navigation::utility_types::NavigationOperation;
use crate::messages::portfolio::document::utility_types::document_metadata::DocumentMetadata;
use crate::messages::portfolio::document::utility_types::misc::PTZ;
use crate::messages::prelude::*;
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use glam::{DAffine2, DVec2};
use serde::{Deserialize, Serialize};
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
enum TransformOperation {
#[default]
None,
Pan {
pre_commit_pan: DVec2,
},
Rotate {
pre_commit_tilt: f64,
snap_tilt: bool,
snap_tilt_released: bool,
},
Zoom {
pre_commit_zoom: f64,
snap_zoom_enabled: bool,
},
pub struct NavigationMessageData<'a> {
pub metadata: &'a DocumentMetadata,
pub document_bounds: Option<[DVec2; 2]>,
pub ipp: &'a InputPreprocessorMessageHandler,
pub selection_bounds: Option<[DVec2; 2]>,
pub ptz: &'a mut PTZ,
}
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Default)]
pub struct NavigationMessageHandler {
transform_operation: TransformOperation,
navigation_operation: NavigationOperation,
mouse_position: ViewportPosition,
finish_operation_with_click: bool,
}
impl Default for NavigationMessageHandler {
fn default() -> Self {
Self {
mouse_position: ViewportPosition::default(),
finish_operation_with_click: false,
transform_operation: TransformOperation::None,
}
}
}
impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &InputPreprocessorMessageHandler, Option<[DVec2; 2]>, &mut PTZ)> for NavigationMessageHandler {
fn process_message(
&mut self,
message: NavigationMessage,
responses: &mut VecDeque<Message>,
(metadata, document_bounds, ipp, selection_bounds, ptz): (&DocumentMetadata, Option<[DVec2; 2]>, &InputPreprocessorMessageHandler, Option<[DVec2; 2]>, &mut PTZ),
) {
use NavigationMessage::*;
impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for NavigationMessageHandler {
fn process_message(&mut self, message: NavigationMessage, responses: &mut VecDeque<Message>, data: NavigationMessageData) {
let NavigationMessageData {
metadata,
document_bounds,
ipp,
selection_bounds,
ptz,
} = data;
let old_zoom = ptz.zoom;
match message {
DecreaseCanvasZoom { center_on_mouse } => {
NavigationMessage::DecreaseCanvasZoom { center_on_mouse } => {
let new_scale = *VIEWPORT_ZOOM_LEVELS.iter().rev().find(|scale| **scale < ptz.zoom).unwrap_or(&ptz.zoom);
if center_on_mouse {
responses.add(self.center_zoom(ipp.viewport_bounds.size(), new_scale / ptz.zoom, ipp.mouse.position));
}
responses.add(SetCanvasZoom { zoom_factor: new_scale });
responses.add(NavigationMessage::SetCanvasZoom { zoom_factor: new_scale });
}
FitViewportToBounds {
NavigationMessage::FitViewportToBounds {
bounds: [pos1, pos2],
prevent_zoom_past_100,
} => {
@ -91,35 +71,35 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
responses.add(PortfolioMessage::UpdateDocumentWidgets);
self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses);
}
FitViewportToSelection => {
NavigationMessage::FitViewportToSelection => {
if let Some(bounds) = selection_bounds {
let transform = metadata.document_to_viewport.inverse();
responses.add(FitViewportToBounds {
responses.add(NavigationMessage::FitViewportToBounds {
bounds: [transform.transform_point2(bounds[0]), transform.transform_point2(bounds[1])],
prevent_zoom_past_100: false,
})
}
}
IncreaseCanvasZoom { center_on_mouse } => {
NavigationMessage::IncreaseCanvasZoom { center_on_mouse } => {
let new_scale = *VIEWPORT_ZOOM_LEVELS.iter().find(|scale| **scale > ptz.zoom).unwrap_or(&ptz.zoom);
if center_on_mouse {
responses.add(self.center_zoom(ipp.viewport_bounds.size(), new_scale / ptz.zoom, ipp.mouse.position));
}
responses.add(SetCanvasZoom { zoom_factor: new_scale });
responses.add(NavigationMessage::SetCanvasZoom { zoom_factor: new_scale });
}
PointerMove {
NavigationMessage::PointerMove {
snap_angle,
wait_for_snap_angle_release,
snap_zoom,
zoom_from_viewport,
} => {
match self.transform_operation {
TransformOperation::None => {}
TransformOperation::Pan { .. } => {
match self.navigation_operation {
NavigationOperation::None => {}
NavigationOperation::Pan { .. } => {
let delta = ipp.mouse.position - self.mouse_position;
responses.add(TranslateCanvas { delta });
responses.add(NavigationMessage::TranslateCanvas { delta });
}
TransformOperation::Rotate {
NavigationOperation::Rotate {
snap_tilt,
snap_tilt_released,
pre_commit_tilt,
@ -131,7 +111,7 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
if !new_snap && snap_tilt {
ptz.tilt = self.snapped_angle(ptz.tilt);
}
self.transform_operation = TransformOperation::Rotate {
self.navigation_operation = NavigationOperation::Rotate {
pre_commit_tilt,
snap_tilt: new_snap,
snap_tilt_released: true,
@ -145,9 +125,9 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
start_offset.angle_between(end_offset)
};
responses.add(SetCanvasTilt { angle_radians: ptz.tilt + rotation });
responses.add(NavigationMessage::SetCanvasTilt { angle_radians: ptz.tilt + rotation });
}
TransformOperation::Zoom { snap_zoom_enabled, pre_commit_zoom } => {
NavigationOperation::Zoom { snap_zoom_enabled, pre_commit_zoom } => {
let zoom_start = self.snapped_scale(ptz.zoom);
let new_snap = ipp.keyboard.get(snap_zoom as usize);
@ -157,7 +137,7 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
}
if snap_zoom_enabled != new_snap {
self.transform_operation = TransformOperation::Zoom {
self.navigation_operation = NavigationOperation::Zoom {
pre_commit_zoom,
snap_zoom_enabled: new_snap,
};
@ -172,23 +152,23 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
if let Some(mouse) = zoom_from_viewport {
let zoom_factor = self.snapped_scale(ptz.zoom) / zoom_start;
responses.add(SetCanvasZoom { zoom_factor: ptz.zoom });
responses.add(NavigationMessage::SetCanvasZoom { zoom_factor: ptz.zoom });
responses.add(self.center_zoom(ipp.viewport_bounds.size(), zoom_factor, mouse));
} else {
responses.add(SetCanvasZoom { zoom_factor: ptz.zoom });
responses.add(NavigationMessage::SetCanvasZoom { zoom_factor: ptz.zoom });
}
}
}
self.mouse_position = ipp.mouse.position;
}
ResetCanvasTiltAndZoomTo100Percent => {
NavigationMessage::ResetCanvasTiltAndZoomTo100Percent => {
ptz.tilt = 0.;
ptz.zoom = 1.;
responses.add(PortfolioMessage::UpdateDocumentWidgets);
self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses);
}
RotateCanvasBegin { was_dispatched_from_menu } => {
NavigationMessage::RotateCanvasBegin { was_dispatched_from_menu } => {
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
responses.add(FrontendMessage::UpdateInputHints {
hint_data: HintData(vec![
@ -205,7 +185,7 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
]),
});
self.transform_operation = TransformOperation::Rotate {
self.navigation_operation = NavigationOperation::Rotate {
pre_commit_tilt: ptz.tilt,
snap_tilt_released: false,
snap_tilt: false,
@ -214,30 +194,30 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
self.mouse_position = ipp.mouse.position;
self.finish_operation_with_click = was_dispatched_from_menu;
}
SetCanvasTilt { angle_radians } => {
NavigationMessage::SetCanvasTilt { angle_radians } => {
ptz.tilt = angle_radians;
self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses);
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
SetCanvasZoom { zoom_factor } => {
NavigationMessage::SetCanvasZoom { zoom_factor } => {
ptz.zoom = zoom_factor.clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX);
ptz.zoom *= Self::clamp_zoom(ptz.zoom, document_bounds, old_zoom, ipp);
responses.add(PortfolioMessage::UpdateDocumentWidgets);
self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses);
}
TransformCanvasEnd { abort_transform } => {
NavigationMessage::TransformCanvasEnd { abort_transform } => {
if abort_transform {
match self.transform_operation {
TransformOperation::None => {}
TransformOperation::Rotate { pre_commit_tilt, .. } => {
match self.navigation_operation {
NavigationOperation::None => {}
NavigationOperation::Rotate { pre_commit_tilt, .. } => {
ptz.tilt = pre_commit_tilt;
responses.add(SetCanvasTilt { angle_radians: pre_commit_tilt });
responses.add(NavigationMessage::SetCanvasTilt { angle_radians: pre_commit_tilt });
}
TransformOperation::Pan { pre_commit_pan, .. } => {
NavigationOperation::Pan { pre_commit_pan, .. } => {
ptz.pan = pre_commit_pan;
self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses);
}
TransformOperation::Zoom { pre_commit_zoom, .. } => {
NavigationOperation::Zoom { pre_commit_zoom, .. } => {
ptz.zoom = pre_commit_zoom;
responses.add(PortfolioMessage::UpdateDocumentWidgets);
self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses);
@ -250,21 +230,21 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
responses.add(BroadcastEvent::CanvasTransformed);
responses.add(ToolMessage::UpdateCursor);
responses.add(ToolMessage::UpdateHints);
self.transform_operation = TransformOperation::None;
self.navigation_operation = NavigationOperation::None;
}
TransformFromMenuEnd { commit_key } => {
NavigationMessage::TransformFromMenuEnd { commit_key } => {
let abort_transform = commit_key == Key::Rmb;
self.finish_operation_with_click = false;
responses.add(TransformCanvasEnd { abort_transform });
responses.add(NavigationMessage::TransformCanvasEnd { abort_transform });
}
TranslateCanvas { delta } => {
NavigationMessage::TranslateCanvas { delta } => {
let transformed_delta = metadata.document_to_viewport.inverse().transform_vector2(delta);
ptz.pan += transformed_delta;
responses.add(BroadcastEvent::CanvasTransformed);
self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses);
}
TranslateCanvasBegin => {
NavigationMessage::TranslateCanvasBegin => {
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Grabbing });
responses.add(FrontendMessage::UpdateInputHints {
@ -273,22 +253,22 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
});
self.mouse_position = ipp.mouse.position;
self.transform_operation = TransformOperation::Pan { pre_commit_pan: ptz.pan };
self.navigation_operation = NavigationOperation::Pan { pre_commit_pan: ptz.pan };
}
TranslateCanvasByViewportFraction { delta } => {
NavigationMessage::TranslateCanvasByViewportFraction { delta } => {
let transformed_delta = metadata.document_to_viewport.inverse().transform_vector2(delta * ipp.viewport_bounds.size());
ptz.pan += transformed_delta;
self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses);
}
WheelCanvasTranslate { use_y_as_x } => {
NavigationMessage::WheelCanvasTranslate { use_y_as_x } => {
let delta = match use_y_as_x {
false => -ipp.mouse.scroll_delta.as_dvec2(),
true => (-ipp.mouse.scroll_delta.y as f64, 0.).into(),
} * VIEWPORT_SCROLL_RATE;
responses.add(TranslateCanvas { delta });
responses.add(NavigationMessage::TranslateCanvas { delta });
}
WheelCanvasZoom => {
NavigationMessage::WheelCanvasZoom => {
let scroll = ipp.mouse.scroll_delta.scroll_delta();
let mut zoom_factor = 1. + scroll.abs() * VIEWPORT_ZOOM_WHEEL_RATE;
if ipp.mouse.scroll_delta.y > 0 {
@ -297,9 +277,9 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
zoom_factor *= Self::clamp_zoom(ptz.zoom * zoom_factor, document_bounds, old_zoom, ipp);
responses.add(self.center_zoom(ipp.viewport_bounds.size(), zoom_factor, ipp.mouse.position));
responses.add(SetCanvasZoom { zoom_factor: ptz.zoom * zoom_factor });
responses.add(NavigationMessage::SetCanvasZoom { zoom_factor: ptz.zoom * zoom_factor });
}
ZoomCanvasBegin => {
NavigationMessage::ZoomCanvasBegin => {
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::ZoomIn });
responses.add(FrontendMessage::UpdateInputHints {
hint_data: HintData(vec![
@ -316,7 +296,7 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
]),
});
self.transform_operation = TransformOperation::Zoom {
self.navigation_operation = NavigationOperation::Zoom {
pre_commit_zoom: ptz.zoom,
snap_zoom_enabled: false,
};
@ -340,7 +320,7 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
FitViewportToSelection,
);
if self.transform_operation != TransformOperation::None {
if self.navigation_operation != NavigationOperation::None {
let transforming = actions!(NavigationMessageDiscriminant;
PointerMove,
TransformCanvasEnd,
@ -363,7 +343,7 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
impl NavigationMessageHandler {
pub fn snapped_angle(&self, tilt: f64) -> f64 {
let increment_radians: f64 = VIEWPORT_ROTATE_SNAP_INTERVAL.to_radians();
if let TransformOperation::Rotate { snap_tilt: true, .. } = self.transform_operation {
if let NavigationOperation::Rotate { snap_tilt: true, .. } = self.navigation_operation {
(tilt / increment_radians).round() * increment_radians
} else {
tilt
@ -371,7 +351,7 @@ impl NavigationMessageHandler {
}
pub fn snapped_scale(&self, zoom: f64) -> f64 {
if let TransformOperation::Zoom { snap_zoom_enabled: true, .. } = self.transform_operation {
if let NavigationOperation::Zoom { snap_zoom_enabled: true, .. } = self.navigation_operation {
*VIEWPORT_ZOOM_LEVELS.iter().min_by(|a, b| (**a - zoom).abs().partial_cmp(&(**b - zoom).abs()).unwrap()).unwrap_or(&zoom)
} else {
zoom

View file

@ -0,0 +1,19 @@
use glam::DVec2;
#[derive(Debug, Default, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum NavigationOperation {
#[default]
None,
Pan {
pre_commit_pan: DVec2,
},
Rotate {
pre_commit_tilt: f64,
snap_tilt: bool,
snap_tilt_released: bool,
},
Zoom {
pre_commit_zoom: f64,
snap_zoom_enabled: bool,
},
}

View file

@ -1,4 +1,5 @@
use super::{node_properties, FrontendGraphDataType, FrontendNodeType};
use super::node_properties;
use super::utility_types::{FrontendGraphDataType, FrontendNodeType};
use crate::consts::{DEFAULT_FONT_FAMILY, DEFAULT_FONT_STYLE};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::utility_types::document_metadata::DocumentMetadata;
@ -7,12 +8,10 @@ use crate::messages::prelude::Message;
use crate::node_graph_executor::NodeGraphExecutor;
use graph_craft::concrete;
use graph_craft::document::value::*;
use graph_craft::document::*;
use graph_craft::document::{value::*, DocumentNodeMetadata};
use graph_craft::imaginate_input::ImaginateSamplingMethod;
use graph_craft::ProtoNodeIdentifier;
#[cfg(feature = "gpu")]
use graphene_core::application_io::SurfaceHandle;
use graphene_core::raster::brush_cache::BrushCache;
use graphene_core::raster::{
BlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, Image, ImageFrame, LuminanceCalculation, NoiseType, RedGreenBlue, RelativeAbsolute,
@ -22,14 +21,12 @@ use graphene_core::text::Font;
use graphene_core::transform::Footprint;
use graphene_core::vector::VectorData;
use graphene_core::*;
#[cfg(feature = "gpu")]
use gpu_executor::*;
use graphene_std::wasm_application_io::WasmEditorApi;
#[cfg(feature = "gpu")]
use {gpu_executor::*, graphene_core::application_io::SurfaceHandle, wgpu_executor::WgpuExecutor};
use once_cell::sync::Lazy;
use std::collections::VecDeque;
#[cfg(feature = "gpu")]
use wgpu_executor::WgpuExecutor;
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct DocumentInputType {

View file

@ -1,15 +1,10 @@
pub mod document_node_types;
mod node_graph_message;
mod node_graph_message_handler;
pub mod node_properties;
pub mod utility_types;
#[doc(inline)]
pub use node_graph_message::{NodeGraphMessage, NodeGraphMessageDiscriminant};
#[doc(inline)]
pub use node_graph_message_handler::*;
mod graph_operation_message;
mod graph_operation_message_handler;
#[doc(inline)]
pub use graph_operation_message::*;
#[doc(inline)]
pub use graph_operation_message_handler::*;

View file

@ -1,4 +1,5 @@
use crate::messages::prelude::*;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
use graph_craft::proto::GraphErrors;

View file

@ -1,523 +1,21 @@
pub use self::document_node_types::*;
use super::load_network_structure;
use crate::application::generate_uuid;
use crate::messages::input_mapper::utility_types::macros::action_keys;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerClassification, LayerPanelEntry, SelectedNodes};
use crate::messages::prelude::*;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, NodeId, NodeInput, NodeNetwork, NodeOutput, Source};
use graph_craft::proto::GraphErrors;
use graphene_core::*;
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypes;
mod document_node_types;
mod node_properties;
use super::utility_types::{FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeLink};
use super::{document_node_types, node_properties};
use crate::application::generate_uuid;
use crate::messages::input_mapper::utility_types::macros::action_keys;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::graph_operation::load_network_structure;
use crate::messages::portfolio::document::node_graph::document_node_types::{resolve_document_node_type, DocumentInputType, NodePropertiesContext};
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerClassification, LayerPanelEntry, SelectedNodes};
use crate::messages::prelude::*;
use glam::IVec2;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum FrontendGraphDataType {
#[default]
#[serde(rename = "general")]
General,
#[serde(rename = "raster")]
Raster,
#[serde(rename = "color")]
Color,
#[serde(rename = "general")]
Text,
#[serde(rename = "vector")]
Subpath,
#[serde(rename = "number")]
Number,
#[serde(rename = "general")]
Boolean,
/// Refers to the mathematical vector, with direction and magnitude.
#[serde(rename = "number")]
Vector,
#[serde(rename = "raster")]
GraphicGroup,
#[serde(rename = "artboard")]
Artboard,
#[serde(rename = "color")]
Palette,
}
impl FrontendGraphDataType {
pub const fn with_tagged_value(value: &TaggedValue) -> Self {
match value {
TaggedValue::String(_) => Self::Text,
TaggedValue::F32(_) | TaggedValue::F64(_) | TaggedValue::U32(_) | TaggedValue::DAffine2(_) => Self::Number,
TaggedValue::Bool(_) => Self::Boolean,
TaggedValue::DVec2(_) | TaggedValue::IVec2(_) => Self::Vector,
TaggedValue::Image(_) => Self::Raster,
TaggedValue::ImageFrame(_) => Self::Raster,
TaggedValue::Color(_) => Self::Color,
TaggedValue::RcSubpath(_) | TaggedValue::Subpaths(_) | TaggedValue::VectorData(_) => Self::Subpath,
TaggedValue::GraphicGroup(_) => Self::GraphicGroup,
TaggedValue::Artboard(_) => Self::Artboard,
TaggedValue::Palette(_) => Self::Palette,
_ => Self::General,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendGraphInput {
#[serde(rename = "dataType")]
data_type: FrontendGraphDataType,
name: String,
#[serde(rename = "resolvedType")]
resolved_type: Option<String>,
connected: Option<NodeId>,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendGraphOutput {
#[serde(rename = "dataType")]
data_type: FrontendGraphDataType,
name: String,
#[serde(rename = "resolvedType")]
resolved_type: Option<String>,
connected: Option<NodeId>,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendNode {
pub id: graph_craft::document::NodeId,
#[serde(rename = "isLayer")]
pub is_layer: bool,
pub alias: String,
pub name: String,
#[serde(rename = "primaryInput")]
pub primary_input: Option<FrontendGraphInput>,
#[serde(rename = "exposedInputs")]
pub exposed_inputs: Vec<FrontendGraphInput>,
#[serde(rename = "primaryOutput")]
pub primary_output: Option<FrontendGraphOutput>,
#[serde(rename = "exposedOutputs")]
pub exposed_outputs: Vec<FrontendGraphOutput>,
pub position: (i32, i32),
pub disabled: bool,
pub previewed: bool,
pub errors: Option<String>,
}
// (link_start, link_end, link_end_input_index)
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendNodeLink {
#[serde(rename = "linkStart")]
pub link_start: NodeId,
#[serde(rename = "linkStartOutputIndex")]
pub link_start_output_index: usize,
#[serde(rename = "linkEnd")]
pub link_end: NodeId,
#[serde(rename = "linkEndInputIndex")]
pub link_end_input_index: usize,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendNodeType {
pub name: String,
pub category: String,
}
impl FrontendNodeType {
pub fn new(name: &'static str, category: &'static str) -> Self {
Self {
name: name.to_string(),
category: category.to_string(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct NodeGraphMessageHandler {
pub network: Vec<NodeId>,
pub resolved_types: ResolvedDocumentNodeTypes,
pub node_graph_errors: GraphErrors,
has_selection: bool,
widgets: [LayoutGroup; 2],
}
impl Default for NodeGraphMessageHandler {
fn default() -> Self {
let right_side_widgets = vec![
// TODO: Replace this with an "Add Node" button, also next to an "Add Layer" button
TextLabel::new("Right Click in Graph to Add Nodes").italic(true).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
TextButton::new("Node Graph")
.icon(Some("GraphViewOpen".into()))
.tooltip("Hide Node Graph")
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::GraphViewOverlayToggle))
.on_update(move |_| DocumentMessage::GraphViewOverlayToggle.into())
.widget_holder(),
];
Self {
network: Vec::new(),
resolved_types: ResolvedDocumentNodeTypes::default(),
node_graph_errors: Vec::new(),
has_selection: false,
widgets: [LayoutGroup::Row { widgets: Vec::new() }, LayoutGroup::Row { widgets: right_side_widgets }],
}
}
}
impl NodeGraphMessageHandler {
/// Send the cached layout to the frontend for the options bar at the top of the node panel
fn send_node_bar_layout(&self, responses: &mut VecDeque<Message>) {
responses.add(LayoutMessage::SendLayout {
layout: Layout::WidgetLayout(WidgetLayout::new(self.widgets.to_vec())),
layout_target: LayoutTarget::NodeGraphBar,
});
}
/// Updates the buttons for disable and preview
fn update_selection_action_buttons(&mut self, document_network: &NodeNetwork, selected_nodes: &SelectedNodes, responses: &mut VecDeque<Message>) {
if let Some(network) = document_network.nested_network(&self.network) {
let mut widgets = Vec::new();
// Don't allow disabling input or output nodes
let mut selection = selected_nodes.selected_nodes().filter(|&&id| !network.imports.contains(&id) && !network.original_outputs_contain(id));
// If there is at least one other selected node then show the hide or show button
if selection.next().is_some() {
// Check if any of the selected nodes are disabled
let is_hidden = selected_nodes.selected_nodes().any(|id| network.disabled.contains(id));
// Check if multiple nodes are selected
let multiple_nodes = selection.next().is_some();
// Generate the enable or disable button accordingly
let (hide_show_label, hide_show_icon) = if is_hidden { ("Make Visible", "EyeHidden") } else { ("Make Hidden", "EyeVisible") };
let hide_button = TextButton::new(hide_show_label)
.icon(Some(hide_show_icon.to_string()))
.tooltip(if is_hidden { "Show selected nodes/layers" } else { "Hide selected nodes/layers" }.to_string() + if multiple_nodes { "s" } else { "" })
.tooltip_shortcut(action_keys!(NodeGraphMessageDiscriminant::ToggleSelectedHidden))
.on_update(move |_| NodeGraphMessage::ToggleSelectedHidden.into())
.widget_holder();
widgets.push(hide_button);
widgets.push(Separator::new(SeparatorType::Related).widget_holder());
}
// If only one node is selected then show the preview or stop previewing button
let mut selection = selected_nodes.selected_nodes();
if let (Some(&node_id), None) = (selection.next(), selection.next()) {
// Is this node the current output
let is_output = network.outputs_contain(node_id);
// Don't show stop previewing button on the original output node
if !(is_output && network.previous_outputs_contain(node_id).unwrap_or(true)) {
let output_button = TextButton::new(if is_output { "End Preview" } else { "Preview" })
.icon(Some("Rescale".to_string()))
.tooltip(if is_output { "Restore preview to the graph output" } else { "Preview selected node/layer" }.to_string() + " (Shortcut: Alt-click node/layer)")
.on_update(move |_| NodeGraphMessage::TogglePreview { node_id }.into())
.widget_holder();
widgets.push(output_button);
}
}
self.widgets[0] = LayoutGroup::Row { widgets };
}
self.send_node_bar_layout(responses);
}
/// Collate the properties panel sections for a node graph
pub fn collate_properties(&self, context: &mut NodePropertiesContext, selected_nodes: &SelectedNodes) -> Vec<LayoutGroup> {
let mut network = context.network;
for segment in &self.network {
network = network.nodes.get(segment).and_then(|node| node.implementation.get_network()).unwrap();
}
// We want:
// - If only nodes (no layers) are selected: display each node's properties
// - If one layer is selected, and zero or more of its upstream nodes: display the properties for the layer and its upstream nodes
// - If multiple layers are selected, or one node plus other non-upstream nodes: display nothing
// First, we filter all the selections into layers and nodes
let (mut layers, mut nodes) = (Vec::new(), Vec::new());
for node_id in selected_nodes.selected_nodes() {
if let Some(layer_or_node) = network.nodes.get(node_id) {
if layer_or_node.is_layer() {
layers.push(*node_id);
} else {
nodes.push(*node_id);
}
};
}
// Next, we decide what to display based on the number of layers and nodes selected
match layers.len() {
// If no layers are selected, show properties for all selected nodes
0 => nodes
.iter()
.filter_map(|node_id| network.nodes.get(node_id).map(|node| node_properties::generate_node_properties(node, *node_id, context)))
.collect(),
// If one layer is selected, filter out all selected nodes that are not upstream of it. If there are no nodes left, show properties for the layer. Otherwise, show nothing.
1 => {
let nodes_not_upstream_of_layer = nodes
.into_iter()
.filter(|&selected_node_id| !network.is_node_upstream_of_another_by_primary_flow(layers[0], selected_node_id));
if nodes_not_upstream_of_layer.count() > 0 {
return Vec::new();
}
// 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)
network
.upstream_flow_back_from_nodes(vec![layers[0]], true)
.enumerate()
.take_while(|(i, (node, _))| if *i == 0 { true } else { !node.is_layer() })
.map(|(_, (node, node_id))| node_properties::generate_node_properties(node, node_id, context))
.collect()
}
// If multiple layers and/or nodes are selected, show nothing
_ => Vec::new(),
}
}
fn collect_links(network: &NodeNetwork) -> Vec<FrontendNodeLink> {
network
.nodes
.iter()
.flat_map(|(link_end, node)| node.inputs.iter().filter(|input| input.is_exposed()).enumerate().map(move |(index, input)| (input, link_end, index)))
.filter_map(|(input, &link_end, link_end_input_index)| {
if let NodeInput::Node {
node_id: link_start,
output_index: link_start_output_index,
// TODO: add ui for lambdas
lambda: _,
} = *input
{
Some(FrontendNodeLink {
link_start,
link_start_output_index,
link_end,
link_end_input_index,
})
} else {
None
}
})
.collect::<Vec<_>>()
}
fn collect_nodes(&self, links: &[FrontendNodeLink], network: &NodeNetwork) -> Vec<FrontendNode> {
let connected_node_to_output_lookup = links.iter().map(|link| ((link.link_start, link.link_start_output_index), link.link_end)).collect::<HashMap<_, _>>();
let mut nodes = Vec::new();
for (&node_id, node) in &network.nodes {
let node_path = vec![node_id];
// TODO: This should be based on the graph runtime type inference system in order to change the colors of node connectors to match the data type in use
let Some(document_node_definition) = document_node_types::resolve_document_node_type(&node.name) else {
warn!("Node '{}' does not exist in library", node.name);
continue;
};
// Inputs
let mut inputs = {
let frontend_graph_inputs = document_node_definition.inputs.iter().enumerate().map(|(index, input_type)| {
// Convert the index in all inputs to the index in only the exposed inputs
let index = node.inputs.iter().take(index).filter(|input| input.is_exposed()).count();
FrontendGraphInput {
data_type: input_type.data_type,
name: input_type.name.to_string(),
resolved_type: self.resolved_types.inputs.get(&Source { node: node_path.clone(), index }).map(|input| format!("{input:?}")),
connected: None,
}
});
node.inputs.iter().zip(frontend_graph_inputs).map(|(node_input, mut frontend_graph_input)| {
if let NodeInput::Node { node_id: connected_node_id, .. } = node_input {
frontend_graph_input.connected = Some(*connected_node_id);
}
(node_input, frontend_graph_input)
})
};
let primary_input = inputs.next().filter(|(input, _)| input.is_exposed()).map(|(_, input_type)| input_type);
let exposed_inputs = inputs.filter(|(input, _)| input.is_exposed()).map(|(_, input_type)| input_type).collect();
// Outputs
let mut outputs = document_node_definition.outputs.iter().enumerate().map(|(index, output_type)| FrontendGraphOutput {
data_type: output_type.data_type,
name: output_type.name.to_string(),
resolved_type: self.resolved_types.outputs.get(&Source { node: node_path.clone(), index }).map(|output| format!("{output:?}")),
connected: connected_node_to_output_lookup.get(&(node_id, index)).copied(),
});
let primary_output = node.has_primary_output.then(|| outputs.next()).flatten();
let exposed_outputs = outputs.collect::<Vec<_>>();
// Errors
let errors = self.node_graph_errors.iter().find(|error| error.node_path.starts_with(&node_path)).map(|error| error.error.clone());
nodes.push(FrontendNode {
id: node_id,
is_layer: node.is_layer(),
alias: node.alias.clone(),
name: node.name.clone(),
primary_input,
exposed_inputs,
primary_output,
exposed_outputs,
position: node.metadata.position.into(),
previewed: network.outputs_contain(node_id),
disabled: network.disabled.contains(&node_id),
errors: errors.map(|e| format!("{e:?}")),
});
}
nodes
}
fn update_layer_panel(network: &NodeNetwork, metadata: &DocumentMetadata, collapsed: &CollapsedLayers, responses: &mut VecDeque<Message>) {
for (&node_id, node) in &network.nodes {
if node.is_layer() {
let layer = LayerNodeIdentifier::new(node_id, network);
let layer_classification = {
if metadata.is_artboard(layer) {
LayerClassification::Artboard
} else if metadata.is_folder(layer) {
LayerClassification::Folder
} else {
LayerClassification::Layer
}
};
let data = LayerPanelEntry {
id: node_id,
layer_classification,
expanded: layer.has_children(metadata) && !collapsed.0.contains(&layer),
depth: layer.ancestors(metadata).count() - 1,
parent_id: layer.parent(metadata).map(|parent| parent.to_node()),
name: network.nodes.get(&node_id).map(|node| node.alias.clone()).unwrap_or_default(),
tooltip: if cfg!(debug_assertions) { format!("Layer ID: {node_id}") } else { "".into() },
disabled: network.disabled.contains(&node_id),
};
responses.add(FrontendMessage::UpdateDocumentLayerDetails { data });
}
}
}
fn send_graph(&self, network: &NodeNetwork, graph_open: bool, metadata: &mut DocumentMetadata, selected_nodes: &mut SelectedNodes, collapsed: &CollapsedLayers, responses: &mut VecDeque<Message>) {
metadata.load_structure(network, selected_nodes);
responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(PropertiesPanelMessage::Refresh);
Self::update_layer_panel(network, metadata, collapsed, responses);
if graph_open {
let links = Self::collect_links(network);
let nodes = self.collect_nodes(&links, network);
responses.add(FrontendMessage::UpdateNodeGraph { nodes, links });
}
}
/// Updates the frontend's selection state in line with the backend
fn update_selected(&mut self, document_network: &NodeNetwork, selected_nodes: &SelectedNodes, responses: &mut VecDeque<Message>) {
self.update_selection_action_buttons(document_network, selected_nodes, responses);
responses.add(FrontendMessage::UpdateNodeGraphSelection {
selected: selected_nodes.selected_nodes_ref().clone(),
});
}
fn remove_references_from_network(network: &mut NodeNetwork, deleting_node_id: NodeId, reconnect: bool) -> bool {
if network.imports.contains(&deleting_node_id) {
warn!("Deleting input node!");
return false;
}
if network.outputs_contain(deleting_node_id) {
warn!("Deleting the output node!");
return false;
}
let mut reconnect_to_input: Option<NodeInput> = None;
if reconnect {
// Check whether the being-deleted node's first (primary) input is a node
if let Some(node) = network.nodes.get(&deleting_node_id) {
// Reconnect to the node below when deleting a layer node.
let reconnect_from_input_index = if node.is_layer() { 1 } else { 0 };
if matches!(&node.inputs.get(reconnect_from_input_index), Some(NodeInput::Node { .. })) {
reconnect_to_input = Some(node.inputs[reconnect_from_input_index].clone());
}
}
}
for (node_id, node) in network.nodes.iter_mut() {
if *node_id == deleting_node_id {
continue;
}
for (input_index, input) in node.inputs.iter_mut().enumerate() {
let NodeInput::Node {
node_id: upstream_node_id,
output_index,
..
} = input
else {
continue;
};
if *upstream_node_id != deleting_node_id {
continue;
}
let Some(node_type) = document_node_types::resolve_document_node_type(&node.name) else {
warn!("Removing input of invalid node type '{}'", node.name);
return false;
};
if let NodeInput::Value { tagged_value, .. } = &node_type.inputs[input_index].default {
let mut refers_to_output_node = false;
// Use the first input node as the new input if deleting node's first input is a node,
// and the current node uses its primary output too
if let Some(reconnect_to_input) = &reconnect_to_input {
if *output_index == 0 {
refers_to_output_node = true;
*input = reconnect_to_input.clone()
}
}
if !refers_to_output_node {
*input = NodeInput::value(tagged_value.clone(), true);
}
}
}
}
true
}
/// Tries to remove a node from the network, returning true on success.
fn remove_node(&mut self, document_network: &mut NodeNetwork, selected_nodes: &mut SelectedNodes, node_id: NodeId, responses: &mut VecDeque<Message>, reconnect: bool) -> bool {
let Some(network) = document_network.nested_network_mut(&self.network) else {
return false;
};
if !Self::remove_references_from_network(network, node_id, reconnect) {
return false;
}
network.nodes.remove(&node_id);
selected_nodes.retain_selected_nodes(|&id| id != node_id);
responses.add(BroadcastEvent::SelectionChanged);
true
}
/// Gets the default node input based on the node name and the input index
pub fn default_node_input(name: String, index: usize) -> Option<NodeInput> {
resolve_document_node_type(&name)
.and_then(|node| node.inputs.get(index))
.map(|input: &DocumentInputType| input.default.clone())
}
/// Returns an iterator of nodes to be copied and their ids, excluding output and input nodes
pub fn copy_nodes<'a>(network: &'a NodeNetwork, new_ids: &'a HashMap<NodeId, NodeId>) -> impl Iterator<Item = (NodeId, DocumentNode)> + 'a {
new_ids
.iter()
.filter(|&(&id, _)| !network.outputs_contain(id))
.filter_map(|(&id, &new)| network.nodes.get(&id).map(|node| (new, node.clone())))
.map(move |(new, node)| (new, node.map_ids(Self::default_node_input, new_ids)))
}
}
#[derive(Debug)]
pub struct NodeGraphHandlerData<'a> {
pub document_network: &'a mut NodeNetwork,
@ -530,6 +28,15 @@ pub struct NodeGraphHandlerData<'a> {
pub graph_view_overlay_open: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub struct NodeGraphMessageHandler {
pub network: Vec<NodeId>,
pub resolved_types: ResolvedDocumentNodeTypes,
pub node_graph_errors: GraphErrors,
has_selection: bool,
widgets: [LayoutGroup; 2],
}
impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGraphMessageHandler {
fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque<Message>, data: NodeGraphHandlerData<'a>) {
let NodeGraphHandlerData {
@ -541,6 +48,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
graph_view_overlay_open,
..
} = data;
match message {
// TODO: automatically remove broadcast messages.
NodeGraphMessage::Init => {
@ -1037,4 +545,380 @@ impl NodeGraphMessageHandler {
actions!(NodeGraphMessageDiscriminant;)
}
}
/// Send the cached layout to the frontend for the options bar at the top of the node panel
fn send_node_bar_layout(&self, responses: &mut VecDeque<Message>) {
responses.add(LayoutMessage::SendLayout {
layout: Layout::WidgetLayout(WidgetLayout::new(self.widgets.to_vec())),
layout_target: LayoutTarget::NodeGraphBar,
});
}
/// Updates the buttons for disable and preview
fn update_selection_action_buttons(&mut self, document_network: &NodeNetwork, selected_nodes: &SelectedNodes, responses: &mut VecDeque<Message>) {
if let Some(network) = document_network.nested_network(&self.network) {
let mut widgets = Vec::new();
// Don't allow disabling input or output nodes
let mut selection = selected_nodes.selected_nodes().filter(|&&id| !network.imports.contains(&id) && !network.original_outputs_contain(id));
// If there is at least one other selected node then show the hide or show button
if selection.next().is_some() {
// Check if any of the selected nodes are disabled
let is_hidden = selected_nodes.selected_nodes().any(|id| network.disabled.contains(id));
// Check if multiple nodes are selected
let multiple_nodes = selection.next().is_some();
// Generate the enable or disable button accordingly
let (hide_show_label, hide_show_icon) = if is_hidden { ("Make Visible", "EyeHidden") } else { ("Make Hidden", "EyeVisible") };
let hide_button = TextButton::new(hide_show_label)
.icon(Some(hide_show_icon.to_string()))
.tooltip(if is_hidden { "Show selected nodes/layers" } else { "Hide selected nodes/layers" }.to_string() + if multiple_nodes { "s" } else { "" })
.tooltip_shortcut(action_keys!(NodeGraphMessageDiscriminant::ToggleSelectedHidden))
.on_update(move |_| NodeGraphMessage::ToggleSelectedHidden.into())
.widget_holder();
widgets.push(hide_button);
widgets.push(Separator::new(SeparatorType::Related).widget_holder());
}
// If only one node is selected then show the preview or stop previewing button
let mut selection = selected_nodes.selected_nodes();
if let (Some(&node_id), None) = (selection.next(), selection.next()) {
// Is this node the current output
let is_output = network.outputs_contain(node_id);
// Don't show stop previewing button on the original output node
if !(is_output && network.previous_outputs_contain(node_id).unwrap_or(true)) {
let output_button = TextButton::new(if is_output { "End Preview" } else { "Preview" })
.icon(Some("Rescale".to_string()))
.tooltip(if is_output { "Restore preview to the graph output" } else { "Preview selected node/layer" }.to_string() + " (Shortcut: Alt-click node/layer)")
.on_update(move |_| NodeGraphMessage::TogglePreview { node_id }.into())
.widget_holder();
widgets.push(output_button);
}
}
self.widgets[0] = LayoutGroup::Row { widgets };
}
self.send_node_bar_layout(responses);
}
/// Collate the properties panel sections for a node graph
pub fn collate_properties(&self, context: &mut NodePropertiesContext, selected_nodes: &SelectedNodes) -> Vec<LayoutGroup> {
let mut network = context.network;
for segment in &self.network {
network = network.nodes.get(segment).and_then(|node| node.implementation.get_network()).unwrap();
}
// We want:
// - If only nodes (no layers) are selected: display each node's properties
// - If one layer is selected, and zero or more of its upstream nodes: display the properties for the layer and its upstream nodes
// - If multiple layers are selected, or one node plus other non-upstream nodes: display nothing
// First, we filter all the selections into layers and nodes
let (mut layers, mut nodes) = (Vec::new(), Vec::new());
for node_id in selected_nodes.selected_nodes() {
if let Some(layer_or_node) = network.nodes.get(node_id) {
if layer_or_node.is_layer() {
layers.push(*node_id);
} else {
nodes.push(*node_id);
}
};
}
// Next, we decide what to display based on the number of layers and nodes selected
match layers.len() {
// If no layers are selected, show properties for all selected nodes
0 => nodes
.iter()
.filter_map(|node_id| network.nodes.get(node_id).map(|node| node_properties::generate_node_properties(node, *node_id, context)))
.collect(),
// If one layer is selected, filter out all selected nodes that are not upstream of it. If there are no nodes left, show properties for the layer. Otherwise, show nothing.
1 => {
let nodes_not_upstream_of_layer = nodes
.into_iter()
.filter(|&selected_node_id| !network.is_node_upstream_of_another_by_primary_flow(layers[0], selected_node_id));
if nodes_not_upstream_of_layer.count() > 0 {
return Vec::new();
}
// 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)
network
.upstream_flow_back_from_nodes(vec![layers[0]], true)
.enumerate()
.take_while(|(i, (node, _))| if *i == 0 { true } else { !node.is_layer() })
.map(|(_, (node, node_id))| node_properties::generate_node_properties(node, node_id, context))
.collect()
}
// If multiple layers and/or nodes are selected, show nothing
_ => Vec::new(),
}
}
fn collect_links(network: &NodeNetwork) -> Vec<FrontendNodeLink> {
network
.nodes
.iter()
.flat_map(|(link_end, node)| node.inputs.iter().filter(|input| input.is_exposed()).enumerate().map(move |(index, input)| (input, link_end, index)))
.filter_map(|(input, &link_end, link_end_input_index)| {
if let NodeInput::Node {
node_id: link_start,
output_index: link_start_output_index,
// TODO: add ui for lambdas
lambda: _,
} = *input
{
Some(FrontendNodeLink {
link_start,
link_start_output_index,
link_end,
link_end_input_index,
})
} else {
None
}
})
.collect::<Vec<_>>()
}
fn collect_nodes(&self, links: &[FrontendNodeLink], network: &NodeNetwork) -> Vec<FrontendNode> {
let connected_node_to_output_lookup = links.iter().map(|link| ((link.link_start, link.link_start_output_index), link.link_end)).collect::<HashMap<_, _>>();
let mut nodes = Vec::new();
for (&node_id, node) in &network.nodes {
let node_path = vec![node_id];
// TODO: This should be based on the graph runtime type inference system in order to change the colors of node connectors to match the data type in use
let Some(document_node_definition) = document_node_types::resolve_document_node_type(&node.name) else {
warn!("Node '{}' does not exist in library", node.name);
continue;
};
// Inputs
let mut inputs = {
let frontend_graph_inputs = document_node_definition.inputs.iter().enumerate().map(|(index, input_type)| {
// Convert the index in all inputs to the index in only the exposed inputs
let index = node.inputs.iter().take(index).filter(|input| input.is_exposed()).count();
FrontendGraphInput {
data_type: input_type.data_type,
name: input_type.name.to_string(),
resolved_type: self.resolved_types.inputs.get(&Source { node: node_path.clone(), index }).map(|input| format!("{input:?}")),
connected: None,
}
});
node.inputs.iter().zip(frontend_graph_inputs).map(|(node_input, mut frontend_graph_input)| {
if let NodeInput::Node { node_id: connected_node_id, .. } = node_input {
frontend_graph_input.connected = Some(*connected_node_id);
}
(node_input, frontend_graph_input)
})
};
let primary_input = inputs.next().filter(|(input, _)| input.is_exposed()).map(|(_, input_type)| input_type);
let exposed_inputs = inputs.filter(|(input, _)| input.is_exposed()).map(|(_, input_type)| input_type).collect();
// Outputs
let mut outputs = document_node_definition.outputs.iter().enumerate().map(|(index, output_type)| FrontendGraphOutput {
data_type: output_type.data_type,
name: output_type.name.to_string(),
resolved_type: self.resolved_types.outputs.get(&Source { node: node_path.clone(), index }).map(|output| format!("{output:?}")),
connected: connected_node_to_output_lookup.get(&(node_id, index)).copied(),
});
let primary_output = node.has_primary_output.then(|| outputs.next()).flatten();
let exposed_outputs = outputs.collect::<Vec<_>>();
// Errors
let errors = self.node_graph_errors.iter().find(|error| error.node_path.starts_with(&node_path)).map(|error| error.error.clone());
nodes.push(FrontendNode {
id: node_id,
is_layer: node.is_layer(),
alias: node.alias.clone(),
name: node.name.clone(),
primary_input,
exposed_inputs,
primary_output,
exposed_outputs,
position: node.metadata.position.into(),
previewed: network.outputs_contain(node_id),
disabled: network.disabled.contains(&node_id),
errors: errors.map(|e| format!("{e:?}")),
});
}
nodes
}
fn update_layer_panel(network: &NodeNetwork, metadata: &DocumentMetadata, collapsed: &CollapsedLayers, responses: &mut VecDeque<Message>) {
for (&node_id, node) in &network.nodes {
if node.is_layer() {
let layer = LayerNodeIdentifier::new(node_id, network);
let layer_classification = {
if metadata.is_artboard(layer) {
LayerClassification::Artboard
} else if metadata.is_folder(layer) {
LayerClassification::Folder
} else {
LayerClassification::Layer
}
};
let data = LayerPanelEntry {
id: node_id,
layer_classification,
expanded: layer.has_children(metadata) && !collapsed.0.contains(&layer),
depth: layer.ancestors(metadata).count() - 1,
parent_id: layer.parent(metadata).map(|parent| parent.to_node()),
name: network.nodes.get(&node_id).map(|node| node.alias.clone()).unwrap_or_default(),
tooltip: if cfg!(debug_assertions) { format!("Layer ID: {node_id}") } else { "".into() },
disabled: network.disabled.contains(&node_id),
};
responses.add(FrontendMessage::UpdateDocumentLayerDetails { data });
}
}
}
fn send_graph(&self, network: &NodeNetwork, graph_open: bool, metadata: &mut DocumentMetadata, selected_nodes: &mut SelectedNodes, collapsed: &CollapsedLayers, responses: &mut VecDeque<Message>) {
metadata.load_structure(network, selected_nodes);
responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(PropertiesPanelMessage::Refresh);
Self::update_layer_panel(network, metadata, collapsed, responses);
if graph_open {
let links = Self::collect_links(network);
let nodes = self.collect_nodes(&links, network);
responses.add(FrontendMessage::UpdateNodeGraph { nodes, links });
}
}
/// Updates the frontend's selection state in line with the backend
fn update_selected(&mut self, document_network: &NodeNetwork, selected_nodes: &SelectedNodes, responses: &mut VecDeque<Message>) {
self.update_selection_action_buttons(document_network, selected_nodes, responses);
responses.add(FrontendMessage::UpdateNodeGraphSelection {
selected: selected_nodes.selected_nodes_ref().clone(),
});
}
fn remove_references_from_network(network: &mut NodeNetwork, deleting_node_id: NodeId, reconnect: bool) -> bool {
if network.imports.contains(&deleting_node_id) {
warn!("Deleting input node!");
return false;
}
if network.outputs_contain(deleting_node_id) {
warn!("Deleting the output node!");
return false;
}
let mut reconnect_to_input: Option<NodeInput> = None;
if reconnect {
// Check whether the being-deleted node's first (primary) input is a node
if let Some(node) = network.nodes.get(&deleting_node_id) {
// Reconnect to the node below when deleting a layer node.
let reconnect_from_input_index = if node.is_layer() { 1 } else { 0 };
if matches!(&node.inputs.get(reconnect_from_input_index), Some(NodeInput::Node { .. })) {
reconnect_to_input = Some(node.inputs[reconnect_from_input_index].clone());
}
}
}
for (node_id, node) in network.nodes.iter_mut() {
if *node_id == deleting_node_id {
continue;
}
for (input_index, input) in node.inputs.iter_mut().enumerate() {
let NodeInput::Node {
node_id: upstream_node_id,
output_index,
..
} = input
else {
continue;
};
if *upstream_node_id != deleting_node_id {
continue;
}
let Some(node_type) = document_node_types::resolve_document_node_type(&node.name) else {
warn!("Removing input of invalid node type '{}'", node.name);
return false;
};
if let NodeInput::Value { tagged_value, .. } = &node_type.inputs[input_index].default {
let mut refers_to_output_node = false;
// Use the first input node as the new input if deleting node's first input is a node,
// and the current node uses its primary output too
if let Some(reconnect_to_input) = &reconnect_to_input {
if *output_index == 0 {
refers_to_output_node = true;
*input = reconnect_to_input.clone()
}
}
if !refers_to_output_node {
*input = NodeInput::value(tagged_value.clone(), true);
}
}
}
}
true
}
/// Tries to remove a node from the network, returning true on success.
fn remove_node(&mut self, document_network: &mut NodeNetwork, selected_nodes: &mut SelectedNodes, node_id: NodeId, responses: &mut VecDeque<Message>, reconnect: bool) -> bool {
let Some(network) = document_network.nested_network_mut(&self.network) else {
return false;
};
if !Self::remove_references_from_network(network, node_id, reconnect) {
return false;
}
network.nodes.remove(&node_id);
selected_nodes.retain_selected_nodes(|&id| id != node_id);
responses.add(BroadcastEvent::SelectionChanged);
true
}
/// Gets the default node input based on the node name and the input index
pub fn default_node_input(name: String, index: usize) -> Option<NodeInput> {
resolve_document_node_type(&name)
.and_then(|node| node.inputs.get(index))
.map(|input: &DocumentInputType| input.default.clone())
}
/// Returns an iterator of nodes to be copied and their ids, excluding output and input nodes
pub fn copy_nodes<'a>(network: &'a NodeNetwork, new_ids: &'a HashMap<NodeId, NodeId>) -> impl Iterator<Item = (NodeId, DocumentNode)> + 'a {
new_ids
.iter()
.filter(|&(&id, _)| !network.outputs_contain(id))
.filter_map(|(&id, &new)| network.nodes.get(&id).map(|node| (new, node.clone())))
.map(move |(new, node)| (new, node.map_ids(Self::default_node_input, new_ids)))
}
}
impl Default for NodeGraphMessageHandler {
fn default() -> Self {
let right_side_widgets = vec![
// TODO: Replace this with an "Add Node" button, also next to an "Add Layer" button
TextLabel::new("Right Click in Graph to Add Nodes").italic(true).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
TextButton::new("Node Graph")
.icon(Some("GraphViewOpen".into()))
.tooltip("Hide Node Graph")
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::GraphViewOverlayToggle))
.on_update(move |_| DocumentMessage::GraphViewOverlayToggle.into())
.widget_holder(),
];
Self {
network: Vec::new(),
resolved_types: ResolvedDocumentNodeTypes::default(),
node_graph_errors: Vec::new(),
has_selection: false,
widgets: [LayoutGroup::Row { widgets: Vec::new() }, LayoutGroup::Row { widgets: right_side_widgets }],
}
}
}

View file

@ -1,7 +1,7 @@
#![allow(clippy::too_many_arguments)]
use super::document_node_types::NodePropertiesContext;
use super::FrontendGraphDataType;
use super::document_node_types::{NodePropertiesContext, IMAGINATE_NODE};
use super::utility_types::FrontendGraphDataType;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
@ -1609,13 +1609,7 @@ pub fn node_section_font(document_node: &DocumentNode, node_id: NodeId, _context
pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let imaginate_node = [context.nested_path, &[node_id]].concat();
let resolve_input = |name: &str| {
super::IMAGINATE_NODE
.inputs
.iter()
.position(|input| input.name == name)
.unwrap_or_else(|| panic!("Input {name} not found"))
};
let resolve_input = |name: &str| IMAGINATE_NODE.inputs.iter().position(|input| input.name == name).unwrap_or_else(|| panic!("Input {name} not found"));
let seed_index = resolve_input("Seed");
let resolution_index = resolve_input("Resolution");
let samples_index = resolve_input("Samples");

View file

@ -0,0 +1,118 @@
use graph_craft::document::value::TaggedValue;
use graph_craft::document::NodeId;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum FrontendGraphDataType {
#[default]
#[serde(rename = "general")]
General,
#[serde(rename = "raster")]
Raster,
#[serde(rename = "color")]
Color,
#[serde(rename = "general")]
Text,
#[serde(rename = "vector")]
Subpath,
#[serde(rename = "number")]
Number,
#[serde(rename = "general")]
Boolean,
/// Refers to the mathematical vector, with direction and magnitude.
#[serde(rename = "number")]
Vector,
#[serde(rename = "raster")]
GraphicGroup,
#[serde(rename = "artboard")]
Artboard,
#[serde(rename = "color")]
Palette,
}
impl FrontendGraphDataType {
pub const fn with_tagged_value(value: &TaggedValue) -> Self {
match value {
TaggedValue::String(_) => Self::Text,
TaggedValue::F32(_) | TaggedValue::F64(_) | TaggedValue::U32(_) | TaggedValue::DAffine2(_) => Self::Number,
TaggedValue::Bool(_) => Self::Boolean,
TaggedValue::DVec2(_) | TaggedValue::IVec2(_) => Self::Vector,
TaggedValue::Image(_) => Self::Raster,
TaggedValue::ImageFrame(_) => Self::Raster,
TaggedValue::Color(_) => Self::Color,
TaggedValue::RcSubpath(_) | TaggedValue::Subpaths(_) | TaggedValue::VectorData(_) => Self::Subpath,
TaggedValue::GraphicGroup(_) => Self::GraphicGroup,
TaggedValue::Artboard(_) => Self::Artboard,
TaggedValue::Palette(_) => Self::Palette,
_ => Self::General,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendGraphInput {
#[serde(rename = "dataType")]
pub data_type: FrontendGraphDataType,
pub name: String,
#[serde(rename = "resolvedType")]
pub resolved_type: Option<String>,
pub connected: Option<NodeId>,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendGraphOutput {
#[serde(rename = "dataType")]
pub data_type: FrontendGraphDataType,
pub name: String,
#[serde(rename = "resolvedType")]
pub resolved_type: Option<String>,
pub connected: Option<NodeId>,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendNode {
pub id: graph_craft::document::NodeId,
#[serde(rename = "isLayer")]
pub is_layer: bool,
pub alias: String,
pub name: String,
#[serde(rename = "primaryInput")]
pub primary_input: Option<FrontendGraphInput>,
#[serde(rename = "exposedInputs")]
pub exposed_inputs: Vec<FrontendGraphInput>,
#[serde(rename = "primaryOutput")]
pub primary_output: Option<FrontendGraphOutput>,
#[serde(rename = "exposedOutputs")]
pub exposed_outputs: Vec<FrontendGraphOutput>,
pub position: (i32, i32),
pub disabled: bool,
pub previewed: bool,
pub errors: Option<String>,
}
// (link_start, link_end, link_end_input_index)
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendNodeLink {
#[serde(rename = "linkStart")]
pub link_start: NodeId,
#[serde(rename = "linkStartOutputIndex")]
pub link_start_output_index: usize,
#[serde(rename = "linkEnd")]
pub link_end: NodeId,
#[serde(rename = "linkEndInputIndex")]
pub link_end_input_index: usize,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendNodeType {
pub name: String,
pub category: String,
}
impl FrontendNodeType {
pub fn new(name: &'static str, category: &'static str) -> Self {
Self {
name: name.to_string(),
category: category.to_string(),
}
}
}

View file

@ -5,6 +5,6 @@ pub mod utility_functions;
pub mod utility_types;
#[doc(inline)]
pub use overlays_message::*;
pub use overlays_message::{OverlaysMessage, OverlaysMessageDiscriminant};
#[doc(inline)]
pub use overlays_message_handler::*;
pub use overlays_message_handler::{OverlaysMessageData, OverlaysMessageHandler};

View file

@ -1,10 +1,8 @@
use super::utility_types::{empty_provider, OverlayProvider};
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
#[impl_message(Message, DocumentMessage, Overlays)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum OverlaysMessage {
Draw,

View file

@ -1,6 +1,11 @@
use super::utility_types::OverlayProvider;
use crate::messages::prelude::*;
pub struct OverlaysMessageData<'a> {
pub overlays_visible: bool,
pub ipp: &'a InputPreprocessorMessageHandler,
}
#[derive(Debug, Clone, Default)]
pub struct OverlaysMessageHandler {
pub overlay_providers: HashSet<OverlayProvider>,
@ -8,8 +13,10 @@ pub struct OverlaysMessageHandler {
context: Option<web_sys::CanvasRenderingContext2d>,
}
impl MessageHandler<OverlaysMessage, (bool, &InputPreprocessorMessageHandler)> for OverlaysMessageHandler {
fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque<Message>, (overlays_visible, ipp): (bool, &InputPreprocessorMessageHandler)) {
impl MessageHandler<OverlaysMessage, OverlaysMessageData<'_>> for OverlaysMessageHandler {
fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque<Message>, data: OverlaysMessageData) {
let OverlaysMessageData { overlays_visible, ipp } = data;
match message {
#[cfg(target_arch = "wasm32")]
OverlaysMessage::Draw => {

View file

@ -1,9 +1,7 @@
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
#[impl_message(Message, DocumentMessage, PropertiesPanel)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum PropertiesPanelMessage {
// Messages
Clear,

View file

@ -1,16 +1,14 @@
use super::utility_types::PropertiesPanelMessageHandlerData;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::NodePropertiesContext;
use crate::messages::portfolio::document::node_graph::document_node_types::NodePropertiesContext;
use crate::messages::portfolio::utility_types::PersistentData;
use crate::messages::prelude::*;
#[derive(Debug, Clone, Default)]
pub struct PropertiesPanelMessageHandler;
pub struct PropertiesPanelMessageHandler {}
impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPanelMessageHandlerData<'a>)> for PropertiesPanelMessageHandler {
fn process_message(&mut self, message: PropertiesPanelMessage, responses: &mut VecDeque<Message>, (persistent_data, data): (&PersistentData, PropertiesPanelMessageHandlerData)) {
use PropertiesPanelMessage::*;
let PropertiesPanelMessageHandlerData {
node_graph_message_handler,
executor,
@ -21,7 +19,7 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
} = data;
match message {
Clear => {
PropertiesPanelMessage::Clear => {
responses.add(LayoutMessage::SendLayout {
layout: Layout::WidgetLayout(WidgetLayout::new(vec![])),
layout_target: LayoutTarget::PropertiesOptions,
@ -31,7 +29,7 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
layout_target: LayoutTarget::PropertiesSections,
});
}
Refresh => {
PropertiesPanelMessage::Refresh => {
let mut context = NodePropertiesContext {
persistent_data,
responses,

View file

@ -1,11 +1,10 @@
use graph_craft::document::DocumentNode;
use graph_craft::document::NodeId;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[repr(u8)]
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug, specta::Type)]
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, Eq, Debug, specta::Type)]
pub enum Clipboard {
Internal,
@ -16,7 +15,7 @@ pub enum Clipboard {
pub const INTERNAL_CLIPBOARD_COUNT: u8 = Clipboard::_InternalClipboardCount as u8;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct CopyBufferEntry {
pub nodes: HashMap<NodeId, DocumentNode>,
pub selected: bool,

View file

@ -1,31 +1,31 @@
use glam::DVec2;
use serde::{Deserialize, Serialize};
use std::fmt;
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct DocumentId(pub u64);
#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize, Hash)]
#[derive(PartialEq, Eq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize, Hash)]
pub enum FlipAxis {
X,
Y,
}
#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize, Hash, specta::Type)]
#[derive(PartialEq, Eq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize, Hash, specta::Type)]
pub enum AlignAxis {
X,
Y,
}
#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize, Hash, specta::Type)]
#[derive(PartialEq, Eq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize, Hash, specta::Type)]
pub enum AlignAggregate {
Min,
Max,
Center,
}
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
pub enum DocumentMode {
#[default]
DesignMode,
@ -124,14 +124,14 @@ impl SnappingState {
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct BoundsSnapping {
pub edges: bool,
pub corners: bool,
pub edge_midpoints: bool,
pub centers: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct PointSnapping {
pub paths: bool,
pub path_intersections: bool,
@ -141,7 +141,7 @@ pub struct PointSnapping {
pub normals: bool,
pub tangents: bool,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize, PartialEq)]
pub enum GridType {
Rectangle { spacing: DVec2 },
Isometric { y_axis_spacing: f64, angle_a: f64, angle_b: f64 },
@ -178,7 +178,7 @@ impl GridType {
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq)]
pub struct GridSnapping {
pub origin: DVec2,
pub grid_type: GridType,
@ -310,7 +310,7 @@ impl fmt::Display for SnappingOptions {
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
pub struct PTZ {
pub pan: DVec2,
pub tilt: f64,

View file

@ -1,11 +1,10 @@
use graph_craft::document::{NodeId, NodeNetwork};
use serde::ser::SerializeStruct;
use serde::{Deserialize, Serialize};
use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, specta::Type)]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]
pub struct RawBuffer(Vec<u8>);
impl From<&[u64]> for RawBuffer {
@ -14,7 +13,7 @@ impl From<&[u64]> for RawBuffer {
Self(v_from_raw)
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, specta::Type)]
#[derive(Debug, Clone, serde::Deserialize, PartialEq, Eq, specta::Type)]
pub struct JsRawBuffer(Vec<u8>);
impl From<RawBuffer> for JsRawBuffer {
@ -22,7 +21,7 @@ impl From<RawBuffer> for JsRawBuffer {
Self(buffer.0)
}
}
impl Serialize for JsRawBuffer {
impl serde::Serialize for JsRawBuffer {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut buffer = serializer.serialize_struct("Buffer", 2)?;
buffer.serialize_field("pointer", &(self.0.as_ptr() as usize))?;
@ -31,7 +30,7 @@ impl Serialize for JsRawBuffer {
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq, specta::Type)]
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]
pub enum LayerClassification {
#[default]
Folder,
@ -39,7 +38,7 @@ pub enum LayerClassification {
Layer,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, specta::Type)]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]
pub struct LayerPanelEntry {
pub id: NodeId,
pub name: String,
@ -53,7 +52,7 @@ pub struct LayerPanelEntry {
pub depth: usize,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, specta::Type)]
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]
pub struct SelectedNodes(pub Vec<NodeId>);
impl SelectedNodes {
@ -106,5 +105,5 @@ impl SelectedNodes {
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, specta::Type)]
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]
pub struct CollapsedLayers(pub Vec<LayerNodeIdentifier>);

View file

@ -1,5 +1,5 @@
use crate::consts::{ROTATE_SNAP_ANGLE, SCALE_SNAP_INTERVAL};
use crate::messages::portfolio::document::node_graph::VectorDataModification;
use crate::messages::portfolio::document::graph_operation::utility_types::{TransformIn, VectorDataModification};
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils;
@ -398,11 +398,9 @@ impl<'a> Selected<'a> {
let new_pos_viewport = layerspace_rotation.transform_point2(viewport_point);
let point = *point_id;
let position = new_pos_viewport;
let modification = VectorDataModification::SetManipulatorPosition { point, position };
responses.add(GraphOperationMessage::Vector {
layer,
modification: VectorDataModification::SetManipulatorPosition { point, position },
});
responses.add(GraphOperationMessage::Vector { layer, modification });
}
}

View file

@ -1,9 +1,7 @@
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
#[impl_message(Message, PortfolioMessage, MenuBar)]
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]
pub enum MenuBarMessage {
// Messages
SendLayout,

View file

@ -3,21 +3,26 @@ use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
use crate::messages::prelude::*;
pub struct MenuBarMessageData {
pub has_active_document: bool,
pub rulers_visible: bool,
}
#[derive(Debug, Clone, Default)]
pub struct MenuBarMessageHandler {
has_active_document: bool,
rulers_visible: bool,
}
impl MessageHandler<MenuBarMessage, (bool, bool)> for MenuBarMessageHandler {
fn process_message(&mut self, message: MenuBarMessage, responses: &mut VecDeque<Message>, (has_active_document, rulers_visible): (bool, bool)) {
use MenuBarMessage::*;
impl MessageHandler<MenuBarMessage, MenuBarMessageData> for MenuBarMessageHandler {
fn process_message(&mut self, message: MenuBarMessage, responses: &mut VecDeque<Message>, data: MenuBarMessageData) {
let MenuBarMessageData { has_active_document, rulers_visible } = data;
self.has_active_document = has_active_document;
self.rulers_visible = rulers_visible;
match message {
SendLayout => self.send_layout(responses, LayoutTarget::MenuBar),
MenuBarMessage::SendLayout => self.send_layout(responses, LayoutTarget::MenuBar),
}
}

View file

@ -4,4 +4,4 @@ mod menu_bar_message_handler;
#[doc(inline)]
pub use menu_bar_message::{MenuBarMessage, MenuBarMessageDiscriminant};
#[doc(inline)]
pub use menu_bar_message_handler::MenuBarMessageHandler;
pub use menu_bar_message_handler::{MenuBarMessageData, MenuBarMessageHandler};

View file

@ -8,4 +8,4 @@ pub mod utility_types;
#[doc(inline)]
pub use portfolio_message::{PortfolioMessage, PortfolioMessageDiscriminant};
#[doc(inline)]
pub use portfolio_message_handler::PortfolioMessageHandler;
pub use portfolio_message_handler::{PortfolioMessageData, PortfolioMessageHandler};

View file

@ -5,10 +5,8 @@ use crate::messages::prelude::*;
use graphene_core::text::Font;
use serde::{Deserialize, Serialize};
#[impl_message(Message, Portfolio)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum PortfolioMessage {
// Sub-messages
#[child]

View file

@ -5,7 +5,7 @@ use crate::messages::dialog::simple_dialogs;
use crate::messages::frontend::utility_types::FrontendDocumentDetails;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
use crate::messages::portfolio::document::DocumentInputs;
use crate::messages::portfolio::document::DocumentMessageData;
use crate::messages::prelude::*;
use crate::messages::tool::utility_types::{HintData, HintGroup};
use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor};
@ -15,6 +15,11 @@ use graphene_core::text::Font;
use std::sync::Arc;
pub struct PortfolioMessageData<'a> {
pub ipp: &'a InputPreprocessorMessageHandler,
pub preferences: &'a PreferencesMessageHandler,
}
#[derive(Debug, Default)]
pub struct PortfolioMessageHandler {
menu_bar_message_handler: MenuBarMessageHandler,
@ -26,8 +31,10 @@ pub struct PortfolioMessageHandler {
pub executor: NodeGraphExecutor,
}
impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &PreferencesMessageHandler)> for PortfolioMessageHandler {
fn process_message(&mut self, message: PortfolioMessage, responses: &mut VecDeque<Message>, (ipp, preferences): (&InputPreprocessorMessageHandler, &PreferencesMessageHandler)) {
impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMessageHandler {
fn process_message(&mut self, message: PortfolioMessage, responses: &mut VecDeque<Message>, data: PortfolioMessageData) {
let PortfolioMessageData { ipp, preferences } = data;
match message {
// Sub-messages
PortfolioMessage::MenuBar(message) => {
@ -39,12 +46,13 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
rulers_visible = document.rulers_visible;
}
self.menu_bar_message_handler.process_message(message, responses, (has_active_document, rulers_visible));
self.menu_bar_message_handler
.process_message(message, responses, MenuBarMessageData { has_active_document, rulers_visible });
}
PortfolioMessage::Document(message) => {
if let Some(document_id) = self.active_document_id {
if let Some(document) = self.documents.get_mut(&document_id) {
let document_inputs = DocumentInputs {
let document_inputs = DocumentMessageData {
document_id,
ipp,
persistent_data: &self.persistent_data,
@ -58,7 +66,7 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
// Messages
PortfolioMessage::DocumentPassMessage { document_id, message } => {
if let Some(document) = self.documents.get_mut(&document_id) {
let document_inputs = DocumentInputs {
let document_inputs = DocumentMessageData {
document_id,
ipp,
persistent_data: &self.persistent_data,

View file

@ -1,14 +1,12 @@
use graphene_std::{imaginate::ImaginatePersistentData, text::FontCache};
use serde::{Deserialize, Serialize};
#[derive(Debug, Default)]
pub struct PersistentData {
pub font_cache: FontCache,
pub imaginate: ImaginatePersistentData,
}
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, Serialize, Deserialize)]
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize)]
pub enum Platform {
#[default]
Unknown,
@ -30,7 +28,7 @@ impl Platform {
}
}
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, Serialize, Deserialize)]
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize)]
pub enum KeyboardPlatformLayout {
/// Standard keyboard mapping used by Windows and Linux
#[default]

View file

@ -1,9 +1,7 @@
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
#[impl_message(Message, Preferences)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum PreferencesMessage {
Load { preferences: String },
ResetToDefaults,

View file

@ -2,9 +2,7 @@ use crate::messages::input_mapper::key_mapping::MappingVariant;
use crate::messages::prelude::*;
use graph_craft::imaginate_input::ImaginatePreferences;
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, specta::Type)]
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct PreferencesMessageHandler {
pub imaginate_server_hostname: String,
pub imaginate_refresh_frequency: f64,

View file

@ -1,30 +1,30 @@
// Root
pub use crate::utility_traits::{ActionList, AsMessage, MessageHandler, ToDiscriminant, TransitiveChild};
// Message, MessageDiscriminant, MessageHandler
// Message, MessageData, MessageDiscriminant, MessageHandler
pub use crate::messages::broadcast::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastMessageHandler};
pub use crate::messages::debug::{DebugMessage, DebugMessageDiscriminant, DebugMessageHandler};
pub use crate::messages::dialog::export_dialog::{ExportDialogMessage, ExportDialogMessageDiscriminant, ExportDialogMessageHandler};
pub use crate::messages::dialog::export_dialog::{ExportDialogMessage, ExportDialogMessageData, ExportDialogMessageDiscriminant, ExportDialogMessageHandler};
pub use crate::messages::dialog::new_document_dialog::{NewDocumentDialogMessage, NewDocumentDialogMessageDiscriminant, NewDocumentDialogMessageHandler};
pub use crate::messages::dialog::preferences_dialog::{PreferencesDialogMessage, PreferencesDialogMessageDiscriminant, PreferencesDialogMessageHandler};
pub use crate::messages::dialog::{DialogMessage, DialogMessageDiscriminant, DialogMessageHandler};
pub use crate::messages::dialog::preferences_dialog::{PreferencesDialogMessage, PreferencesDialogMessageData, PreferencesDialogMessageDiscriminant, PreferencesDialogMessageHandler};
pub use crate::messages::dialog::{DialogMessage, DialogMessageData, DialogMessageDiscriminant, DialogMessageHandler};
pub use crate::messages::frontend::{FrontendMessage, FrontendMessageDiscriminant};
pub use crate::messages::globals::{GlobalsMessage, GlobalsMessageDiscriminant, GlobalsMessageHandler};
pub use crate::messages::input_mapper::key_mapping::{KeyMappingMessage, KeyMappingMessageDiscriminant, KeyMappingMessageHandler};
pub use crate::messages::input_mapper::{InputMapperMessage, InputMapperMessageDiscriminant, InputMapperMessageHandler};
pub use crate::messages::input_preprocessor::{InputPreprocessorMessage, InputPreprocessorMessageDiscriminant, InputPreprocessorMessageHandler};
pub use crate::messages::input_mapper::key_mapping::{KeyMappingMessage, KeyMappingMessageData, KeyMappingMessageDiscriminant, KeyMappingMessageHandler};
pub use crate::messages::input_mapper::{InputMapperMessage, InputMapperMessageData, InputMapperMessageDiscriminant, InputMapperMessageHandler};
pub use crate::messages::input_preprocessor::{InputPreprocessorMessage, InputPreprocessorMessageData, InputPreprocessorMessageDiscriminant, InputPreprocessorMessageHandler};
pub use crate::messages::layout::{LayoutMessage, LayoutMessageDiscriminant, LayoutMessageHandler};
pub use crate::messages::portfolio::document::navigation::{NavigationMessage, NavigationMessageDiscriminant, NavigationMessageHandler};
pub use crate::messages::portfolio::document::node_graph::{GraphOperationMessage, GraphOperationMessageDiscriminant, GraphOperationMessageHandler};
pub use crate::messages::portfolio::document::graph_operation::{GraphOperationMessage, GraphOperationMessageData, GraphOperationMessageDiscriminant, GraphOperationMessageHandler};
pub use crate::messages::portfolio::document::navigation::{NavigationMessage, NavigationMessageData, NavigationMessageDiscriminant, NavigationMessageHandler};
pub use crate::messages::portfolio::document::node_graph::{NodeGraphMessage, NodeGraphMessageDiscriminant, NodeGraphMessageHandler};
pub use crate::messages::portfolio::document::overlays::{OverlaysMessage, OverlaysMessageDiscriminant, OverlaysMessageHandler};
pub use crate::messages::portfolio::document::overlays::{OverlaysMessage, OverlaysMessageData, OverlaysMessageDiscriminant, OverlaysMessageHandler};
pub use crate::messages::portfolio::document::properties_panel::{PropertiesPanelMessage, PropertiesPanelMessageDiscriminant, PropertiesPanelMessageHandler};
pub use crate::messages::portfolio::document::{DocumentMessage, DocumentMessageDiscriminant, DocumentMessageHandler};
pub use crate::messages::portfolio::menu_bar::{MenuBarMessage, MenuBarMessageDiscriminant, MenuBarMessageHandler};
pub use crate::messages::portfolio::{PortfolioMessage, PortfolioMessageDiscriminant, PortfolioMessageHandler};
pub use crate::messages::portfolio::document::{DocumentMessage, DocumentMessageData, DocumentMessageDiscriminant, DocumentMessageHandler};
pub use crate::messages::portfolio::menu_bar::{MenuBarMessage, MenuBarMessageData, MenuBarMessageDiscriminant, MenuBarMessageHandler};
pub use crate::messages::portfolio::{PortfolioMessage, PortfolioMessageData, PortfolioMessageDiscriminant, PortfolioMessageHandler};
pub use crate::messages::preferences::{PreferencesMessage, PreferencesMessageDiscriminant, PreferencesMessageHandler};
pub use crate::messages::tool::transform_layer::{TransformLayerMessage, TransformLayerMessageDiscriminant, TransformLayerMessageHandler};
pub use crate::messages::tool::{ToolMessage, ToolMessageDiscriminant, ToolMessageHandler};
pub use crate::messages::tool::{ToolMessage, ToolMessageData, ToolMessageDiscriminant, ToolMessageHandler};
pub use crate::messages::workspace::{WorkspaceMessage, WorkspaceMessageDiscriminant, WorkspaceMessageHandler};
// Message, MessageDiscriminant
@ -50,7 +50,6 @@ pub use crate::messages::tool::tool_messages::text_tool::{TextToolMessage, TextT
// Helper
pub use crate::messages::globals::global_variables::*;
pub use crate::messages::portfolio::document::node_graph::TransformIn;
pub use crate::messages::portfolio::document::utility_types::misc::DocumentId;
pub use graphite_proc_macros::*;

View file

@ -3,9 +3,7 @@ use crate::messages::prelude::Message;
use graphene_core::Color;
use serde::{Deserialize, Serialize};
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum ToolColorType {
Primary,
Secondary,

View file

@ -1,4 +1,4 @@
use crate::messages::portfolio::document::node_graph::VectorDataModification;
use crate::messages::portfolio::document::graph_operation::utility_types::VectorDataModification;
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::prelude::*;

View file

@ -1,8 +1,8 @@
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::snapping::SnapManager;
use crate::messages::{input_mapper::utility_types::input_keyboard::Key, portfolio::document::graph_operation::utility_types::TransformIn};
use glam::{DAffine2, DVec2, Vec2Swizzles};
use super::snapping::{SnapCandidatePoint, SnapConstraint, SnapData};

View file

@ -1,7 +1,7 @@
use super::graph_modification_utils;
use super::snapping::{are_manipulator_handles_colinear, SnapCandidatePoint, SnapData, SnapManager, SnappedPoint};
use crate::consts::{DRAG_THRESHOLD, INSERT_POINT_ON_SEGMENT_TOO_CLOSE_DISTANCE};
use crate::messages::portfolio::document::node_graph::VectorDataModification;
use crate::messages::portfolio::document::graph_operation::utility_types::VectorDataModification;
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::misc::{GeometrySnapSource, SnapSource};
use crate::messages::prelude::*;

View file

@ -9,6 +9,6 @@ pub mod utility_types;
#[doc(inline)]
pub use tool_message::{ToolMessage, ToolMessageDiscriminant};
#[doc(inline)]
pub use tool_message_handler::ToolMessageHandler;
pub use tool_message_handler::{ToolMessageData, ToolMessageHandler};
#[doc(inline)]
pub use transform_layer::{TransformLayerMessage, TransformLayerMessageDiscriminant};

View file

@ -3,10 +3,8 @@ use crate::messages::prelude::*;
use graphene_core::raster::color::Color;
use serde::{Deserialize, Serialize};
#[impl_message(Message, Tool)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum ToolMessage {
// Sub-messages
#[child]

View file

@ -9,6 +9,14 @@ use crate::node_graph_executor::NodeGraphExecutor;
use graphene_core::raster::color::Color;
pub struct ToolMessageData<'a> {
pub document_id: DocumentId,
pub document: &'a DocumentMessageHandler,
pub input: &'a InputPreprocessorMessageHandler,
pub persistent_data: &'a PersistentData,
pub node_graph: &'a NodeGraphExecutor,
}
#[derive(Debug, Default)]
pub struct ToolMessageHandler {
pub tool_state: ToolFsmState,
@ -16,13 +24,15 @@ pub struct ToolMessageHandler {
pub shape_editor: ShapeState,
}
impl MessageHandler<ToolMessage, (&DocumentMessageHandler, DocumentId, &InputPreprocessorMessageHandler, &PersistentData, &NodeGraphExecutor)> for ToolMessageHandler {
fn process_message(
&mut self,
message: ToolMessage,
responses: &mut VecDeque<Message>,
(document, document_id, input, persistent_data, node_graph): (&DocumentMessageHandler, DocumentId, &InputPreprocessorMessageHandler, &PersistentData, &NodeGraphExecutor),
) {
impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, data: ToolMessageData) {
let ToolMessageData {
document_id,
document,
input,
persistent_data,
node_graph,
} = data;
let font_cache = &persistent_data.font_cache;
match message {

View file

@ -17,7 +17,7 @@ pub struct ArtboardTool {
}
#[impl_message(Message, ToolMessage, Artboard)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum ArtboardToolMessage {
// Standard messages
Abort,
@ -50,8 +50,6 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for Artboar
}
fn actions(&self) -> ActionList {
use ArtboardToolFsmState::*;
let mut common = actions!(ArtboardToolMessageDiscriminant;
DeleteSelected,
NudgeSelected,
@ -59,7 +57,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for Artboar
);
let additional = match self.fsm_state {
Ready => actions!(ArtboardToolMessageDiscriminant; PointerDown),
ArtboardToolFsmState::Ready => actions!(ArtboardToolMessageDiscriminant; PointerDown),
_ => actions!(ArtboardToolMessageDiscriminant; PointerUp, Abort),
};
common.extend(additional);

View file

@ -1,6 +1,6 @@
use super::tool_prelude::*;
use crate::messages::portfolio::document::node_graph::resolve_document_node_type;
use crate::messages::portfolio::document::node_graph::transform_utils::{get_current_normalized_pivot, get_current_transform};
use crate::messages::portfolio::document::graph_operation::transform_utils::{get_current_normalized_pivot, get_current_transform};
use crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
@ -13,7 +13,7 @@ use graphene_core::Color;
const BRUSH_MAX_SIZE: f64 = 5000.;
#[derive(PartialEq, Copy, Clone, Debug, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum DrawMode {
Draw = 0,
Erase,
@ -52,7 +52,7 @@ impl Default for BrushOptions {
}
#[impl_message(Message, ToolMessage, Brush)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum BrushToolMessage {
// Standard messages
Abort,
@ -65,7 +65,7 @@ pub enum BrushToolMessage {
UpdateOptions(BrushToolMessageOptionsUpdate),
}
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum BrushToolMessageOptionsUpdate {
BlendMode(BlendMode),
ChangeDiameter(f64),
@ -222,15 +222,13 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for BrushTo
}
fn actions(&self) -> ActionList {
use BrushToolFsmState::*;
match self.fsm_state {
Ready => actions!(BrushToolMessageDiscriminant;
BrushToolFsmState::Ready => actions!(BrushToolMessageDiscriminant;
DragStart,
DragStop,
UpdateOptions,
),
Drawing => actions!(BrushToolMessageDiscriminant;
BrushToolFsmState::Drawing => actions!(BrushToolMessageDiscriminant;
DragStop,
PointerMove,
Abort,

View file

@ -34,7 +34,7 @@ impl Default for EllipseToolOptions {
}
}
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum EllipseOptionsUpdate {
FillColor(Option<Color>),
FillColorType(ToolColorType),
@ -45,7 +45,7 @@ pub enum EllipseOptionsUpdate {
}
#[impl_message(Message, ToolMessage, Ellipse)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum EllipseToolMessage {
// Standard messages
Overlays(OverlayContext),
@ -138,14 +138,12 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for Ellipse
}
fn actions(&self) -> ActionList {
use EllipseToolFsmState::*;
match self.fsm_state {
Ready => actions!(EllipseToolMessageDiscriminant;
EllipseToolFsmState::Ready => actions!(EllipseToolMessageDiscriminant;
DragStart,
PointerMove,
),
Drawing => actions!(EllipseToolMessageDiscriminant;
EllipseToolFsmState::Drawing => actions!(EllipseToolMessageDiscriminant;
DragStop,
Abort,
PointerMove,

View file

@ -8,7 +8,7 @@ pub struct EyedropperTool {
}
#[impl_message(Message, ToolMessage, Eyedropper)]
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum EyedropperToolMessage {
// Standard messages
Abort,

View file

@ -8,7 +8,7 @@ pub struct FillTool {
}
#[impl_message(Message, ToolMessage, Fill)]
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum FillToolMessage {
// Standard messages
Abort,
@ -42,14 +42,12 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for FillToo
self.fsm_state.process_event(message, &mut (), tool_data, &(), responses, true);
}
fn actions(&self) -> ActionList {
use FillToolFsmState::*;
match self.fsm_state {
Ready => actions!(FillToolMessageDiscriminant;
FillToolFsmState::Ready => actions!(FillToolMessageDiscriminant;
FillPrimaryColor,
FillSecondaryColor,
),
Filling => actions!(FillToolMessageDiscriminant;
FillToolFsmState::Filling => actions!(FillToolMessageDiscriminant;
PointerUp,
Abort,
),

View file

@ -1,5 +1,5 @@
use super::tool_prelude::*;
use crate::messages::portfolio::document::node_graph::VectorDataModification;
use crate::messages::portfolio::document::graph_operation::utility_types::VectorDataModification;
use crate::messages::portfolio::document::overlays::utility_functions::path_endpoint_overlays;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
@ -14,7 +14,6 @@ use graphene_core::Color;
use bezier_rs::ManipulatorGroup;
use glam::DVec2;
use serde::{Deserialize, Serialize};
#[derive(Default)]
pub struct FreehandTool {
@ -40,7 +39,7 @@ impl Default for FreehandOptions {
}
#[impl_message(Message, ToolMessage, Freehand)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum FreehandToolMessage {
// Standard messages
Overlays(OverlayContext),
@ -54,7 +53,7 @@ pub enum FreehandToolMessage {
UpdateOptions(FreehandOptionsUpdate),
}
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum FreehandOptionsUpdate {
FillColor(Option<Color>),
FillColorType(ToolColorType),
@ -149,14 +148,12 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for Freehan
}
fn actions(&self) -> ActionList {
use FreehandToolFsmState::*;
match self.fsm_state {
Ready => actions!(FreehandToolMessageDiscriminant;
FreehandToolFsmState::Ready => actions!(FreehandToolMessageDiscriminant;
DragStart,
DragStop,
),
Drawing => actions!(FreehandToolMessageDiscriminant;
FreehandToolFsmState::Drawing => actions!(FreehandToolMessageDiscriminant;
DragStop,
PointerMove,
Abort,

View file

@ -21,7 +21,7 @@ pub struct GradientOptions {
}
#[impl_message(Message, ToolMessage, Gradient)]
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum GradientToolMessage {
// Standard messages
Abort,
@ -37,7 +37,7 @@ pub enum GradientToolMessage {
UpdateOptions(GradientOptionsUpdate),
}
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum GradientOptionsUpdate {
Type(GradientType),
}

View file

@ -1,9 +1,10 @@
use super::tool_prelude::*;
use crate::messages::portfolio::document::node_graph::{self, IMAGINATE_NODE};
use crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type;
use crate::messages::portfolio::document::node_graph::document_node_types::{new_image_network, IMAGINATE_NODE};
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::tool::common_functionality::resize::Resize;
use serde::{Deserialize, Serialize};
use graph_craft::document::{generate_uuid, DocumentNodeMetadata, NodeId, NodeInput};
#[derive(Default)]
pub struct ImaginateTool {
@ -12,7 +13,7 @@ pub struct ImaginateTool {
}
#[impl_message(Message, ToolMessage, Imaginate)]
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum ImaginateToolMessage {
// Standard messages
Abort,
@ -35,13 +36,11 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for Imagina
}
fn actions(&self) -> ActionList {
use ImaginateToolFsmState::*;
match self.fsm_state {
Ready => actions!(ImaginateToolMessageDiscriminant;
ImaginateToolFsmState::Ready => actions!(ImaginateToolMessageDiscriminant;
DragStart,
),
Drawing => actions!(ImaginateToolMessageDiscriminant;
ImaginateToolFsmState::Drawing => actions!(ImaginateToolMessageDiscriminant;
DragStop,
Abort,
Resize,
@ -107,17 +106,15 @@ impl Fsm for ImaginateToolFsmState {
shape_data.layer = Some(LayerNodeIdentifier::new(NodeId(generate_uuid()), document.network()));
responses.add(DocumentMessage::DeselectAllLayers);
use graph_craft::document::*;
// Utility function to offset the position of each consecutive node
let mut pos = 8;
let mut next_pos = || {
pos += 8;
graph_craft::document::DocumentNodeMetadata::position((pos, 4))
DocumentNodeMetadata::position((pos, 4))
};
// Get the node type for the Transform and Imaginate nodes
let Some(transform_node_type) = crate::messages::portfolio::document::node_graph::resolve_document_node_type("Transform") else {
let Some(transform_node_type) = resolve_document_node_type("Transform") else {
warn!("Transform node should be in registry");
return ImaginateToolFsmState::Drawing;
};
@ -128,7 +125,7 @@ impl Fsm for ImaginateToolFsmState {
let imaginate_node_id = NodeId(101);
// Create the network based on the Input -> Output passthrough default network
let mut network = node_graph::new_image_network(16, imaginate_node_id);
let mut network = new_image_network(16, imaginate_node_id);
// Insert the nodes into the default network
network.nodes.insert(
@ -137,7 +134,7 @@ impl Fsm for ImaginateToolFsmState {
);
network.nodes.insert(
imaginate_node_id,
imaginate_node_type.to_document_node_default_inputs([Some(graph_craft::document::NodeInput::node(transform_node_id, 0))], next_pos()),
imaginate_node_type.to_document_node_default_inputs([Some(NodeInput::node(transform_node_id, 0))], next_pos()),
);
responses.add(NodeGraphMessage::ShiftNode { node_id: imaginate_node_id });

View file

@ -1,5 +1,6 @@
use super::tool_prelude::*;
use crate::consts::LINE_ROTATE_SNAP_ANGLE;
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
@ -34,7 +35,7 @@ impl Default for LineOptions {
}
#[impl_message(Message, ToolMessage, Line)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum LineToolMessage {
// Standard messages
Overlays(OverlayContext),
@ -49,7 +50,7 @@ pub enum LineToolMessage {
UpdateOptions(LineOptionsUpdate),
}
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum LineOptionsUpdate {
LineWeight(f64),
StrokeColor(Option<Color>),

View file

@ -25,5 +25,4 @@ pub mod tool_prelude {
pub use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
pub use glam::{DAffine2, DVec2};
pub use serde::{Deserialize, Serialize};
}

View file

@ -7,7 +7,7 @@ pub struct NavigateTool {
}
#[impl_message(Message, ToolMessage, Navigate)]
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum NavigateToolMessage {
// Standard messages
Abort,
@ -45,10 +45,8 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for Navigat
}
fn actions(&self) -> ActionList {
use NavigateToolFsmState::*;
match self.fsm_state {
Ready => actions!(NavigateToolMessageDiscriminant;
NavigateToolFsmState::Ready => actions!(NavigateToolMessageDiscriminant;
TranslateCanvasBegin,
RotateCanvasBegin,
ZoomCanvasBegin,

View file

@ -21,7 +21,7 @@ pub struct PathTool {
}
#[impl_message(Message, ToolMessage, Path)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum PathToolMessage {
// Standard messages
Abort,
@ -174,10 +174,8 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
// Different actions depending on state may be wanted:
fn actions(&self) -> ActionList {
use PathToolFsmState::*;
match self.fsm_state {
Ready => actions!(PathToolMessageDiscriminant;
PathToolFsmState::Ready => actions!(PathToolMessageDiscriminant;
FlipSmoothSharp,
MouseDown,
Delete,
@ -188,7 +186,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
BreakPath,
DeleteAndBreakPath,
),
Dragging => actions!(PathToolMessageDiscriminant;
PathToolFsmState::Dragging => actions!(PathToolMessageDiscriminant;
Escape,
RightClick,
FlipSmoothSharp,
@ -198,7 +196,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
BreakPath,
DeleteAndBreakPath,
),
DrawingBox => actions!(PathToolMessageDiscriminant;
PathToolFsmState::DrawingBox => actions!(PathToolMessageDiscriminant;
FlipSmoothSharp,
DragStop,
PointerMove,
@ -209,7 +207,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
Escape,
RightClick,
),
InsertPoint => actions!(PathToolMessageDiscriminant;
PathToolFsmState::InsertPoint => actions!(PathToolMessageDiscriminant;
Enter,
MouseDown,
PointerMove,

View file

@ -1,6 +1,6 @@
use super::tool_prelude::*;
use crate::consts::LINE_ROTATE_SNAP_ANGLE;
use crate::messages::portfolio::document::node_graph::VectorDataModification;
use crate::messages::portfolio::document::graph_operation::utility_types::VectorDataModification;
use crate::messages::portfolio::document::overlays::utility_functions::path_overlays;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
@ -41,7 +41,7 @@ impl Default for PenOptions {
}
#[impl_message(Message, ToolMessage, Pen)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum PenToolMessage {
// Standard messages
Abort,
@ -68,7 +68,7 @@ enum PenToolFsmState {
PlacingAnchor,
}
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum PenOptionsUpdate {
FillColor(Option<Color>),
FillColorType(ToolColorType),
@ -217,12 +217,10 @@ impl PenToolData {
let first_or_last = if from_start { manipulator_groups.first() } else { manipulator_groups.last() };
let Some(last_handle) = first_or_last else { return };
let id = last_handle.id;
let modification = VectorDataModification::SetManipulatorColinearHandlesState { id, colinear: false };
// Stop the handles on the first point from being colinear
responses.add(GraphOperationMessage::Vector {
layer,
modification: VectorDataModification::SetManipulatorColinearHandlesState { id, colinear: false },
});
responses.add(GraphOperationMessage::Vector { layer, modification });
}
fn create_new_path(

View file

@ -39,7 +39,7 @@ impl Default for PolygonOptions {
}
#[impl_message(Message, ToolMessage, Polygon)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum PolygonToolMessage {
// Standard messages
Overlays(OverlayContext),
@ -54,13 +54,13 @@ pub enum PolygonToolMessage {
UpdateOptions(PolygonOptionsUpdate),
}
#[derive(PartialEq, Copy, Clone, Debug, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum PolygonType {
Convex = 0,
Star = 1,
}
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum PolygonOptionsUpdate {
FillColor(Option<Color>),
FillColorType(ToolColorType),
@ -182,14 +182,12 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for Polygon
}
fn actions(&self) -> ActionList {
use PolygonToolFsmState::*;
match self.fsm_state {
Ready => actions!(PolygonToolMessageDiscriminant;
PolygonToolFsmState::Ready => actions!(PolygonToolMessageDiscriminant;
DragStart,
PointerMove,
),
Drawing => actions!(PolygonToolMessageDiscriminant;
PolygonToolFsmState::Drawing => actions!(PolygonToolMessageDiscriminant;
DragStop,
Abort,
PointerMove,

View file

@ -34,7 +34,7 @@ impl Default for RectangleToolOptions {
}
}
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum RectangleOptionsUpdate {
FillColor(Option<Color>),
FillColorType(ToolColorType),
@ -45,7 +45,7 @@ pub enum RectangleOptionsUpdate {
}
#[impl_message(Message, ToolMessage, Rectangle)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum RectangleToolMessage {
// Standard messages
Overlays(OverlayContext),
@ -127,14 +127,12 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for Rectang
}
fn actions(&self) -> ActionList {
use RectangleToolFsmState::*;
match self.fsm_state {
Ready => actions!(RectangleToolMessageDiscriminant;
RectangleToolFsmState::Ready => actions!(RectangleToolMessageDiscriminant;
DragStart,
PointerMove,
),
Drawing => actions!(RectangleToolMessageDiscriminant;
RectangleToolFsmState::Drawing => actions!(RectangleToolMessageDiscriminant;
DragStop,
Abort,
PointerMove,

View file

@ -4,6 +4,7 @@ use super::tool_prelude::*;
use crate::application::generate_uuid;
use crate::consts::{ROTATE_SNAP_ANGLE, SELECTION_TOLERANCE};
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis};
@ -31,12 +32,12 @@ pub struct SelectOptions {
nested_selection_behavior: NestedSelectionBehavior,
}
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum SelectOptionsUpdate {
NestedSelectionBehavior(NestedSelectionBehavior),
}
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, Serialize, Deserialize, specta::Type)]
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum NestedSelectionBehavior {
#[default]
Deepest,
@ -52,7 +53,7 @@ impl fmt::Display for NestedSelectionBehavior {
}
}
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct SelectToolPointerKeys {
pub axis_align: Key,
pub snap_angle: Key,
@ -61,7 +62,7 @@ pub struct SelectToolPointerKeys {
}
#[impl_message(Message, ToolMessage, Select)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum SelectToolMessage {
// Standard messages
Abort,
@ -204,8 +205,6 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SelectT
}
fn actions(&self) -> ActionList {
use SelectToolFsmState::*;
let mut common = actions!(SelectToolMessageDiscriminant;
PointerMove,
Abort,
@ -214,7 +213,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SelectT
);
let additional = match self.fsm_state {
Ready { .. } => actions!(SelectToolMessageDiscriminant; DragStart),
SelectToolFsmState::Ready { .. } => actions!(SelectToolMessageDiscriminant; DragStart),
_ => actions!(SelectToolMessageDiscriminant; DragStop),
};
common.extend(additional);

Some files were not shown because too many files have changed in this diff Show more